diff --git a/web_server.py b/web_server.py index 89241dbd..e62f7ef6 100644 --- a/web_server.py +++ b/web_server.py @@ -5933,6 +5933,35 @@ def delete_tidal_playlist(playlist_id): print(f"โŒ Error deleting Tidal playlist: {e}") return jsonify({"error": str(e)}), 500 +@app.route('/api/tidal/update_phase/', methods=['POST']) +def update_tidal_playlist_phase(playlist_id): + """Update Tidal playlist phase (used when modal closes to reset from download_complete to discovered)""" + try: + if playlist_id not in tidal_discovery_states: + return jsonify({"error": "Tidal playlist not found"}), 404 + + data = request.get_json() + if not data or 'phase' not in data: + return jsonify({"error": "Phase not provided"}), 400 + + new_phase = data['phase'] + valid_phases = ['fresh', 'discovering', 'discovered', 'syncing', 'sync_complete', 'downloading', 'download_complete'] + + if new_phase not in valid_phases: + return jsonify({"error": f"Invalid phase. Must be one of: {', '.join(valid_phases)}"}), 400 + + state = tidal_discovery_states[playlist_id] + old_phase = state.get('phase', 'unknown') + state['phase'] = new_phase + state['last_accessed'] = time.time() + + print(f"๐Ÿ”„ Updated Tidal playlist {playlist_id} phase: {old_phase} โ†’ {new_phase}") + return jsonify({"success": True, "message": f"Phase updated to {new_phase}", "old_phase": old_phase, "new_phase": new_phase}) + + except Exception as e: + print(f"โŒ Error updating Tidal playlist phase: {e}") + return jsonify({"error": str(e)}), 500 + def _run_tidal_discovery_worker(playlist_id): """Background worker for Tidal Spotify discovery process (like sync.py)""" @@ -6711,6 +6740,35 @@ def delete_youtube_playlist(url_hash): print(f"โŒ Error deleting YouTube playlist: {e}") return jsonify({"error": str(e)}), 500 +@app.route('/api/youtube/update_phase/', methods=['POST']) +def update_youtube_playlist_phase(url_hash): + """Update YouTube playlist phase (used when modal closes to reset from download_complete to discovered)""" + try: + if url_hash not in youtube_playlist_states: + return jsonify({"error": "YouTube playlist not found"}), 404 + + data = request.get_json() + if not data or 'phase' not in data: + return jsonify({"error": "Phase not provided"}), 400 + + new_phase = data['phase'] + valid_phases = ['fresh', 'parsed', 'discovering', 'discovered', 'syncing', 'sync_complete', 'downloading', 'download_complete'] + + if new_phase not in valid_phases: + return jsonify({"error": f"Invalid phase. Must be one of: {', '.join(valid_phases)}"}), 400 + + state = youtube_playlist_states[url_hash] + old_phase = state.get('phase', 'unknown') + state['phase'] = new_phase + state['last_accessed'] = time.time() + + print(f"๐Ÿ”„ Updated YouTube playlist {url_hash} phase: {old_phase} โ†’ {new_phase}") + return jsonify({"success": True, "message": f"Phase updated to {new_phase}", "old_phase": old_phase, "new_phase": new_phase}) + + except Exception as e: + print(f"โŒ Error updating YouTube playlist phase: {e}") + return jsonify({"error": str(e)}), 500 + def convert_youtube_results_to_spotify_tracks(discovery_results): """Convert YouTube discovery results to Spotify tracks format for sync""" spotify_tracks = [] diff --git a/webui/static/script.js b/webui/static/script.js index 13e758bb..9709b456 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -2969,7 +2969,7 @@ async function openDownloadMissingModalForYouTube(virtualPlaylistId, playlistNam modal.style.display = 'flex'; } -function closeDownloadMissingModal(playlistId) { +async function closeDownloadMissingModal(playlistId) { const process = activeDownloadProcesses[playlistId]; if (!process) { // If somehow called without a process, try to find and remove the element @@ -2998,14 +2998,41 @@ function closeDownloadMissingModal(playlistId) { if (playlistId.startsWith('youtube_')) { const urlHash = playlistId.replace('youtube_', ''); updateYouTubeCardPhase(urlHash, 'discovered'); + + // Update backend state to prevent rehydration issues on page refresh (similar to Tidal fix) + try { + const response = await fetch(`/api/youtube/update_phase/${urlHash}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + phase: 'discovered' + }) + }); + + if (response.ok) { + console.log(`โœ… [Modal Close] Updated backend phase for YouTube playlist ${urlHash} to 'discovered'`); + } else { + console.warn(`โš ๏ธ [Modal Close] Failed to update backend phase for YouTube playlist ${urlHash}`); + } + } catch (error) { + console.error(`โŒ [Modal Close] Error updating backend phase for YouTube playlist ${urlHash}:`, error); + } } // Enhanced Tidal playlist state management (based on GUI sync.py patterns) if (playlistId.startsWith('tidal_')) { const tidalPlaylistId = playlistId.replace('tidal_', ''); + console.log(`๐Ÿงน [Modal Close] Processing Tidal playlist close: playlistId="${playlistId}", tidalPlaylistId="${tidalPlaylistId}"`); + console.log(`๐Ÿงน [Modal Close] Current Tidal state:`, tidalPlaylistStates[tidalPlaylistId]); + // Clear download-specific state but preserve discovery results (like GUI closeEvent) if (tidalPlaylistStates[tidalPlaylistId]) { + const currentPhase = tidalPlaylistStates[tidalPlaylistId].phase; + console.log(`๐Ÿงน [Modal Close] Current phase before reset: ${currentPhase}`); + // Preserve discovery data for future use (like GUI modal behavior) const preservedData = { playlist: tidalPlaylistStates[tidalPlaylistId].playlist, @@ -3023,15 +3050,36 @@ function closeDownloadMissingModal(playlistId) { Object.assign(tidalPlaylistStates[tidalPlaylistId], preservedData); tidalPlaylistStates[tidalPlaylistId].phase = 'discovered'; - // ALTERNATIVE: Reset to fresh state for new discovery (uncomment if user prefers this) - // tidalPlaylistStates[tidalPlaylistId].phase = 'fresh'; - console.log(`๐Ÿงน [Modal Close] Reset Tidal playlist ${tidalPlaylistId} - cleared download state, preserved discovery data`); + console.log(`๐Ÿงน [Modal Close] New phase after reset: ${tidalPlaylistStates[tidalPlaylistId].phase}`); + } else { + console.error(`โŒ [Modal Close] No Tidal state found for playlistId: ${tidalPlaylistId}`); } updateTidalCardPhase(tidalPlaylistId, 'discovered'); console.log(`๐Ÿ”„ [Modal Close] Reset Tidal playlist ${tidalPlaylistId} to discovered phase`); console.log(`๐Ÿ“ [Modal Close] Expected button text for discovered phase: "${getActionButtonText('discovered')}"`); + + // Update backend state to prevent rehydration issues on page refresh + try { + const response = await fetch(`/api/tidal/update_phase/${tidalPlaylistId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + phase: 'discovered' + }) + }); + + if (response.ok) { + console.log(`โœ… [Modal Close] Updated backend phase for Tidal playlist ${tidalPlaylistId} to 'discovered'`); + } else { + console.warn(`โš ๏ธ [Modal Close] Failed to update backend phase for Tidal playlist ${tidalPlaylistId}`); + } + } catch (error) { + console.error(`โŒ [Modal Close] Error updating backend phase for Tidal playlist ${tidalPlaylistId}:`, error); + } } // Clear wishlist modal state when modal is fully closed