feat(plex): prune orphaned people after each sync (S4)
The plex_people headshot cache can outlive its subjects — when a title leaves the Plex library, cast/crew who appear in nothing else are orphaned. prune_orphan_people deletes rows whose name no longer appears in ANY movie/show cast_names/directors, run at the end of every plex_sync (best-effort; a jsonb_typeof='array' guard skips scalar/null JSONB values). The filterable cast_names self-clean with their row; this cleans the only per-person cache. Verified: an injected orphan is dropped, real people kept. (Plex cast-explore S4)
This commit is contained in:
@@ -12,6 +12,7 @@ Cast + intro/credit markers are fetched lazily on the item-detail/player path (P
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import sysconfig
|
||||
@@ -135,10 +136,36 @@ def sync(db: Session) -> dict:
|
||||
db.rollback()
|
||||
log.warning("Plex sync failed: %s", e)
|
||||
return {"error": str(e)}
|
||||
# Prune the headshot cache: a reconcile may have removed titles, orphaning people who no longer
|
||||
# appear in ANY item's cast/directors — drop those rows, mirroring how Plex drops orphaned metadata.
|
||||
try:
|
||||
stats["people_pruned"] = prune_orphan_people(db)
|
||||
except Exception as e: # best-effort housekeeping — never fail the sync over it
|
||||
log.warning("Plex people prune failed: %s", e)
|
||||
log.info("Plex sync done: %s", stats)
|
||||
return stats
|
||||
|
||||
|
||||
# The person names still referenced by ANY movie/show (cast OR directors). plex_people rows outside
|
||||
# this set are orphans left by a library removal.
|
||||
_REFERENCED_PEOPLE_SQL = """
|
||||
SELECT jsonb_array_elements_text(cast_names) FROM plex_items WHERE jsonb_typeof(cast_names) = 'array'
|
||||
UNION SELECT jsonb_array_elements_text(directors) FROM plex_items WHERE jsonb_typeof(directors) = 'array'
|
||||
UNION SELECT jsonb_array_elements_text(cast_names) FROM plex_shows WHERE jsonb_typeof(cast_names) = 'array'
|
||||
UNION SELECT jsonb_array_elements_text(directors) FROM plex_shows WHERE jsonb_typeof(directors) = 'array'
|
||||
"""
|
||||
|
||||
|
||||
def prune_orphan_people(db: Session) -> int:
|
||||
"""Delete plex_people (headshot cache) rows whose name no longer appears in any movie/show cast or
|
||||
directors — orphaned by a library removal. Returns the number pruned. Commits its own work."""
|
||||
res = db.execute(
|
||||
text(f"DELETE FROM plex_people WHERE name NOT IN ({_REFERENCED_PEOPLE_SQL})")
|
||||
)
|
||||
db.commit()
|
||||
return res.rowcount or 0
|
||||
|
||||
|
||||
def _upsert_library(db: Session, key: str, title: str, kind: str) -> PlexLibrary:
|
||||
lib = db.query(PlexLibrary).filter_by(plex_key=key).first()
|
||||
if lib is None:
|
||||
|
||||
Reference in New Issue
Block a user