From e8ee8576a0d540807c875d1a4ef77238d2cf3315 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Tue, 26 May 2026 15:40:57 -0700 Subject: [PATCH] Fix Last.fm radios mirrored under wrong source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: ", 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. --- core/listenbrainz_manager.py | 32 ++++++++++++++++++++++++++++++++ webui/static/sync-services.js | 17 +++++++++++++---- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/core/listenbrainz_manager.py b/core/listenbrainz_manager.py index c29e4cc6..09855211 100644 --- a/core/listenbrainz_manager.py +++ b/core/listenbrainz_manager.py @@ -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: " 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 = { diff --git a/webui/static/sync-services.js b/webui/static/sync-services.js index 67f6ded7..a133ef04 100644 --- a/webui/static/sync-services.js +++ b/webui/static/sync-services.js @@ -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: " 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); }