From 4178c1eb56621b0cddbbc86eaa90f78fd2d195b4 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 9 Apr 2026 09:38:13 -0700 Subject: [PATCH] Fix Deezer ARL sync/download rehydration and add album data caching Sync rehydration: after loading Deezer ARL playlists, checks each for active syncs via /api/sync/status and re-attaches polling with live card updates. Download rehydration: rehydrateModal now handles deezer_arl_ playlist IDs, and openDownloadMissingModal routes cache misses to the correct ARL endpoint. Fix All now prompts for dead file action. Album data caching: get_playlist_tracks now checks the metadata cache before fetching album release dates from the Deezer API. Cache hits are instant, misses are fetched and stored for future use across all playlists. Import fixed from core.metadata_cache instead of web_server to avoid circular dependency. --- core/deezer_download_client.py | 23 +++++++++++++- webui/static/script.js | 57 +++++++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/core/deezer_download_client.py b/core/deezer_download_client.py index f20a963d..82d7dce4 100644 --- a/core/deezer_download_client.py +++ b/core/deezer_download_client.py @@ -315,20 +315,41 @@ class DeezerDownloadClient: break raw_tracks.extend(page_tracks) - # Batch-fetch release dates for unique albums + # Batch-fetch release dates for unique albums (cache-first) album_ids = set() for t in raw_tracks: aid = t.get('album', {}).get('id') if aid: album_ids.add(str(aid)) album_release_dates = {} + try: + from core.metadata_cache import get_metadata_cache + cache = get_metadata_cache() + except Exception: + cache = None for aid in album_ids: + # Check metadata cache first + if cache: + try: + cached = cache.get_entity('deezer', 'album', aid) + if cached and cached.get('release_date'): + album_release_dates[aid] = cached['release_date'] + continue + except Exception: + pass + # Cache miss — fetch from API try: time.sleep(0.3) # Respect rate limits a_resp = self._session.get(f'https://api.deezer.com/album/{aid}', timeout=10) if a_resp.ok: a_data = a_resp.json() album_release_dates[aid] = a_data.get('release_date', '') + # Store in metadata cache for future use + if cache: + try: + cache.store_entity('deezer', 'album', aid, a_data) + except Exception: + pass except Exception: pass diff --git a/webui/static/script.js b/webui/static/script.js index b8a9bfb6..3eb63314 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -10195,7 +10195,29 @@ async function rehydrateModal(processInfo, userRequested = false) { return; } - // Handle regular Spotify playlist processes + // Handle Deezer ARL playlist processes — ensure playlist data is in spotifyPlaylists for modal reuse + if (playlist_id.startsWith('deezer_arl_') && !spotifyPlaylists.find(p => p.id === playlist_id)) { + const rawId = playlist_id.replace('deezer_arl_', ''); + const deezerPlaylist = deezerArlPlaylists.find(p => String(p.id) === rawId); + if (deezerPlaylist) { + spotifyPlaylists.push({ + id: playlist_id, + name: deezerPlaylist.name, + track_count: deezerPlaylist.track_count || 0, + image_url: deezerPlaylist.image_url || '', + owner: deezerPlaylist.owner || '', + }); + } else { + // Playlists not loaded yet — use process info as fallback + spotifyPlaylists.push({ + id: playlist_id, + name: playlist_name || 'Deezer Playlist', + track_count: 0, + }); + } + } + + // Handle regular Spotify / Deezer ARL playlist processes let playlistData = spotifyPlaylists.find(p => p.id === playlist_id); if (!playlistData) { console.warn(`Cannot rehydrate modal: Playlist data for ${playlist_id} not loaded.`); @@ -11707,7 +11729,10 @@ async function openDownloadMissingModal(playlistId) { let tracks = playlistTrackCache[playlistId]; if (!tracks) { try { - const response = await fetch(`/api/spotify/playlist/${playlistId}`); + const fetchUrl = playlistId.startsWith('deezer_arl_') + ? `/api/deezer/arl-playlist/${playlistId.replace('deezer_arl_', '')}` + : `/api/spotify/playlist/${playlistId}`; + const response = await fetch(fetchUrl); const fullPlaylist = await response.json(); if (fullPlaylist.error) throw new Error(fullPlaylist.error); tracks = fullPlaylist.tracks; @@ -26570,6 +26595,27 @@ async function loadDeezerArlPlaylists() { renderDeezerArlPlaylists(); deezerArlPlaylistsLoaded = true; + // Check for active syncs or downloads and rehydrate UI + await checkForActiveProcesses(); + for (const p of deezerArlPlaylists) { + const arlId = `deezer_arl_${p.id}`; + try { + const syncResp = await fetch(`/api/sync/status/${arlId}`); + if (syncResp.ok) { + const syncState = await syncResp.json(); + if (syncState.status === 'syncing') { + // Re-attach sync polling and update card UI + if (!spotifyPlaylists.find(sp => sp.id === arlId)) { + spotifyPlaylists.push({ id: arlId, name: p.name, track_count: p.track_count || 0, image_url: p.image_url || '', owner: p.owner || '' }); + } + updateCardToSyncing(arlId, syncState.progress?.progress || 0, syncState.progress); + startSyncPolling(arlId); + console.log(`🔄 Rehydrated active sync for Deezer ARL playlist: ${p.name}`); + } + } + } catch (e) { /* No active sync — normal */ } + } + } catch (error) { container.innerHTML = `