mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
[DeckSync] Address auto-sync review: disconnect, backfill and modal conflicts
- Fail the run when the connection drops while a sync is running, and clear the active conflict, so the queue is not sent on a dead client and a re-login as another account never receives this profile's uploads. - Backfill last_modified from upload_time in the 37 -> 38 migration and drop the "last_modified is null" arm from the sync-check query, so rows that predate the column stop reporting as changed on every poll. - Skip conflicts during auto runs and report how many were skipped, instead of opening the modal conflict dialog from a timer or a file-watcher event.
This commit is contained in:
parent
00cb839a30
commit
6d67b76fa3
4 changed files with 41 additions and 3 deletions
|
|
@ -109,6 +109,14 @@ DeckSyncManager::DeckSyncManager(AbstractClient *_client, ColorIdentityProvider
|
|||
if (status == StatusDisconnected) {
|
||||
offline = true;
|
||||
debounceTimer->stop();
|
||||
// A run cannot survive the session it was started on: the in-flight
|
||||
// command comes back RespNotConnected, the remaining queue would be
|
||||
// sent on a dead client, and a re-login as another account would
|
||||
// receive this profile's uploads and deletes.
|
||||
if (syncing) {
|
||||
activeConflict = ActiveConflict();
|
||||
failSync(tr("Disconnected."));
|
||||
}
|
||||
}
|
||||
},
|
||||
Qt::UniqueConnection);
|
||||
|
|
@ -201,7 +209,7 @@ void DeckSyncManager::onDebounceTimeout()
|
|||
return;
|
||||
}
|
||||
if (canAutoRun()) {
|
||||
syncNow();
|
||||
startSync(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -233,10 +241,16 @@ bool DeckSyncManager::isSyncing() const
|
|||
}
|
||||
|
||||
void DeckSyncManager::syncNow()
|
||||
{
|
||||
startSync(false);
|
||||
}
|
||||
|
||||
void DeckSyncManager::startSync(bool _autoRun)
|
||||
{
|
||||
if (syncing) {
|
||||
return;
|
||||
}
|
||||
autoRun = _autoRun;
|
||||
const DeckSyncProfile runProfile = buildProfile();
|
||||
if (client == nullptr || !runProfile.isValid() || deckPath.isEmpty()) {
|
||||
emit syncFinished(SyncSummary{}, tr("Not connected to a server."));
|
||||
|
|
@ -253,6 +267,7 @@ void DeckSyncManager::syncNow()
|
|||
downloadedCount = 0;
|
||||
unchangedCount = 0;
|
||||
skippedCount = 0;
|
||||
conflictCount = 0;
|
||||
pendingUploads.clear();
|
||||
pendingDownloads.clear();
|
||||
pendingDeletes.clear();
|
||||
|
|
@ -746,6 +761,16 @@ void DeckSyncManager::queueConflict(const QString &relativePath,
|
|||
void DeckSyncManager::handleConflictDownload(const QString &relativePath, const QString &serverDeckString)
|
||||
{
|
||||
const ConflictDownload ctx = pendingConflicts.take(relativePath);
|
||||
if (autoRun) {
|
||||
// An auto run has no user in front of it: opening the modal conflict
|
||||
// dialog from a timer or a file-watcher event would pop up mid-game.
|
||||
// Leave the path untouched so the next manual sync classifies it again
|
||||
// and asks.
|
||||
++conflictCount;
|
||||
++skippedCount;
|
||||
processNext();
|
||||
return;
|
||||
}
|
||||
if (sessionDefault == DeckSyncConflictKeepMine) {
|
||||
applyUpload(relativePath, ctx.serverPath, ctx.deckId, ctx.guid);
|
||||
processNext();
|
||||
|
|
@ -1245,6 +1270,9 @@ void DeckSyncManager::finishSync()
|
|||
.arg(summary.downloaded)
|
||||
.arg(summary.unchanged)
|
||||
.arg(summary.skipped));
|
||||
if (conflictCount > 0) {
|
||||
emit statusMessage(tr("%1 conflicting decks skipped. Press Sync now to resolve them.").arg(conflictCount));
|
||||
}
|
||||
emit syncFinished(summary, QString());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ private:
|
|||
qint64 lastModifiedSecs = 0;
|
||||
};
|
||||
|
||||
void startSync(bool autoRun);
|
||||
void failSync(const QString &error);
|
||||
void finishSync();
|
||||
[[nodiscard]] SyncSummary currentSummary() const;
|
||||
|
|
@ -240,6 +241,8 @@ private:
|
|||
int downloadedCount = 0;
|
||||
int unchangedCount = 0;
|
||||
int skippedCount = 0;
|
||||
int conflictCount = 0;
|
||||
bool autoRun = false;
|
||||
};
|
||||
|
||||
#endif // DECK_SYNC_MANAGER_H
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ ALTER TABLE `cockatrice_decklist_files`
|
|||
ADD COLUMN `last_modified` datetime NULL AFTER `hash`,
|
||||
ADD COLUMN `deleted` tinyint(1) NOT NULL DEFAULT 0 AFTER `last_modified`;
|
||||
|
||||
-- Rows created before this migration only get a last_modified when a client
|
||||
-- re-uploads them, so every sync check would keep reporting them as changed.
|
||||
-- Backfill from the upload time once, here, instead of carrying a
|
||||
-- "last_modified is null" branch in the poll query forever.
|
||||
UPDATE `cockatrice_decklist_files` SET `last_modified` = `upload_time` WHERE `last_modified` IS NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_deck_versions` (
|
||||
`id` int(7) unsigned zerofill NOT NULL auto_increment,
|
||||
`deck_id` int(7) unsigned zerofill NOT NULL,
|
||||
|
|
|
|||
|
|
@ -904,10 +904,11 @@ bool AbstractServerSocketInterface::syncCheckHelper(int userId, qint64 sinceSecs
|
|||
// 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.
|
||||
// last_modified is backfilled by the 37 -> 38 migration, so a plain range
|
||||
// check is enough.
|
||||
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))");
|
||||
"and last_modified >= FROM_UNIXTIME(:since)");
|
||||
query->bindValue(":id_user", userId);
|
||||
query->bindValue(":since", sinceSecs);
|
||||
if (!sqlInterface->execSqlQuery(query)) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue