From 47889387ad0876c87379ca8e295bab04a58637c1 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Fri, 12 Jun 2026 17:27:35 -0700 Subject: [PATCH] Playlists: resolve synthetic mirrored batch refs (youtube_mirrored_/auto_mirror_) to PK --- core/playlists/source_refs.py | 25 +++++++++++++++++++++++ database/music_database.py | 8 ++++++-- tests/test_resolve_mirrored_playlist.py | 27 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/core/playlists/source_refs.py b/core/playlists/source_refs.py index 22909895..4cf03534 100644 --- a/core/playlists/source_refs.py +++ b/core/playlists/source_refs.py @@ -19,6 +19,31 @@ from urllib.parse import parse_qs, urlparse _SPOTIFY_ID_RE = re.compile(r"^[A-Za-z0-9]{16,32}$") +# Synthetic batch playlist_id prefixes that wrap a mirrored_playlists PK. +# Download/discovery flows build a batch playlist_id as f"{prefix}{pk}" — e.g. +# auto_mirror_ (core/automation/handlers/sync_playlist.py), youtube_mirrored_ +# (YouTube discovery), and mirrored_ (web_server url hashes). The trailing +# digits are the mirrored_playlists primary key, NOT an upstream source id, so a +# (source, source_playlist_id) lookup will never match them. +_MIRRORED_PK_PREFIXES = ("youtube_mirrored_", "auto_mirror_", "mirrored_") + + +def extract_mirrored_pk(playlist_ref: object) -> Optional[int]: + """Return the mirrored_playlists PK from a synthetic batch ref, else None. + + Handles the synthetic forms above plus a bare numeric ref. Anything else + (a real upstream source id) returns None so the caller falls back to a + (source, source_playlist_id) lookup. + """ + ref = str(playlist_ref or "").strip() + if not ref: + return None + for prefix in _MIRRORED_PK_PREFIXES: + if ref.startswith(prefix): + tail = ref[len(prefix):] + return int(tail) if tail.isdigit() else None + return int(ref) if ref.isdigit() else None + @dataclass(frozen=True) class MirroredSourceRef: diff --git a/database/music_database.py b/database/music_database.py index a3b2fb03..ef0705e1 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -13704,8 +13704,12 @@ class MusicDatabase: row = self.get_mirrored_playlist_by_source(default_source, ref, profile_id) if row: return row - if ref.isdigit(): - return self.get_mirrored_playlist(int(ref)) + # Fallback: bare numeric ref or a synthetic batch id (auto_mirror_, + # youtube_mirrored_, mirrored_) whose trailing digits are the PK. + from core.playlists.source_refs import extract_mirrored_pk + pk = extract_mirrored_pk(ref) + if pk is not None: + return self.get_mirrored_playlist(pk) return None def set_mirrored_playlist_organize_by_playlist( diff --git a/tests/test_resolve_mirrored_playlist.py b/tests/test_resolve_mirrored_playlist.py index 75d3feea..fa45b007 100644 --- a/tests/test_resolve_mirrored_playlist.py +++ b/tests/test_resolve_mirrored_playlist.py @@ -55,3 +55,30 @@ def test_empty_refs_return_none(tmp_path): assert db.resolve_mirrored_playlist(None) is None assert db.resolve_mirrored_playlist('') is None assert db.resolve_mirrored_playlist(' ') is None + + +def test_synthetic_mirrored_batch_ref_resolves_by_pk(tmp_path): + """A discovery/mirror batch carries a synthetic playlist_id like + youtube_mirrored_ with batch source 'mirrored'. (source, source_playlist_id) + can't match it — it must resolve via the embedded PK. Regression for the + organize-by-playlist 'all found but no folder built' report.""" + db = MusicDatabase(str(tmp_path / "m.db")) + pk = db.mirror_playlist(source='youtube', source_playlist_id='abc123XYZ', + name='My Mirror', tracks=[], profile_id=1) + assert pk + for ref in (f'youtube_mirrored_{pk}', f'auto_mirror_{pk}', f'mirrored_{pk}'): + row = db.resolve_mirrored_playlist(ref, profile_id=1, default_source='mirrored') + assert row is not None and row['id'] == pk, ref + + +def test_extract_mirrored_pk_pure(): + from core.playlists.source_refs import extract_mirrored_pk + assert extract_mirrored_pk('youtube_mirrored_63') == 63 + assert extract_mirrored_pk('auto_mirror_7') == 7 + assert extract_mirrored_pk('mirrored_12') == 12 + assert extract_mirrored_pk('42') == 42 # bare PK + assert extract_mirrored_pk('908622995') == 908622995 # numeric upstream id (PK fallback only) + assert extract_mirrored_pk('37i9dQZF1DXcBWIGoYBM5M') is None # real spotify id + assert extract_mirrored_pk('youtube_mirrored_') is None # no digits + assert extract_mirrored_pk('') is None + assert extract_mirrored_pk(None) is None