mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 14:16:16 -08:00
fix(server-storage): conditional playlist metadata writes, retried on a fresh read
A favorite or rename (keepPayload PUT) is sent with If-Match on the version it was read at. The server answers 412 when another device wrote since; the store throws PlaylistWriteConflict and PlaylistsService's per-playlist write queue runs the mutation again, which reads the playlist afresh, so two devices' metadata edits both survive. A retry on the same unread copy writes unconditionally, as before; refresh and import writes stay last write wins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d6f28f8c8
commit
7afb9758f4
9 files changed
+159
-12
No files matched your search
@@ -85,7 +85,8 @@ Server storage adds versioned routes under `/v1`, which the PWA uses in
|
||||
`PUT ...?keepPayload=1` keeps the stored `playlist`, `items` and
|
||||
`header` fields (`409 payload_missing` without a stored row); every `PUT`
|
||||
answers with the new `ETag` and `X-Replaced-ETag`, the version it
|
||||
replaced
|
||||
replaced, and one with `If-Match` answers `412 conflict` when another
|
||||
write replaced that version
|
||||
- `GET /v1/user-state`, `PUT|DELETE /v1/user-state/:namespace/:playlistId`:
|
||||
Xtream favorites, recently viewed, collection snapshots and playback
|
||||
positions, one JSON value per namespace and playlist; `GET` adds a
|
||||
@@ -193,12 +194,16 @@ settings and guards read them in every build.
|
||||
|
||||
- **Playlists.** `ServerStorageSession.start()` (an app initializer) plugs
|
||||
`ServerPlaylistDocumentStore` into `PlaylistStorageBackend` (kinds
|
||||
`electron`, `server`, `indexeddb`), so `PlaylistsService` is unchanged. The
|
||||
store caches full documents by ETag and uploads a document whose payload it
|
||||
served unchanged with `keepPayload=1`, so a favorite or a rename does not
|
||||
resend a large M3U. It keeps its copy after such a write only when
|
||||
`X-Replaced-ETag` names the version its payload came from; otherwise
|
||||
another device wrote in between and the next read fetches the document.
|
||||
`electron`, `server`, `indexeddb`). The store caches full documents by
|
||||
ETag and uploads a document whose payload it served unchanged with
|
||||
`keepPayload=1`, so a favorite or a rename does not resend a large M3U.
|
||||
Such a write carries `If-Match` with the version it was read at: when
|
||||
another device wrote in between, the server answers `412`, the store
|
||||
throws `PlaylistWriteConflict`, and `PlaylistsService`'s per-playlist
|
||||
write queue runs the mutation again on a fresh read (a retry on the same,
|
||||
unread copy writes unconditionally, as before). After a write the store
|
||||
keeps its copy only when `X-Replaced-ETag` names the version its payload
|
||||
came from.
|
||||
- **Xtream user data.** `USER_DATA_STORAGE` becomes `ServerUserDataStorage`:
|
||||
the four synced namespaces are cached under the `iptvnator-server:` key
|
||||
prefix (the legacy unprefixed keys are never written in server mode) and
|
||||
|
||||
@@ -209,8 +209,11 @@ https://claude.ai/artifact/3WheJAgeNL5ag6kmtPrJcD
|
||||
|
||||
## 11. Risks
|
||||
|
||||
- **Concurrent edits on two devices**: last write wins per document.
|
||||
Every playlist mutation re-reads first, so the window is one round trip.
|
||||
- **Concurrent edits on two devices**: a metadata edit (`keepPayload`) is
|
||||
sent with `If-Match` on the version it was read at; on a `412` the
|
||||
per-playlist write queue runs the mutation again on a fresh read. Other
|
||||
document writes (refresh, import) stay last write wins; every playlist
|
||||
mutation re-reads first, so the window is one round trip.
|
||||
Xtream map entries are written on the tag they were read at and replayed
|
||||
on the server's value after a `412`, so both devices' edits are kept.
|
||||
- **Large payloads**: M3U global favorites and backup export read all full
|
||||
|
||||
Reference in new issue
Block a user