Synchronization Behavior
Understanding the exact comparison and failure rules is important before copying a large history.
Workflow
resolve range
→ persist session
→ fetch source and target snapshots
→ compare and build queue
→ enrich queued tracks from source
→ submit queued scrobbles to target
→ verify against fresh source and target histories
→ build result and delete completed state
All work is synchronous. Metadata lookups and submissions are performed one scrobble at a time in ascending timestamp order.
Range resolution
The requested range is inclusive at both ends.
- An explicit start is used unchanged.
- Without a start, the target's most recent scrobble timestamp is used.
- If the target is empty and start is omitted,
MissingStartTimestampErroris raised. - An explicit end is used unchanged.
- Without an end,
int(time.time())is used. - Negative bounds and start-after-end ranges raise
InvalidSyncRangeError.
Using the target's latest timestamp as the inferred start is safe because the event already present at that exact timestamp is removed during comparison, while other source events at the same timestamp can still be discovered.
Snapshot and identity rules
Both networks are queried for the full resolved range. Snapshot storage de-duplicates within each side using:
(artist, title, timestamp)
A source event is queued only when no target event has exactly the same three fields. Album, MusicBrainz ID, and duration do not participate. SQLite's default text equality is exact, so spelling and case differences are distinct.
Consequences:
- two plays of the same track at different timestamps are distinct;
- album differences alone are considered the same event;
- duplicate source rows with the same identity collapse into one snapshot row;
- a remote service that changes artist/title spelling can cause an apparent missing event.
Metadata enrichment
For every missing event, the engine asks the source network for track metadata by artist and title. It stores an optional MusicBrainz ID and duration in milliseconds. Missing metadata values are valid; malformed responses are not.
Duration is converted to seconds at submission time with round(milliseconds / 1000) and a minimum of one second. Album comes from the source snapshot.
Submission and retries
Each ready item is marked syncing before the target call and synced only after the call returns successfully.
Metadata and submission operations retry only NetworkConnectionError. Defaults are three total attempts and a five-second delay. Once retries are exhausted, the item is marked failed with its stage and error, processing stops, and the error is re-raised. Authentication errors and invalid responses are not retried.
Snapshot fetches and resume reconciliation calls are not covered by the retry service.
Post-sync verification
After all pending submissions finish, the engine verifies the entire resolved range against fresh network data:
- it fetches source history once;
- it fetches target history and compares exact
(artist, title, timestamp)occurrences; - when source occurrences are missing, it waits two seconds and checks the target again; and
- it returns after source coverage passes or after three target checks.
The wait allows a target service time to expose accepted submissions through its recent-tracks API. Unlike stored snapshots, verification is occurrence-aware: repeated identical events are counted with multiplicity. It reports both source events missing on the target and target events missing on the source.
Verification polling does not use RetryService. A network exception from a fresh read propagates immediately and leaves the persisted session available for resume, which recovers its stored state and reaches verification again without resubmitting already-synced items. If all reads succeed but source coverage remains incomplete, SyncResult is returned normally with the missing events and the local session is deleted.
The CLI displays this result and offers to start another synchronization over the same range. The core API leaves that decision to its caller.
Progress callbacks
Callbacks receive SyncProgress for:
- every phase transition;
- source and target snapshot completion;
- initial and per-item metadata progress; and
- initial and per-item submission progress.
current_scrobble_completed=False identifies an item currently being worked on; True reports that the same item finished. A phase transition uses zero totals. Comparison itself has no item-level progress report.
Callbacks are invoked synchronously and exceptions propagate, leaving recoverable state when persistence has already occurred.
Verification has no separate SyncPhase or item-level progress callback. The completed phase callback is reported only after verification and local cleanup succeed.
Result semantics
SyncResult counts the stored source snapshot, target snapshot, total queued items, synced items, and failed items. Its verification field separately contains fresh source and target counts, both directions of missing occurrences, and source-coverage and exact-history properties. On a normal return all queued items have synchronized, and the completed session state is then deleted even if fresh verification remains incomplete.
Direction and concurrency
The engine is one-way and does not merge histories automatically. A separate reverse run is required for bidirectional coverage, and each direction should be reviewed independently.
The CLI prevents a new run when its database contains unfinished work. The Python service does not impose this policy. It also does not provide an async API or explicit cross-process scheduling; applications should serialize operations that share a database or isolate them in separate database files.