#include "intent_open_shared_deck.h" #include "../deck_loader/deck_loader.h" #include "../widgets/dialogs/dlg_shared_decks_preview.h" #include "../widgets/tabs/tab_supervisor.h" #include "intent_connect_to_server.h" #include #include #include #include #include #include #include #include #include #include IntentOpenSharedDeck::IntentOpenSharedDeck(TabSupervisor *_tabSupervisor, RemoteClient *_remoteClient, const CardDatabaseQuerier *_querier, std::unique_ptr _context) : Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), querier(_querier), context(_context.release()) { downloadTimer = new QTimer(this); downloadTimer->setSingleShot(true); downloadTimer->setInterval(15000); connect(downloadTimer, &QTimer::timeout, this, &IntentOpenSharedDeck::onDownloadTimeout); } bool IntentOpenSharedDeck::checkPrecondition() const { if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) { return false; } // serverName()/serverPort() reflect the server the client was configured // to connect to, which may differ from the actual TCP peer (e.g. when // connecting through a proxy), so compare those configured values. The // share token must be resolved against the host the link named — a link to // the same host on another port is a different server. if (remoteClient->serverName().compare(context->serverContext.hostname, Qt::CaseInsensitive) != 0) { return false; } return QString::number(remoteClient->serverPort()) == context->serverContext.port; } void IntentOpenSharedDeck::onPreconditionSatisfied() { // Resolve the share token to its items first; a share can contain more than // one deck, and each item is downloaded by id. Time the round trip like the // downloads, so a silent server cannot hang the chain forever. listPhase = true; downloadTimer->start(); Command_DeckShareList cmd; cmd.set_token(context->shareToken.toStdString()); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); connect(pend, &PendingCommand::finished, this, &IntentOpenSharedDeck::listShareFinished); remoteClient->sendCommand(pend); } void IntentOpenSharedDeck::onPreconditionNotSatisfied() { runDependency(new IntentConnectToServer(remoteClient, &context->serverContext)); } void IntentOpenSharedDeck::listShareFinished(const Response &response, const CommandContainer & /* commandContainer */) { downloadTimer->stop(); listPhase = false; if (response.response_code() != Response::RespOk) { emitFailed(tr("The shared deck could not be found or has expired")); return; } const Response_DeckShareList &resp = response.GetExtension(Response_DeckShareList::ext); if (resp.items_size() == 0) { emitFailed(tr("The shared deck is empty")); return; } QList items; items.reserve(resp.items_size()); for (const ServerInfo_DeckShareItem &item : resp.items()) { items.append(item); itemNames.insert(item.id(), QString::fromStdString(item.name())); } const QString serverText = context->serverContext.hostname + ":" + context->serverContext.port; // Ask the user which decks to open before downloading anything. previewDialog = new DlgSharedDecksPreview(tabSupervisor, querier, QString::fromStdString(resp.name()), resp.expires_at(), serverText, items); connect(previewDialog, &DlgSharedDecksPreview::openRequested, this, &IntentOpenSharedDeck::startDownloads); connect(previewDialog, &DlgSharedDecksPreview::cancelled, this, &IntentOpenSharedDeck::emitCancelled); connect(previewDialog, &DlgSharedDecksPreview::cancelled, previewDialog, &QWidget::deleteLater); previewDialog->show(); previewDialog->raise(); previewDialog->activateWindow(); } void IntentOpenSharedDeck::startDownloads(const QList &itemIds) { pendingItemIds = itemIds; totalItems = itemIds.size(); completedItems = 0; loadedDecks.clear(); downloadNextItem(); } void IntentOpenSharedDeck::downloadNextItem() { if (pendingItemIds.isEmpty()) { finishAll(); return; } currentItemId = pendingItemIds.takeFirst(); downloadTimer->start(); Command_DeckShareDownload cmd; cmd.set_token(context->shareToken.toStdString()); cmd.set_item_id(currentItemId); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); connect(pend, &PendingCommand::finished, this, &IntentOpenSharedDeck::downloadShareFinished); remoteClient->sendCommand(pend); } void IntentOpenSharedDeck::downloadShareFinished(const Response &response, const CommandContainer & /* commandContainer */) { downloadTimer->stop(); QString failureReason; if (response.response_code() != Response::RespOk) { failureReason = tr("Failed to download the shared deck"); } else { const Response_DeckShareDownload &resp = response.GetExtension(Response_DeckShareDownload::ext); const QString deckString = QString::fromStdString(resp.deck()); if (deckString.isEmpty()) { failureReason = tr("The shared deck is empty"); } else { std::optional deckOpt = DeckLoader::loadFromRemote(deckString, LoadedDeck::LoadInfo::NON_REMOTE_ID); if (!deckOpt) { failureReason = tr("The shared deck could not be loaded"); } else { loadedDecks.append(deckOpt.value()); ++completedItems; previewDialog->setDownloadProgress(completedItems, totalItems, itemNames.value(currentItemId, tr("Unknown deck"))); downloadNextItem(); return; } } } onItemFailure(failureReason); } void IntentOpenSharedDeck::onItemFailure(const QString &reason) { downloadTimer->stop(); if (loadedDecks.isEmpty()) { previewDialog->deleteLater(); emitFailed(reason); return; } const int downloadedCount = loadedDecks.size(); const QMessageBox::StandardButton answer = QMessageBox::question( previewDialog, tr("Open shared decks"), tr("Could not download the deck \"%1\".\n\n%n deck(s) were already downloaded. Open them?", "", downloadedCount) .arg(itemNames.value(currentItemId, tr("Unknown deck"))), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer == QMessageBox::Yes) { finishAll(); } else { previewDialog->deleteLater(); emitCancelled(); } } void IntentOpenSharedDeck::onDownloadTimeout() { // The list phase has no preview dialog yet to report progress into; fail the // whole intent instead of letting the shared deck hang in limbo. if (listPhase) { emitFailed(tr("Timed out while loading the shared deck")); return; } onItemFailure(tr("Timed out while downloading the shared deck")); } void IntentOpenSharedDeck::finishAll() { previewDialog->deleteLater(); for (const LoadedDeck &deck : loadedDecks) { tabSupervisor->openDeckInNewTab(deck); } emitFinished(); }