fix(plex): resolve deep-review findings on the Discover/Collections batch
Round of USER-triggered deep-review fixes (11 CONFIRMED + 1 PLAUSIBLE, all verified) on the 22-commit branch: - collections: split the mutate-then-resync error surface. A resync READ that fails after a successful Plex mutation no longer reports a false 422 "Plex write failed" + rollback; it returns 502 "sync pending" (the next full sync reconciles the mirror). New _write_or_pending_error helper; delete is exempt (no Plex read). Covered by new happy-path / 422-rollback / 502 tests. - New-collection dup-name guard is now advisory only: existingNames is the search-scoped, all-library union, but the backend enforces uniqueness PER library — so it no longer blocks a valid same-name collection in another library, and the backend 409 is localized to nameTaken. - member-remove now confirms before the write-through (matches delete-collection). - add-title / new-collection library search render an error state on request failure instead of a misleading "No matches". - history: seed navCounter past the current entry's persisted sfScroll on load so post-reload ids can't collide with back-stack entries and mis-restore scroll. - PageScroller: LRU-cap the navId-keyed savedScroll map (was unbounded). - share isIntrinsicReadOnly between the browse tile and the info page so the lock/edit affordance can't drift.
This commit is contained in:
@@ -208,6 +208,23 @@ def _reject_duplicate_name(
|
||||
raise HTTPException(status_code=409, detail="A collection with this name already exists")
|
||||
|
||||
|
||||
def _write_or_pending_error(wrote: bool, e: Exception) -> HTTPException:
|
||||
"""Map a PlexError raised inside a mutate-then-resync block to the honest status. If the mutating
|
||||
call itself failed (``wrote`` False) the write never landed → 422 "Plex write failed". If the
|
||||
write succeeded but the follow-up resync READ (Plex metadata/children) failed, the change IS on
|
||||
the Plex server → don't claim a write failure; report 502 "sync pending" (the next full sync
|
||||
reconciles the local mirror)."""
|
||||
if wrote:
|
||||
return HTTPException(
|
||||
status_code=502,
|
||||
detail=(
|
||||
"Collection updated in Plex, but refreshing the local copy failed; "
|
||||
f"it will reconcile on the next sync ({e})"
|
||||
),
|
||||
)
|
||||
return HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||
|
||||
|
||||
class CreateCollectionIn(BaseModel):
|
||||
library: str = ""
|
||||
title: str = ""
|
||||
@@ -239,15 +256,17 @@ def create_collection(
|
||||
if lib is None:
|
||||
raise HTTPException(status_code=404, detail="Unknown library")
|
||||
_reject_duplicate_name(db, lib.id, title)
|
||||
wrote = False
|
||||
try:
|
||||
with PlexClient(db) as plex:
|
||||
new = plex.create_collection(lib.plex_key, title, seed)
|
||||
wrote = True
|
||||
col = plex_sync.resync_collection(db, plex, lib, str(new.get("ratingKey")))
|
||||
col.editable = True
|
||||
db.commit()
|
||||
except (PlexError, PlexNotConfigured) as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||
raise _write_or_pending_error(wrote, e)
|
||||
return _collection_card(col)
|
||||
|
||||
|
||||
@@ -260,14 +279,16 @@ def collection_add_item(
|
||||
) -> dict:
|
||||
col = _editable_collection_or_403(db, rating_key)
|
||||
lib = db.get(PlexLibrary, col.library_id)
|
||||
wrote = False
|
||||
try:
|
||||
with PlexClient(db) as plex:
|
||||
plex.add_collection_item(rating_key, str(item_rating_key))
|
||||
wrote = True
|
||||
col = plex_sync.resync_collection(db, plex, lib, rating_key)
|
||||
db.commit()
|
||||
except (PlexError, PlexNotConfigured) as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||
raise _write_or_pending_error(wrote, e)
|
||||
return _collection_card(col)
|
||||
|
||||
|
||||
@@ -280,14 +301,16 @@ def collection_remove_item(
|
||||
) -> dict:
|
||||
col = _editable_collection_or_403(db, rating_key)
|
||||
lib = db.get(PlexLibrary, col.library_id)
|
||||
wrote = False
|
||||
try:
|
||||
with PlexClient(db) as plex:
|
||||
plex.remove_collection_item(rating_key, str(item_rating_key))
|
||||
wrote = True
|
||||
col = plex_sync.resync_collection(db, plex, lib, rating_key)
|
||||
db.commit()
|
||||
except (PlexError, PlexNotConfigured) as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||
raise _write_or_pending_error(wrote, e)
|
||||
return _collection_card(col)
|
||||
|
||||
|
||||
@@ -308,14 +331,16 @@ def collection_rename(
|
||||
raise HTTPException(status_code=400, detail="title is required")
|
||||
_reject_duplicate_name(db, col.library_id, title, exclude_rk=rating_key)
|
||||
lib = db.get(PlexLibrary, col.library_id)
|
||||
wrote = False
|
||||
try:
|
||||
with PlexClient(db) as plex:
|
||||
plex.rename_collection(lib.plex_key, rating_key, title)
|
||||
wrote = True
|
||||
col = plex_sync.resync_collection(db, plex, lib, rating_key)
|
||||
db.commit()
|
||||
except (PlexError, PlexNotConfigured) as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||
raise _write_or_pending_error(wrote, e)
|
||||
return _collection_card(col)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user