perf(plex): batch bulk pushes, cache show enrichment; remove dead code + DRY (F6/F8/F10)
F6: coalesce whole-show/season Plex watch-pushes into ONE background task (push_bulk_state_to_plex) that reuses a single DB session + keep-alive Plex client, instead of scheduling one task (own session + HTTP client) per episode. F8: cache a show's live Plex enrichment (metadata + related) per rating_key for a short TTL and fetch the two calls in parallel; repeat opens skip the network. Raw payloads are cached; per-user shaping stays out. An empty related list is not cached (plex.related swallows a transient failure as [] — don't pin it for the TTL). F10: remove dead code — the pre-unified /browse route + browse(), the /people route + people() + _person_photo(), api.plexPeople, interface PlexPerson, and the orphaned plex.people i18n block. DRY: appendPlexFilters() shared by plexLibrary + plexFacets; one exported plexDetailUi.Filterable (was Fil + Filterable); PlexInfo migrated onto the shared plexDetailUi hooks/menu (DetailCustomizeMenu gained overlay + extra props; useDetailPrefs exposes savePref; PrefToggle exported). Reviewed (high) → clean. F7 (facet aggregate collapse) deferred: self-exclusion gives each sub-aggregate a distinct WHERE, and no measurement shows /facets slow.
This commit is contained in:
parent
9f8e410033
commit
b3af04a997
9 changed files with 180 additions and 450 deletions
|
|
@ -206,6 +206,51 @@ def push_state_to_plex(
|
|||
db.commit()
|
||||
|
||||
|
||||
def push_bulk_state_to_plex(user_id: int, changes: list[tuple[int, str, str]]) -> None:
|
||||
"""Coalesced Siftlode→Plex push for a whole-show / whole-season mark (see `_bulk_state`). Same
|
||||
contract as `push_state_to_plex` (own DB session, best-effort, never raises) but ONE background
|
||||
task for the entire batch: it reuses a single DB session and a single keep-alive PlexClient across
|
||||
all `changes` instead of scheduling N tasks that each rebuild a session + HTTP client. Plex has no
|
||||
batch-scrobble endpoint, so the per-item HTTP calls remain (looped over the shared connection), but
|
||||
the O(N) task/session/client setup is gone. `changes` is a list of (item_id, rating_key, action)
|
||||
with action ``watched`` (scrobble) or ``unwatched`` (unscrobble)."""
|
||||
if not changes:
|
||||
return
|
||||
with SessionLocal() as db:
|
||||
if link_for_push(db, user_id) is None:
|
||||
return
|
||||
synced_ids: list[int] = []
|
||||
try:
|
||||
with PlexClient(db) as plex:
|
||||
for item_id, rating_key, action in changes:
|
||||
try:
|
||||
if action == "watched":
|
||||
plex.scrobble(rating_key)
|
||||
elif action == "unwatched":
|
||||
plex.unscrobble(rating_key)
|
||||
else:
|
||||
continue
|
||||
except PlexError as e:
|
||||
# One bad item mustn't abort the rest of the batch; leave its row dirty
|
||||
# (synced_to_plex=False) for a later reconcile.
|
||||
log.warning(
|
||||
"Plex bulk push failed (user %s, item %s, %s): %s", user_id, item_id, action, e
|
||||
)
|
||||
continue
|
||||
synced_ids.append(item_id)
|
||||
except PlexError as e:
|
||||
# Couldn't even open the client — nothing pushed; everything stays dirty for reconcile.
|
||||
log.warning("Plex bulk push could not connect (user %s): %s", user_id, e)
|
||||
return
|
||||
# Flag every successfully-pushed row that still exists (an "unwatched" deletes the row, so
|
||||
# only the "watched" changes have a row to flag).
|
||||
if synced_ids:
|
||||
db.query(PlexState).filter(
|
||||
PlexState.user_id == user_id, PlexState.item_id.in_(synced_ids)
|
||||
).update({PlexState.synced_to_plex: True}, synchronize_session=False)
|
||||
db.commit()
|
||||
|
||||
|
||||
# --- Phase C: incremental + full Plex ↔ Siftlode reconcile (scheduler jobs) ------------------------
|
||||
|
||||
# Clock-skew / round-trip slack when comparing a Plex timestamp to a Siftlode one. A state we just
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue