mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -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
8bb0a9b98a
commit
875e87d1bc
4 changed files with 41 additions and 3 deletions
|
|
@ -109,6 +109,14 @@ DeckSyncManager::DeckSyncManager(AbstractClient *_client, ColorIdentityProvider
|
||||||
if (status == StatusDisconnected) {
|
if (status == StatusDisconnected) {
|
||||||
offline = true;
|
offline = true;
|
||||||
debounceTimer->stop();
|
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);
|
Qt::UniqueConnection);
|
||||||
|
|
@ -201,7 +209,7 @@ void DeckSyncManager::onDebounceTimeout()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (canAutoRun()) {
|
if (canAutoRun()) {
|
||||||
syncNow();
|
startSync(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,10 +241,16 @@ bool DeckSyncManager::isSyncing() const
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckSyncManager::syncNow()
|
void DeckSyncManager::syncNow()
|
||||||
|
{
|
||||||
|
startSync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeckSyncManager::startSync(bool _autoRun)
|
||||||
{
|
{
|
||||||
if (syncing) {
|
if (syncing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
autoRun = _autoRun;
|
||||||
const DeckSyncProfile runProfile = buildProfile();
|
const DeckSyncProfile runProfile = buildProfile();
|
||||||
if (client == nullptr || !runProfile.isValid() || deckPath.isEmpty()) {
|
if (client == nullptr || !runProfile.isValid() || deckPath.isEmpty()) {
|
||||||
emit syncFinished(SyncSummary{}, tr("Not connected to a server."));
|
emit syncFinished(SyncSummary{}, tr("Not connected to a server."));
|
||||||
|
|
@ -253,6 +267,7 @@ void DeckSyncManager::syncNow()
|
||||||
downloadedCount = 0;
|
downloadedCount = 0;
|
||||||
unchangedCount = 0;
|
unchangedCount = 0;
|
||||||
skippedCount = 0;
|
skippedCount = 0;
|
||||||
|
conflictCount = 0;
|
||||||
pendingUploads.clear();
|
pendingUploads.clear();
|
||||||
pendingDownloads.clear();
|
pendingDownloads.clear();
|
||||||
pendingDeletes.clear();
|
pendingDeletes.clear();
|
||||||
|
|
@ -746,6 +761,16 @@ void DeckSyncManager::queueConflict(const QString &relativePath,
|
||||||
void DeckSyncManager::handleConflictDownload(const QString &relativePath, const QString &serverDeckString)
|
void DeckSyncManager::handleConflictDownload(const QString &relativePath, const QString &serverDeckString)
|
||||||
{
|
{
|
||||||
const ConflictDownload ctx = pendingConflicts.take(relativePath);
|
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) {
|
if (sessionDefault == DeckSyncConflictKeepMine) {
|
||||||
applyUpload(relativePath, ctx.serverPath, ctx.deckId, ctx.guid);
|
applyUpload(relativePath, ctx.serverPath, ctx.deckId, ctx.guid);
|
||||||
processNext();
|
processNext();
|
||||||
|
|
@ -1245,6 +1270,9 @@ void DeckSyncManager::finishSync()
|
||||||
.arg(summary.downloaded)
|
.arg(summary.downloaded)
|
||||||
.arg(summary.unchanged)
|
.arg(summary.unchanged)
|
||||||
.arg(summary.skipped));
|
.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());
|
emit syncFinished(summary, QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,7 @@ private:
|
||||||
qint64 lastModifiedSecs = 0;
|
qint64 lastModifiedSecs = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void startSync(bool autoRun);
|
||||||
void failSync(const QString &error);
|
void failSync(const QString &error);
|
||||||
void finishSync();
|
void finishSync();
|
||||||
[[nodiscard]] SyncSummary currentSummary() const;
|
[[nodiscard]] SyncSummary currentSummary() const;
|
||||||
|
|
@ -240,6 +241,8 @@ private:
|
||||||
int downloadedCount = 0;
|
int downloadedCount = 0;
|
||||||
int unchangedCount = 0;
|
int unchangedCount = 0;
|
||||||
int skippedCount = 0;
|
int skippedCount = 0;
|
||||||
|
int conflictCount = 0;
|
||||||
|
bool autoRun = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DECK_SYNC_MANAGER_H
|
#endif // DECK_SYNC_MANAGER_H
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ ALTER TABLE `cockatrice_decklist_files`
|
||||||
ADD COLUMN `last_modified` datetime NULL AFTER `hash`,
|
ADD COLUMN `last_modified` datetime NULL AFTER `hash`,
|
||||||
ADD COLUMN `deleted` tinyint(1) NOT NULL DEFAULT 0 AFTER `last_modified`;
|
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` (
|
CREATE TABLE IF NOT EXISTS `cockatrice_deck_versions` (
|
||||||
`id` int(7) unsigned zerofill NOT NULL auto_increment,
|
`id` int(7) unsigned zerofill NOT NULL auto_increment,
|
||||||
`deck_id` int(7) unsigned zerofill NOT NULL,
|
`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
|
// their files linger as tombstones, so walking the live folder tree would
|
||||||
// miss deletes pointing at nonexistent folders. The client only checks
|
// miss deletes pointing at nonexistent folders. The client only checks
|
||||||
// changed_files_size(), so folder paths are not reconstructed here.
|
// 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 "
|
QSqlQuery *query = sqlInterface->prepareQuery("select id, guid, hash, last_modified, deleted from "
|
||||||
"{prefix}_decklist_files where id_user = :id_user "
|
"{prefix}_decklist_files where id_user = :id_user "
|
||||||
"and (last_modified is null or last_modified >= "
|
"and last_modified >= FROM_UNIXTIME(:since)");
|
||||||
"FROM_UNIXTIME(:since))");
|
|
||||||
query->bindValue(":id_user", userId);
|
query->bindValue(":id_user", userId);
|
||||||
query->bindValue(":since", sinceSecs);
|
query->bindValue(":since", sinceSecs);
|
||||||
if (!sqlInterface->execSqlQuery(query)) {
|
if (!sqlInterface->execSqlQuery(query)) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue