fix(xtream): preserve favorites across catalog refreshes

This commit is contained in:
4gray committed 2026-10-13 23:31:25 +02:00
1 parent ec4b9286a7
commit 8ccfcb68cc
121 files changed
+4000 -398

No files matched your search

+20
View File
@@ -656,6 +656,26 @@ is unavailable or the SQLite playlist migration has not completed.
5. `DB_GET_GLOBAL_FAVORITES`
6. `DB_GET_ALL_GLOBAL_FAVORITES`
7. `DB_REORDER_GLOBAL_FAVORITES`
8. `DB_RESTORE_XTREAM_FAVORITES`
9. `DB_GET_FAVORITES_FOR_BACKUP`
Xtream favorites survive catalog/category deletion through nullable `content_id`
links and independent typed provider identities plus display snapshots. Reads
return the durable `favorite_id`, nullable catalog IDs and `catalog_status`;
remove accepts `{ favoriteId }` and reorder accepts `favorite_id` without a
catalog row. Legacy content-ID callers resolve playlist/type/provider identity,
including duplicate category rows. Import completion reconciles links and saved
metadata; detail enrichment also updates the snapshot. Known identity conflicts
remain unresolved. See [Xtream compatibility](./xtream-portal-compatibility.md#durable-electron-favorites).
`DB_RESTORE_XTREAM_FAVORITES` materializes pending/backup snapshots without an
online catalog. Inserts and their replay receipt share one synchronous worker
transaction, so retry after removal does not resurrect an item. Explicit backup
imports supply a fresh restore ID; legacy pending payloads use a canonical
hash. `DB_DELETE_XTREAM_CONTENT` returns no canonical favorites to replay.
`DB_GET_FAVORITES_FOR_BACKUP` deliberately includes locked/unresolved rows;
ordinary UI reads preserve parental-lock filtering. A failed export read must
propagate to the caller.
### Recently viewed
@@ -37,6 +37,60 @@ See [Nx Workspace Boundaries](./nx-workspace-boundaries.md),
[Portal Detail Navigation](./portal-detail-navigation.md), and
[VOD Multi-Source](./vod-multi-source.md).
## Durable Electron Favorites
SQLite favorites are user-owned records keyed by playlist, normalized content
kind (`live`, `movie`, `series`), and provider ID. The nullable catalog link uses
`ON DELETE SET NULL`; only explicit favorite removal/clear or playlist deletion
removes a saved record. Refresh and import-cache cleanup never replay canonical
favorites from a temporary snapshot. The two Refresh entry points continue to
share `XtreamRefreshFlowService` and its per-playlist single-flight guard.
Favorite rows retain a durable `favorite_id`, saved title/artwork, added date
and order. Their `id`/`category_id` may be null; consumers must not invent a
catalog ID. Remove/reorder can use the stable favorite ID while detail toggles
resolve any category row through the same typed provider identity. Completed
imports reconnect valid identities and refresh display snapshots. Metadata
learned in detail views is copied into the saved snapshot before catalog loss.
Conflicting known TMDB IDs or release years prevent automatic reconnection;
changed provider IDs never reconnect by title.
Adding a conflicting replacement returns an explicit failure and retains the
older favorite. The detail toggle stays inactive and explains that the older
entry must be removed from Favorites before the replacement can be saved.
`catalog_status` is `present`, `missing`, or `unknown`. Only a completed import
of the relevant type can prove absence. Failed, cancelled and in-progress
imports remain unknown. Collections and dashboard rails retain unresolved rows
with saved titles/status and removal/order controls; they suppress playback,
details, downloads and EPG requests that depend on catalog resolution. History
navigation rechecks current collection state. Removing a live row or making it
unavailable closes its active detail and invalidates pending selection work,
including a retained player during a channel switch. Unresolved records are not
promoted to dashboard hero slides. Parental locks still gate display: an
unresolved category cannot prove that it is unlocked, so those records remain
stored but are withheld while locks apply.
Legacy pending favorites are converted before destructive refresh, import
requests and ordinary collection reads, even if portal-status preflight is
offline. Backup merges convert any older pending favorites before parking the
incoming archive. Favorite inserts and a replay receipt commit together;
retries cannot resurrect a subsequently removed favorite. Deliberate backup import creates a
fresh receipt ID, which stays internal and is never exported. Identity-only old
backups use a labelled saved-item fallback until catalog metadata returns.
Backups include unresolved snapshots and locked records through a dedicated
unfiltered read; a failed favorites read fails export rather than producing an
empty collection. New backup imports materialize favorites before waiting for
an online catalog. Existing history, hidden-category and playback-position
restore contracts remain separate.
Both Xtream data-source and portal-activity capabilities require the pending
favorite conversion bridge; activity reads also require playlist lookup.
Records already deleted by an older build cannot be recovered without a
surviving pending state or backup. Opening an Xtream section may initialize
content when its cache is not ready; startup imports and explicit refresh use
the same durable persistence invariant. This does not add a refresh scheduler.
## PWA Collection Persistence
Collection operations carry `{ id, type }`: `id` remains the SQLite row ID