From d7e1cdeb8390fcfbcd37fc49a44d41e7f5477f49 Mon Sep 17 00:00:00 2001 From: JohnBaumb <80135794+JohnBaumb@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:26:17 -0700 Subject: [PATCH] Fix seasonal playlist track ID for non-spotify/itunes sources, fix polling interval leak --- core/seasonal_discovery.py | 6 ++++-- web_server.py | 11 ++++++++--- webui/static/discover.js | 21 +++++++++++++++++++++ webui/static/init.js | 7 +++++++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/core/seasonal_discovery.py b/core/seasonal_discovery.py index 0e7dfadc..16ede399 100644 --- a/core/seasonal_discovery.py +++ b/core/seasonal_discovery.py @@ -371,8 +371,10 @@ class SeasonalDiscoveryService: config = SEASONAL_CONFIG[season_key] keywords = config['keywords'] - # Use the right track ID column based on source - track_id_col = 'spotify_track_id' if source == 'spotify' else 'itunes_track_id' + # itunes stores IDs in itunes_track_id; all other sources + # (spotify, deezer, discogs, hydrabase, etc.) use spotify_track_id + # as the generic ID column. + track_id_col = 'itunes_track_id' if source == 'itunes' else 'spotify_track_id' seasonal_tracks = [] diff --git a/web_server.py b/web_server.py index b73fba5b..f396d7f0 100644 --- a/web_server.py +++ b/web_server.py @@ -44348,7 +44348,10 @@ def get_current_seasonal_playlist(): if not track_ids: return jsonify({"success": True, "tracks": []}) - track_id_col = 'spotify_track_id' if active_source == 'spotify' else 'itunes_track_id' + # itunes stores IDs in itunes_track_id; all other sources + # (spotify, deezer, discogs, hydrabase, etc.) use spotify_track_id + # as the generic ID column. + track_id_col = 'itunes_track_id' if active_source == 'itunes' else 'spotify_track_id' tracks = [] with database._get_connection() as conn: cursor = conn.cursor() @@ -44477,8 +44480,10 @@ def get_seasonal_playlist(season_key): if not track_ids: return jsonify({"success": True, "tracks": []}) - # Use source-appropriate ID column for lookups - track_id_col = 'spotify_track_id' if active_source == 'spotify' else 'itunes_track_id' + # itunes stores IDs in itunes_track_id; all other sources + # (spotify, deezer, discogs, hydrabase, etc.) use spotify_track_id + # as the generic ID column. + track_id_col = 'itunes_track_id' if active_source == 'itunes' else 'spotify_track_id' # Fetch track details from seasonal tracks or discovery pool (filtered by source) tracks = [] diff --git a/webui/static/discover.js b/webui/static/discover.js index 74c7b675..85fc68b9 100644 --- a/webui/static/discover.js +++ b/webui/static/discover.js @@ -9332,7 +9332,23 @@ function pollDiscoverSyncFromTab(playlistType, virtualPlaylistId, playlistName) } function pollDiscoverBatchFromTab(playlistType, batchId, playlistName) { + // Clear any existing poller for this playlist type + if (discoverSyncPollers[playlistType]) { + clearInterval(discoverSyncPollers[playlistType]); + delete discoverSyncPollers[playlistType]; + } + + let ticks = 0; + const maxTicks = 600; // 30 min at 3s intervals + const pollInterval = setInterval(async () => { + ticks++; + // Stall guard — stop polling after maxTicks or if the card is no longer in DOM + if (ticks > maxTicks || !document.getElementById(`discover-sync-card-${playlistType}`)) { + clearInterval(pollInterval); + delete discoverSyncPollers[playlistType]; + return; + } try { const resp = await fetch(`/api/playlists/${batchId}/download_status`); if (!resp.ok) { clearInterval(pollInterval); return; } @@ -9341,6 +9357,7 @@ function pollDiscoverBatchFromTab(playlistType, batchId, playlistName) { if (phase === 'complete' || phase === 'error' || phase === 'cancelled') { clearInterval(pollInterval); + delete discoverSyncPollers[playlistType]; const btn = document.getElementById(`discover-sync-btn-${playlistType}`); if (btn) { btn.disabled = false; btn.textContent = '\u27f3 Sync Now'; } @@ -9387,8 +9404,12 @@ function pollDiscoverBatchFromTab(playlistType, batchId, playlistName) { } } catch (error) { clearInterval(pollInterval); + delete discoverSyncPollers[playlistType]; } }, 3000); + + // Register so page-leave cleanup can clear it + discoverSyncPollers[playlistType] = pollInterval; } /** diff --git a/webui/static/init.js b/webui/static/init.js index 0e9add42..d46ac6d8 100644 --- a/webui/static/init.js +++ b/webui/static/init.js @@ -2243,6 +2243,13 @@ async function loadPageData(pageId) { if (typeof _stopNebulaLivePolling === 'function') _stopNebulaLivePolling(); if (pageId !== 'sync') { cleanupBeatportContent(); + // Clear any discover sync tab pollers when leaving the sync page + if (typeof discoverSyncPollers === 'object') { + for (const key of Object.keys(discoverSyncPollers)) { + clearInterval(discoverSyncPollers[key]); + delete discoverSyncPollers[key]; + } + } } switch (pageId) { case 'dashboard':