diff --git a/backend/app/plex/sync.py b/backend/app/plex/sync.py index cd4201f..d565584 100644 --- a/backend/app/plex/sync.py +++ b/backend/app/plex/sync.py @@ -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: