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); }