diff --git a/backend/.basedpyright/baseline.json b/backend/.basedpyright/baseline.json index a669dffb..2a942acc 100644 --- a/backend/.basedpyright/baseline.json +++ b/backend/.basedpyright/baseline.json @@ -580,14 +580,6 @@ "endColumn": 89, "lineCount": 1 } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 17, - "endColumn": 18, - "lineCount": 1 - } } ], "./app/plex/tmdb.py": [ diff --git a/backend/app/sync/playlists.py b/backend/app/sync/playlists.py index 3c02ff58..bde36501 100644 --- a/backend/app/sync/playlists.py +++ b/backend/app/sync/playlists.py @@ -22,7 +22,7 @@ from app import logs, progress, quota from app.auth import has_read_scope from app.models import Channel, Playlist, PlaylistItem, User, Video from app.sync.runner import token_users -from app.sync.subscriptions import should_prune +from app.sync.subscriptions import vanished from app.sync.videos import apply_video_details, parse_dt from app.youtube.client import YouTubeClient, YouTubeError @@ -338,18 +338,10 @@ def pull_user_playlists(db: Session, user: User) -> dict: Playlist.source == "local", Playlist.yt_playlist_id.is_not(None), ) - ) - .scalars() - .all() - ) - # Drop mirrors whose YouTube playlist no longer exists — locked first, as every item write. - # Never on an empty answer that contradicts the mirrors we hold: the same 200-with-no-items - # blip `should_prune` guards the subscriptions against would drop every mirror (E32 S2). - gone = ( - [pl for ytid, pl in existing.items() if ytid not in yt_ids] - if should_prune(len(yt_ids), len(existing)) - else [] + ).scalars() ) + # Drop vanished mirrors (none on an empty answer) — locked first, as every item write. + gone = vanished(existing, yt_ids) if gone: lock_playlists(db, *(pl.id for pl in gone)) for pl in gone: diff --git a/backend/app/sync/subscriptions.py b/backend/app/sync/subscriptions.py index ae3cbe92..4621dbfa 100644 --- a/backend/app/sync/subscriptions.py +++ b/backend/app/sync/subscriptions.py @@ -1,6 +1,6 @@ """Import the authenticated user's YouTube subscriptions and channel metadata.""" -from collections.abc import Iterable +from collections.abc import Collection, Iterable from datetime import datetime, timedelta, timezone from sqlalchemy import or_, select @@ -119,6 +119,15 @@ def should_prune(fetched_count: int, existing_count: int) -> bool: return fetched_count > 0 or existing_count == 0 +def vanished[K, V](existing: dict[K, V], listed: Collection[K]) -> list[V]: + """The local mirrors whose key the remote listing no longer has — none at all when the listing + is an empty answer that contradicts them (`should_prune`). The YouTube playlist mirrors use it: + without it, one blip dropped every mirror (E32 S2).""" + if not should_prune(len(listed), len(existing)): + return [] + return [row for key, row in existing.items() if key not in listed] + + def import_subscriptions(db: Session, user: User) -> dict: """Pull all subscriptions for `user`, upsert channels + subscription links, prune channels the user has unsubscribed from on YouTube, and fetch channel details for