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.
pull/273/head
Broque Thomas 1 week ago
parent 37d325ee10
commit 4178c1eb56

@ -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

@ -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 = `<div class="playlist-placeholder">❌ Error: ${error.message}</div>`;
showToast(`Error loading Deezer playlists: ${error.message}`, 'error');
@ -62894,9 +62940,12 @@ async function fixAllMatchingFindings() {
const jobId = jobFilter ? jobFilter.value : '';
const severity = severityFilter ? severityFilter.value : '';
// If fixing orphan files, prompt for action FIRST (staging vs delete)
// If fixing orphan files or dead files, prompt for action FIRST
let fixAction = null;
if (jobId === 'orphan_file_detector' || _isMassOrphanFix(jobId, _repairFindingsTotal)) {
if (jobId === 'dead_file_cleaner') {
fixAction = await _promptDeadFileAction();
if (!fixAction) return;
} else if (jobId === 'orphan_file_detector' || _isMassOrphanFix(jobId, _repairFindingsTotal)) {
fixAction = await _promptOrphanAction();
if (!fixAction) return;
// Confirm before proceeding

Loading…
Cancel
Save