diff --git a/cockatrice/src/interface/deck_sync/deck_sync_manager.cpp b/cockatrice/src/interface/deck_sync/deck_sync_manager.cpp index 768ba3411..4eceeee9c 100644 --- a/cockatrice/src/interface/deck_sync/deck_sync_manager.cpp +++ b/cockatrice/src/interface/deck_sync/deck_sync_manager.cpp @@ -299,11 +299,9 @@ void DeckSyncManager::syncNow() .modified = QFileInfo(absolutePath).lastModified()}); } - if (!firstSyncRun && sidecar.lastSyncSecs > 0) { - sendSyncCheck(sidecar.lastSyncSecs); - return; - } - fetchFullList(); + // Always ask the server first: its server_time becomes the sync baseline, + // so uploads racing the DeckList response cannot fall outside the window. + sendSyncCheck(sidecar.lastSyncSecs); } void DeckSyncManager::fetchFullList() @@ -334,6 +332,9 @@ void DeckSyncManager::onCheckFinished(const Response &response, const CommandCon return; } const Response_DeckSyncStatus &status = response.GetExtension(Response_DeckSyncStatus::ext); + if (status.has_server_time()) { + pendingServerTime = static_cast(status.server_time()); + } DeckSyncSidecar sidecar(sidecarPath); if (!sidecar.load()) { fetchFullList(); diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index 48111f6ea..8f8555292 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -260,6 +260,13 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, syncManager = new DeckSyncManager( client, [](const DeckList &deck) { return getDeckColorIdentity(deck, CardDatabaseManager::query()); }, this); + syncManager->setDeckPath(SettingsCache::instance().paths().getDeckPath()); + connect(&SettingsCache::instance().paths(), &PathsSettings::deckPathChanged, this, [this] { + const QString deckPath = SettingsCache::instance().paths().getDeckPath(); + localDirModel->setRootPath(deckPath); + localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0)); + syncManager->setDeckPath(deckPath); + }); syncManager->setConflictDefault(SettingsCache::instance().deckSync().getConflictDefault()); syncManager->setAutoSyncEnabled(SettingsCache::instance().deckSync().getAutoSync()); syncManager->setSyncIntervalMinutes(SettingsCache::instance().deckSync().getSyncIntervalMinutes()); diff --git a/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp b/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp index 41ccf6460..4f92b017c 100644 --- a/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp @@ -71,6 +71,7 @@ QString PathsSettings::getRedirectCachePath() const void PathsSettings::setDeckPath(const QString &_deckPath) { setValue(_deckPath, "decks"); + emit deckPathChanged(); } void PathsSettings::setFiltersPath(const QString &_filtersPath) diff --git a/libcockatrice_settings/libcockatrice/settings/paths_settings.h b/libcockatrice_settings/libcockatrice/settings/paths_settings.h index fa00428e3..aef65aff5 100644 --- a/libcockatrice_settings/libcockatrice/settings/paths_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/paths_settings.h @@ -59,6 +59,7 @@ public: void setAppDirWarningAcknowledged(bool _appDirWarningAcknowledged); signals: + void deckPathChanged(); void cardDatabasePathChanged(); void picsPathChanged(); void themeChanged(); diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 6da45fd41..50a544538 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -898,51 +898,31 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckSetVisibility(const return Response::RespOk; } -bool AbstractServerSocketInterface::syncCheckHelper(int folderId, - const QString &prefix, - int userId, - qint64 sinceSecs, - Response_DeckSyncStatus *status) +bool AbstractServerSocketInterface::syncCheckHelper(int userId, qint64 sinceSecs, Response_DeckSyncStatus *status) { - QSqlQuery *query = sqlInterface->prepareQuery("select id, name from {prefix}_decklist_folders where id_parent = " - ":id_parent and id_user = :id_user"); - query->bindValue(":id_parent", folderId); - query->bindValue(":id_user", userId); - if (!sqlInterface->execSqlQuery(query)) { - return false; - } - QList> subfolders; - while (query->next()) { - subfolders.append({query->value(0).toInt(), query->value(1).toString()}); - } - for (const auto &[subfolderId, subfolderName] : subfolders) { - const QString path = prefix.isEmpty() ? subfolderName : prefix + "/" + subfolderName; - if (!syncCheckHelper(subfolderId, path, userId, sinceSecs, status)) { - return false; - } - } - - query = sqlInterface->prepareQuery("select id, name, guid, hash, last_modified, deleted from " - "{prefix}_decklist_files where id_folder = :id_folder and id_user = :id_user " - "and (last_modified is null or last_modified > FROM_UNIXTIME(:since))"); - query->bindValue(":id_folder", folderId); + // Single query over the user's files: folder rows are hard-deleted while + // their files linger as tombstones, so walking the live folder tree would + // miss deletes pointing at nonexistent folders. The client only checks + // changed_files_size(), so folder paths are not reconstructed here. + QSqlQuery *query = sqlInterface->prepareQuery("select id, guid, hash, last_modified, deleted from " + "{prefix}_decklist_files where id_user = :id_user " + "and (last_modified is null or last_modified >= " + "FROM_UNIXTIME(:since))"); query->bindValue(":id_user", userId); query->bindValue(":since", sinceSecs); if (!sqlInterface->execSqlQuery(query)) { return false; } while (query->next()) { - const QString name = query->value(1).toString(); DeckSyncChangedFile *changed = status->add_changed_files(); - changed->set_path((prefix.isEmpty() ? name : prefix + "/" + name).toStdString()); changed->set_deck_id(query->value(0).toUInt()); - changed->set_guid(query->value(2).toString().toStdString()); - changed->set_hash(query->value(3).toString().toStdString()); - const QDateTime lastModified = query->value(4).toDateTime(); + changed->set_guid(query->value(1).toString().toStdString()); + changed->set_hash(query->value(2).toString().toStdString()); + const QDateTime lastModified = query->value(3).toDateTime(); if (lastModified.isValid()) { changed->set_last_modified(lastModified.toSecsSinceEpoch()); } - changed->set_deleted(query->value(5).toBool()); + changed->set_deleted(query->value(4).toBool()); } return true; } @@ -957,7 +937,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdDeckSyncCheck(const Com sqlInterface->checkSql(); Response_DeckSyncStatus *re = new Response_DeckSyncStatus; - if (!syncCheckHelper(0, QString(), userInfo->id(), static_cast(cmd.last_sync_time()), re)) { + if (!syncCheckHelper(userInfo->id(), static_cast(cmd.last_sync_time()), re)) { delete re; return Response::RespContextError; } diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 7a01f7c1c..a55f8df0f 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -165,8 +165,7 @@ private: bool storeDeckVersion(int deckId, const QString &content, const QString &hash); DeckList *getDeckFromDatabase(int deckId); Response::ResponseCode cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc); - bool - syncCheckHelper(int folderId, const QString &prefix, int userId, qint64 sinceSecs, Response_DeckSyncStatus *status); + bool syncCheckHelper(int userId, qint64 sinceSecs, Response_DeckSyncStatus *status); Response::ResponseCode cmdDeckSyncCheck(const Command_DeckSyncCheck &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckDownloadPublic(const Command_DeckDownloadPublic &cmd, ResponseContainer &rc); Response::ResponseCode cmdDeckShareCreate(const Command_DeckShareCreate &cmd, ResponseContainer &rc);