mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 12:36:15 -08:00
fix(xtream): preserve pending backups and restore positions atomically (#1873)
* fix(xtream): preserve pending backups and restore positions atomically * fix(xtream): retain sparse duplicate playback metadata on restore * fix(xtream): require atomic restore in SQLite capability gate * fix(xtream): preflight atomic restore without changing storage
This commit is contained in:
1 parent
2b400cb81d
commit
2fa03c8efc
25 files changed
+1037
-142
No files matched your search
@@ -5,13 +5,13 @@ settings screen.
|
||||
|
||||
## Entry Points
|
||||
|
||||
- UI: `/Users/4gray/Code/iptvnator/apps/web/src/app/settings/settings-backup-section.component.ts`
|
||||
- UI: `apps/web/src/app/settings/settings-backup-section.component.ts`
|
||||
(embedded in `settings.component.html`), with the file read/handoff in
|
||||
`/Users/4gray/Code/iptvnator/apps/web/src/app/settings/settings-backup.facade.ts`
|
||||
- Backup service: `/Users/4gray/Code/iptvnator/libs/services/src/lib/playlist-backup.service.ts`
|
||||
- Manifest types: `/Users/4gray/Code/iptvnator/libs/shared/interfaces/src/lib/playlist-backup.interface.ts`
|
||||
`apps/web/src/app/settings/settings-backup.facade.ts`
|
||||
- Backup service: `libs/services/src/lib/playlist-backup.service.ts`
|
||||
- Manifest types: `libs/shared/interfaces/src/lib/playlist-backup.interface.ts`
|
||||
- Xtream pending restore storage:
|
||||
`/Users/4gray/Code/iptvnator/libs/services/src/lib/xtream-pending-restore.service.ts`
|
||||
`libs/services/src/lib/xtream-pending-restore.service.ts`
|
||||
|
||||
## Manifest Contract
|
||||
|
||||
@@ -63,6 +63,19 @@ Xtream backups export only connection metadata plus portable user state.
|
||||
- favorites by `{ contentType, xtreamId, addedAt?, position? }`
|
||||
- recently viewed by `{ contentType, xtreamId, viewedAt }`
|
||||
- playback positions as `PlaybackPositionData[]`
|
||||
- optional VOD source pins by `{ matchKey, contentId, updatedAt? }`
|
||||
|
||||
An imported Xtream archive can wait for its catalog to download. Export reads
|
||||
that pending snapshot before consulting the catalog: its hidden categories,
|
||||
favorites, history, and positions are authoritative, including empty lists.
|
||||
Pending pins are authoritative when present; an absent `sourcePins` field keeps
|
||||
the existing store's pins when that store is available, or stays absent when it
|
||||
is unavailable. Re-export neither applies nor consumes the pending snapshot,
|
||||
so it also works before the portal has been opened and in browser runtimes.
|
||||
Parental category locks still come from the current lock store.
|
||||
|
||||
A failed pending-storage or playback-position read fails export. An unreadable
|
||||
collection must never be serialized as a successful empty collection.
|
||||
|
||||
Explicitly excluded:
|
||||
|
||||
@@ -164,11 +177,10 @@ worker IPC boundary in the snake_case wire shape declared by
|
||||
`XCategoryFromDb`/`XtreamCategoryFromDb`; the category operations project
|
||||
their Drizzle rows explicitly to keep that contract true.
|
||||
|
||||
Clearing the playlist's existing pins goes through a dedicated
|
||||
delete-by-playlist operation, not the keyed clear: that one caps its key list
|
||||
to bound an IN clause, so a playlist with more pinned movies than the cap kept
|
||||
the surplus while still reporting success. A failure now fails the entry
|
||||
rather than leaving the union of old and archived pins.
|
||||
Replacing the playlist's pins deletes by playlist and writes the replacements
|
||||
in one transaction. It does not use the capped keyed-clear operation, which
|
||||
could leave surplus pins behind. Any replacement failure fails the entry and
|
||||
retains the pending snapshot for retry.
|
||||
|
||||
`sourcePins` (VOD multi-source) is the one **optional** collection, and the
|
||||
normalizer preserves that: an absent field stays absent rather than becoming
|
||||
@@ -190,12 +202,28 @@ Electron restore behavior:
|
||||
1. Category import reads pending hidden-category state while saving categories.
|
||||
2. After content import, favorites/recent state is restored by typed
|
||||
`{ contentType, xtreamId }` matching.
|
||||
3. Playback positions are cleared and re-applied from backup state.
|
||||
3. Playback positions are replaced for the destination playlist in one SQLite
|
||||
transaction through `DB_REPLACE_ALL_PLAYBACK_POSITIONS`. Empty means clear;
|
||||
a failed insert rolls back the deletion and all earlier inserts.
|
||||
4. VOD source pins are re-applied against the IMPORTED playlist id.
|
||||
|
||||
For existing Xtream playlists with a fully populated offline cache, backup
|
||||
import applies the restore immediately. Otherwise the typed restore payload is
|
||||
left pending until the next Xtream initialization/import.
|
||||
left pending until the next Xtream initialization/import. Both paths use strict
|
||||
playback-position replacement: unavailable storage, rejected IPC, or an
|
||||
unsuccessful response fail the restore and leave the pending snapshot intact.
|
||||
Atomic restore support is checked separately from the ordinary Xtream SQLite
|
||||
capability gate. An older preload missing the replacement method keeps using
|
||||
SQLite for playback and backup export. Import parks the snapshot first, then
|
||||
rejects unsupported atomic restore before checking catalog completeness or
|
||||
writing category visibility, collections, positions, or pins. Deferred restore
|
||||
performs the same preflight before applying user state. The retained snapshot
|
||||
can be retried once atomic replacement becomes available. Ordinary playback
|
||||
keeps its existing capability checks and best-effort persistence API.
|
||||
The whole restore is not one database transaction; already applied
|
||||
categories/favorites/history can
|
||||
remain after a later failure, but the snapshot remains available to retry the
|
||||
complete restore. It is consumed only after every restore step succeeds.
|
||||
|
||||
## Current UX
|
||||
|
||||
|
||||
@@ -661,6 +661,20 @@ is unavailable or the SQLite playlist migration has not completed.
|
||||
4. `DB_GET_RECENT_PLAYBACK_POSITIONS`
|
||||
5. `DB_GET_ALL_PLAYBACK_POSITIONS`
|
||||
6. `DB_CLEAR_PLAYBACK_POSITION`
|
||||
7. `DB_REPLACE_ALL_PLAYBACK_POSITIONS`
|
||||
|
||||
Backup restore uses `DB_REPLACE_ALL_PLAYBACK_POSITIONS` to replace only the
|
||||
specified playlist's positions in one synchronous transaction. Every insert
|
||||
and the delete use `.run()` so an insertion failure rolls the whole replacement
|
||||
back. An empty replacement deliberately clears that playlist. Renderer backup
|
||||
callers propagate read/write errors and retain pending restore state for retry;
|
||||
normal playback persistence keeps its best-effort behavior. The renderer checks
|
||||
atomic replacement support at the start of the immediate or deferred restore
|
||||
pass, before updating existing categories, collections, positions, or pins.
|
||||
Missing support fails restore with the pending snapshot retained; it does not
|
||||
switch ordinary Xtream storage or backup export away from SQLite. Deferred
|
||||
catalog import can still seed newly imported categories from pending visibility
|
||||
state before that restore pass runs.
|
||||
|
||||
## SQLite Concurrency Rules
|
||||
|
||||
|
||||
Reference in new issue
Block a user