- 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.
- Remove the duplicated download loop and the stray '<' plus extra brace
that left the head unbuildable.
- Skip the fallback upload under DownloadMode so a local-only deck is not
pushed when the user asked for downloads only.
- Treat a server row without a hash as unknown rather than changed, so
pre-migration decks are not re-downloaded on every sync.
- Treat a record whose server row is gone as a remote delete only when the
local file still matches the sidecar's base hash. An edit made after the
remote delete is newer than the delete, so it uploads and revives the row
instead of being removed.
- Revive a tombstone with the same user and guid in cmdDeckUpload instead of
inserting a second row next to it, so the deck keeps its id and version
history and the promise in servatrice.ini.example holds.
- Run tombstone purging on its own clock, driven by the new
[deck_sync] purge_interval option, so disabling share cleanup no longer
disables purging silently.
- Write the base snapshot through QSaveFile and log a failed commit, so a
crash or a full disk cannot leave a truncated base for DeckDiff::merge().
Sync keyed local files to server rows by path, but the server names a row
after the deck, not after the file, so any deck whose file name and deck name
differ uploaded again on every run. A second pass then brought the server copy
back as an extra local file.
- Match by the sidecar's row id, then guid, then content, and only then by the
server path, so renaming either side no longer duplicates a deck.
- Key the server tree by row id: the server allows several rows with the same
name in one folder, and keying by path silently dropped all but one.
- Stop discarding rows whose name contains '/' ("U/W Control"). They stay
matchable, and only the download file name is sanitised.
- Build the download path from the folder plus a sanitised row name, including
every folder segment, so a hostile server name cannot escape the deck folder.
- Download a colliding server row under a unique name and count it, instead of
skipping it without a trace.
- Hash the whole deck instead of the card hash: SHA-1 over the native deck
string covers name, printings, banner, tokens and sideboard plans. The client
and the server hash the exact bytes they exchange, so both agree.
Adds deck_content_hash_test for the client/server hashing invariant and for the
name-only change the card hash cannot see.
- Emit SyncSummary from syncFinished() instead of four positional ints, and
build it from a single currentSummary() helper, so the counts cannot drift
between the status message and the signal.
- Use a designated initializer for LocalFile, like the other records.
- Drop setClient() and setColorIdentityProvider(): both fields are already
constructor parameters and the setters had no callers. setProfile() and
setDeckPath() stay, since the tab re-reads them before every run.
- Fix the include paths in tab_deck_storage.h and the narrowing conversion of
the tree item id, so the branch builds again.