Pre-create rolling LB series mirrors when LB cache updates

Make the rolling Weekly Jams / Weekly Exploration / Top Discoveries
/ Top Missed Recordings mirror entries appear in Auto-Sync's
sidebar the moment ListenBrainz first publishes any member of the
series — without requiring the user to manually discover a per-
period card first.

Previously the rolling mirror was only created on discovery
completion, so users with cached LB playlists but no discovery
history saw an empty ListenBrainz group in the Auto-Sync manager
and couldn't schedule the rolling entries.

- ``_ensure_rolling_series_mirror(cursor, title)`` new helper on
  ``ListenBrainzManager``: detect_series + ``INSERT OR IGNORE``
  the matching ``mirrored_playlists`` row with the synthetic
  source_playlist_id, the canonical name, and zero tracks.
  Idempotent — no-op when the rolling mirror already exists or
  when the title doesn't belong to a series.
- ``_update_playlist`` now calls the helper after the cache row
  is inserted/updated, so every LB refresh that lands a per-
  period series member guarantees a rolling mirror exists.

First Auto-Sync schedule fired against an empty rolling mirror
populates tracks through the existing LB adapter +
``_maybe_discover`` hook — synthetic id resolves to the latest
cache row, tracks come back with needs_discovery=True, matching
engine runs, mirror gets tracks. No extra wiring needed.

236 tests still green.
This commit is contained in:
Broque Thomas 2026-05-26 16:51:35 -07:00
parent d8cc2f5f01
commit 1eadd9a65e

View file

@ -209,11 +209,61 @@ class ListenBrainzManager:
if tracks:
self._cache_tracks(playlist_id, playlist_mbid, tracks, cursor)
# Ensure a rolling-series mirror row exists for known LB series
# (Weekly Jams / Weekly Exploration / Top Discoveries / Top
# Missed Recordings). The Auto-Sync sidebar then surfaces the
# rolling entry as schedulable even before the user has
# explicitly discovered any per-period card — first scheduled
# refresh fills tracks via the LB adapter's synthetic-id
# resolution.
self._ensure_rolling_series_mirror(cursor, title)
conn.commit()
conn.close()
return result_type
def _ensure_rolling_series_mirror(self, cursor, playlist_title: str):
"""Upsert a placeholder ``mirrored_playlists`` row for the
rolling series this title belongs to.
Idempotent uses ``INSERT OR IGNORE``, so existing rolling
mirrors (which may already have discovered tracks) are not
touched. No-op for non-series titles (Last.fm radios,
user-created playlists, collaborative playlists)."""
try:
# Defer import to avoid a top-level dependency loop — the
# series detector lives in core.playlists which itself
# transitively imports manager-flavor helpers.
from core.playlists.lb_series import detect_series
match = detect_series(playlist_title or "")
if match is None:
return
cursor.execute(
"""
INSERT OR IGNORE INTO mirrored_playlists
(source, source_playlist_id, name, description, owner, image_url, track_count, profile_id, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
""",
(
match.source_for_mirror,
match.series_id,
match.canonical_name,
"Rolling ListenBrainz series — refresh resolves to the latest period automatically.",
"ListenBrainz",
"",
0,
self.profile_id,
),
)
if cursor.rowcount:
logger.info(
f"Pre-created rolling mirror placeholder '{match.canonical_name}' "
f"(series id: {match.series_id})"
)
except Exception as exc:
logger.debug(f"Rolling-series mirror ensure skipped: {exc}")
def _cache_tracks(self, playlist_id: int, playlist_mbid: str, tracks: List[Dict], cursor):
"""
Cache tracks for a playlist, including fetching cover art URLs in parallel