Skip to content

fix(trakt): make history sync incremental and idempotent - #84

Open
moodiness wants to merge 1 commit into
ellite:mainfrom
moodiness:fix/trakt-history-safety
Open

fix(trakt): make history sync incremental and idempotent#84
moodiness wants to merge 1 commit into
ellite:mainfrom
moodiness:fix/trakt-history-safety

Conversation

@moodiness

Copy link
Copy Markdown
Contributor

Summary

  • preserve every local Trakt watch event and its original watched_at timestamp during full pushes
  • compare timestamped local events with remote Trakt history before writing, making repeated full pushes idempotent
  • add cursor-based incremental history pulls with a 5-minute overlap window; only advance the cursor after a successful sync
  • keep the first pull and explicit Full resync exhaustive, while normal pulls fetch only recent history
  • add a dedicated Full resync action in Settings

Why

Issue #82 reports duplicate and incorrect Trakt history after pulls. The pull-to-push echo guard already exists, but the full push path still collapsed local history to one event per media and recreated events at push time. This change removes that destructive behavior and reduces recurring pulls to a bounded, overlap-safe window.

Verification

  • python -m unittest discover -s tests -v — 59 passed
  • python -m compileall -q core models routers tests
  • python -m alembic headsb6c7d8e9f0a1 (head)
  • npm ci && npm run build — Astro production build passed

Addresses #82.

@ellite

ellite commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Hi!
I have a release almost ready, with a lot of changes. I think this one is included there.
Please wait for the release and check if this PR still makes sense.

Thank you for all the contributions.

@moodiness

Copy link
Copy Markdown
Contributor Author

Thanks, understood. I'll wait for the release, then rebase and compare the new Trakt implementation against this PR. I'll update or close it depending on what remains relevant.

@moodiness
moodiness force-pushed the fix/trakt-history-safety branch from 55b3669 to a0bdc96 Compare July 26, 2026 17:57
@moodiness

Copy link
Copy Markdown
Contributor Author

Rebased this PR onto the current upstream/main at ce6eea8 (v1.42.0). The rebase completed without conflicts.

I also compared the v1.42.0 Trakt/webhook changes with this PR. The new webhook deduplication in 9569105 addresses a likely trigger for #82: repeated Plex deliveries and the media.scrobble / media.stop pair can no longer create duplicate completed events in the common case.

This PR remains complementary rather than redundant:

  • v1.42.0 does not change the Trakt pull or full-push implementation;
  • a normal full push still collapses local history to one entry per media and posts it without the original watched_at, so repeated full pushes are not idempotent;
  • v1.42.0 does not add cursor-based incremental history pulls or the explicit full-resync action;
  • this PR preserves every local event timestamp, checks remote history before writing, and bounds recurring pulls with an overlap-safe cursor.

Validation after the rebase:

  • backend suite: 71 tests passed;
  • python -m compileall -q core models routers tests: passed;
  • Alembic: single head b6c7d8e9f0a1;
  • frontend production build: passed.

@moodiness
moodiness force-pushed the fix/trakt-history-safety branch from a0bdc96 to fb73805 Compare July 27, 2026 17:08
@moodiness

Copy link
Copy Markdown
Contributor Author

Rebased and updated this PR on top of v1.43.0 (09cf030). The branch now keeps the sync cancellation work from 27f66b8, reuses its timestamp-aware add_to_history_batch() client, and chains the Trakt cursor migration after the new a9b8c7d6e5f4 migration.

The v1.43 fix addresses the immediate “all titles watched now” failure by sending a timestamp, but these safety gaps remained and are retained/fixed by this PR:

  • only completed WatchEvent rows are eligible for a Trakt history push; an incomplete-only event can no longer be sent with watched_at=None and become a false play stamped as now;
  • every distinct completed local play is preserved, instead of collapsing rewatches to only the latest timestamp per media;
  • the full push compares timestamped local plays with remote Trakt history and skips existing events before posting, rather than submitting the same batch on every run;
  • recurring pulls remain cursor-based with a five-minute overlap, while the explicit Full resync action remains exhaustive;
  • timestamp formatting uses stable UTC millisecond precision so local and remote history keys compare consistently.

Added regression coverage for the two gaps found while reviewing v1.43:

  1. two completed plays of the same title are both included in the full-push payload;
  2. a media item with only an incomplete watch event is not pushed to Trakt history.

Validation after the rebase:

  • full backend suite: 73 tests passed;
  • Trakt suite: 14 tests passed;
  • focused Full Push safety scenarios: 3 tests passed;
  • python -m compileall -q core models routers tests: passed;
  • Alembic: single head b6c7d8e9f0a1;
  • Astro production build: passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants