Fix Last.fm radios mirrored under wrong source

Two-part fix for Last.fm Radio playlists showing up in the
ListenBrainz group of the Auto-Sync manager + Mirrored tab
instead of their own Last.fm group:

1. **Mirror-creation hook** (sync-services.js): the
   ``_mirrorListenBrainzAfterDiscovery`` helper hardcoded
   ``source='listenbrainz'`` on every auto-mirror call, even for
   Last.fm Radio playlists (which share the same MB-track shape +
   discovery worker but should land under ``source='lastfm'``).
   ``save_lastfm_radio_playlist`` always prefixes the playlist name
   with "Last.fm Radio: <seed>", so the helper now keys on that
   prefix to pick the right mirror source + owner fallback. Going
   forward, new Last.fm radios mirror correctly the moment
   discovery completes.

2. **Backfill** (listenbrainz_manager.py): legacy mirror rows
   created before the fix above are stuck under
   ``source='listenbrainz'``. Added
   ``_retag_misrouted_lastfm_radio_mirrors`` to ``_cleanup_old_playlists``
   so the next LB refresh re-tags any row whose name starts with
   "Last.fm Radio:" but is still on ``source='listenbrainz'``.
   Idempotent — UPDATE only matches misrouted rows.
This commit is contained in:
Broque Thomas 2026-05-26 15:40:57 -07:00
parent bbc950d325
commit e8ee8576a0
2 changed files with 45 additions and 4 deletions

View file

@ -326,11 +326,43 @@ class ListenBrainzManager:
covers_found = sum(1 for t in track_data_list if t.get('album_cover_url'))
logger.info(f"Fetched {covers_found}/{len(track_data_list)} cover art URLs")
def _retag_misrouted_lastfm_radio_mirrors(self, cursor):
"""Re-tag mirrored_playlists rows that should be 'lastfm' but
were inserted as 'listenbrainz'.
Backfill for the Phase 1c.1 bug where the auto-mirror helper
hardcoded ``source='listenbrainz'`` regardless of playlist
origin. Last.fm Radio playlists carry a consistent
"Last.fm Radio: <seed>" title prefix from
``save_lastfm_radio_playlist``, so any mirror row matching
that prefix should sit under the Last.fm group instead of
the ListenBrainz one. Idempotent only updates rows that
are still misrouted."""
try:
cursor.execute(
"""
UPDATE mirrored_playlists
SET source = 'lastfm'
WHERE source = 'listenbrainz'
AND name LIKE 'Last.fm Radio:%'
"""
)
if cursor.rowcount:
logger.info(
f"Re-tagged {cursor.rowcount} Last.fm Radio mirror rows "
"from source='listenbrainz' to source='lastfm'"
)
except Exception as exc:
logger.debug(f"Last.fm radio mirror retag skipped: {exc}")
def _cleanup_old_playlists(self):
"""Remove old playlists, keeping only the 25 most recent per type"""
conn = self._get_db_connection()
cursor = conn.cursor()
# One-shot backfill for legacy misrouting (see method docstring).
self._retag_misrouted_lastfm_radio_mirrors(cursor)
# For each playlist type, keep only the N most recent
# lastfm_radio keeps fewer since they're auto-regenerated weekly
playlist_type_limits = {

View file

@ -10855,18 +10855,27 @@ function _mirrorListenBrainzAfterDiscovery(playlistMbid) {
return;
}
// Last.fm Radio playlists live in the same listenbrainz_playlists
// table but are persisted by ``save_lastfm_radio_playlist`` with
// a "Last.fm Radio: <seed>" title prefix and ``playlist_type='lastfm_radio'``.
// Route them to ``source='lastfm'`` so the Auto-Sync manager
// groups them under the Last.fm Radio section + the cascade-
// delete hook targets the right mirror source.
const title = state.playlist.name || 'ListenBrainz Playlist';
const mirrorSource = title.startsWith('Last.fm Radio:') ? 'lastfm' : 'listenbrainz';
const ownerFallback = mirrorSource === 'lastfm' ? 'Last.fm' : 'ListenBrainz';
mirrorPlaylist(
'listenbrainz',
mirrorSource,
playlistMbid,
state.playlist.name || 'ListenBrainz Playlist',
title,
tracks,
{
owner: state.playlist.creator || 'ListenBrainz',
owner: state.playlist.creator || ownerFallback,
description: state.playlist.description || '',
image_url: state.playlist.image_url || '',
}
);
console.log(`🪞 [LB Mirror] Mirrored '${state.playlist.name}' with ${tracks.length} matched tracks`);
console.log(`🪞 [${mirrorSource} Mirror] Mirrored '${title}' with ${tracks.length} matched tracks`);
} catch (err) {
console.warn('LB mirror-after-discovery failed:', err);
}