diff --git a/core/listenbrainz_manager.py b/core/listenbrainz_manager.py index 2eeec3b7..e0636f6c 100644 --- a/core/listenbrainz_manager.py +++ b/core/listenbrainz_manager.py @@ -399,6 +399,35 @@ class ListenBrainzManager: conn.close() return playlists + def get_playlist_type(self, playlist_mbid: str) -> str: + """Get the playlist_type for a cached playlist, or None if not found""" + conn = self._get_db_connection() + cursor = conn.cursor() + cursor.execute( + "SELECT playlist_type FROM listenbrainz_playlists WHERE playlist_mbid = ? AND profile_id = ?", + (playlist_mbid, self.profile_id) + ) + row = cursor.fetchone() + conn.close() + return row[0] if row else None + + def delete_cached_playlist(self, playlist_mbid: str): + """Delete a cached playlist and its tracks (CASCADE handles tracks via FK)""" + conn = self._get_db_connection() + cursor = conn.cursor() + # Delete tracks first (SQLite FK CASCADE requires PRAGMA foreign_keys=ON) + cursor.execute(""" + DELETE FROM listenbrainz_tracks WHERE playlist_id IN ( + SELECT id FROM listenbrainz_playlists WHERE playlist_mbid = ? AND profile_id = ? + ) + """, (playlist_mbid, self.profile_id)) + cursor.execute( + "DELETE FROM listenbrainz_playlists WHERE playlist_mbid = ? AND profile_id = ?", + (playlist_mbid, self.profile_id) + ) + conn.commit() + conn.close() + def get_cached_tracks(self, playlist_mbid: str) -> List[Dict]: """Get cached tracks for a playlist from database""" conn = self._get_db_connection() diff --git a/database/music_database.py b/database/music_database.py index 6a3200a7..55f16be1 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -2328,6 +2328,17 @@ class MusicDatabase: cursor.execute("DROP TABLE listenbrainz_playlists") cursor.execute("ALTER TABLE listenbrainz_playlists_new RENAME TO listenbrainz_playlists") + # Clean up playlists that lost their tracks during table recreation + # (track playlist_id foreign keys may reference stale IDs). + # This forces a fresh re-fetch from ListenBrainz on next page load. + cursor.execute(""" + DELETE FROM listenbrainz_playlists + WHERE id NOT IN (SELECT DISTINCT playlist_id FROM listenbrainz_tracks) + """) + cleaned = cursor.rowcount + if cleaned: + logger.info(f"Cleaned up {cleaned} stale playlists (will re-fetch from ListenBrainz)") + cursor.execute("CREATE INDEX IF NOT EXISTS idx_lb_playlists_profile ON listenbrainz_playlists (profile_id)") cursor.execute(""" diff --git a/web_server.py b/web_server.py index 18240bf7..b9e97e6d 100644 --- a/web_server.py +++ b/web_server.py @@ -32009,7 +32009,8 @@ def get_discover_genre_playlist(genre_name): # =============================== def _get_profile_lb_manager(): - """Create a profile-aware ListenBrainzManager for the current user""" + """Create a profile-aware ListenBrainzManager for the current user. + Always uses the actual profile_id so each profile has its own playlist cache.""" from core.listenbrainz_manager import ListenBrainzManager profile_id = get_current_profile_id() token, base_url, username, source = _get_lb_credentials_for_profile(profile_id) @@ -32089,11 +32090,24 @@ def get_listenbrainz_collaborative(): @app.route('/api/discover/listenbrainz/playlist/', methods=['GET']) def get_listenbrainz_playlist_tracks(playlist_mbid): - """Get tracks from a specific ListenBrainz playlist (from cache)""" + """Get tracks from a specific ListenBrainz playlist (from cache, with on-demand refresh)""" try: lb_manager, username, source = _get_profile_lb_manager() tracks = lb_manager.get_cached_tracks(playlist_mbid) + if not tracks: + # Cache miss or stale entry with no tracks — try fetching from LB API + if lb_manager.client.is_authenticated(): + print(f"🔄 Cache miss for playlist {playlist_mbid}, fetching from ListenBrainz...") + # Remove stale playlist row (if any) so _update_playlist doesn't + # skip due to matching track_count with 0 actual tracks + existing_type = lb_manager.get_playlist_type(playlist_mbid) or 'created_for' + lb_manager.delete_cached_playlist(playlist_mbid) + full_playlist = lb_manager.client.get_playlist_details(playlist_mbid) + if full_playlist: + lb_manager._update_playlist(full_playlist, existing_type) + tracks = lb_manager.get_cached_tracks(playlist_mbid) + if not tracks: return jsonify({ "success": False,