listenbrainz fully connected to discovery modal
This commit is contained in:
parent
ffe02d5330
commit
a61c581c25
2 changed files with 336 additions and 206 deletions
|
|
@ -16777,9 +16777,10 @@ def get_listenbrainz_playlist_state(playlist_mbid):
|
|||
'spotify_matches': state['spotify_matches'],
|
||||
'spotify_total': state['spotify_total'],
|
||||
'discovery_results': state['discovery_results'],
|
||||
'sync_playlist_id': state['sync_playlist_id'],
|
||||
'converted_spotify_playlist_id': state['converted_spotify_playlist_id'],
|
||||
'sync_progress': state['sync_progress'],
|
||||
'sync_playlist_id': state.get('sync_playlist_id'),
|
||||
'converted_spotify_playlist_id': state.get('converted_spotify_playlist_id'),
|
||||
'download_process_id': state.get('download_process_id'),
|
||||
'sync_progress': state.get('sync_progress', {}),
|
||||
'created_at': state['created_at'],
|
||||
'last_accessed': state['last_accessed']
|
||||
}
|
||||
|
|
@ -16931,6 +16932,44 @@ def get_listenbrainz_discovery_status(playlist_mbid):
|
|||
print(f"❌ Error getting ListenBrainz discovery status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/listenbrainz/update-phase/<playlist_mbid>', methods=['POST'])
|
||||
def update_listenbrainz_phase(playlist_mbid):
|
||||
"""Update ListenBrainz playlist phase (for phase transitions and persistence)"""
|
||||
try:
|
||||
if playlist_mbid not in listenbrainz_playlist_states:
|
||||
return jsonify({"error": "ListenBrainz playlist not found"}), 404
|
||||
|
||||
data = request.get_json() or {}
|
||||
new_phase = data.get('phase')
|
||||
|
||||
if not new_phase:
|
||||
return jsonify({"error": "Phase is required"}), 400
|
||||
|
||||
state = listenbrainz_playlist_states[playlist_mbid]
|
||||
state['phase'] = new_phase
|
||||
state['last_accessed'] = time.time()
|
||||
|
||||
# Update download process ID if provided (for download persistence)
|
||||
if 'download_process_id' in data:
|
||||
state['download_process_id'] = data['download_process_id']
|
||||
logger.info(f"🎵 Updated ListenBrainz download_process_id: {data['download_process_id']}")
|
||||
|
||||
# Update converted Spotify playlist ID if provided (for download persistence)
|
||||
if 'converted_spotify_playlist_id' in data:
|
||||
state['converted_spotify_playlist_id'] = data['converted_spotify_playlist_id']
|
||||
logger.info(f"🎵 Updated ListenBrainz converted_spotify_playlist_id: {data['converted_spotify_playlist_id']}")
|
||||
|
||||
logger.info(f"🎵 Updated ListenBrainz playlist {playlist_mbid} phase to: {new_phase}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"phase": new_phase
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error updating ListenBrainz playlist phase: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/listenbrainz/discovery/update_match', methods=['POST'])
|
||||
def update_listenbrainz_discovery_match():
|
||||
"""Update a ListenBrainz discovery result with manually selected Spotify track"""
|
||||
|
|
|
|||
|
|
@ -3094,7 +3094,7 @@ async function loadListenBrainzPlaylistsFromBackend() {
|
|||
for (const playlistInfo of playlists) {
|
||||
const playlistMbid = playlistInfo.playlist_mbid;
|
||||
|
||||
console.log(`🎵 Hydrating ListenBrainz playlist: ${playlistInfo.playlist.name} (Phase: ${playlistInfo.phase})`);
|
||||
console.log(`🎵 Hydrating ListenBrainz playlist: ${playlistInfo.playlist.name} (Phase: ${playlistInfo.phase}, MBID: ${playlistMbid})`);
|
||||
|
||||
// Fetch full state for non-fresh playlists to restore discovery results
|
||||
if (playlistInfo.phase !== 'fresh') {
|
||||
|
|
@ -3155,6 +3155,15 @@ async function loadListenBrainzPlaylistsFromBackend() {
|
|||
console.log(`🔄 [Backend Loading] Auto-starting polling for discovering playlist: ${playlistInfo.playlist.name}`);
|
||||
startListenBrainzDiscoveryPolling(playlistInfo.playlist_mbid);
|
||||
}
|
||||
// Show sync button for discovered playlists (hidden by default)
|
||||
else if (playlistInfo.phase === 'discovered' || playlistInfo.phase === 'syncing' || playlistInfo.phase === 'sync_complete') {
|
||||
const playlistId = `discover-lb-playlist-${playlistInfo.playlist_mbid}`;
|
||||
const syncBtn = document.getElementById(`${playlistId}-sync-btn`);
|
||||
if (syncBtn) {
|
||||
syncBtn.style.display = 'inline-block';
|
||||
console.log(`✅ Showing sync button for discovered playlist: ${playlistInfo.playlist.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listenbrainzPlaylistsLoaded = true;
|
||||
|
|
@ -4745,7 +4754,46 @@ async function closeDownloadMissingModal(playlistId) {
|
|||
console.error(`❌ [Modal Close] Error updating backend phase for Tidal playlist ${tidalPlaylistId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reset ListenBrainz playlist phase to 'discovered' when modal is closed
|
||||
if (playlistId.startsWith('listenbrainz_')) {
|
||||
const playlistMbid = playlistId.replace('listenbrainz_', '');
|
||||
|
||||
console.log(`🧹 [Modal Close] Processing ListenBrainz playlist close: playlistId="${playlistId}", mbid="${playlistMbid}"`);
|
||||
|
||||
// Clear download-specific state but preserve discovery results
|
||||
if (listenbrainzPlaylistStates[playlistMbid]) {
|
||||
const currentPhase = listenbrainzPlaylistStates[playlistMbid].phase;
|
||||
console.log(`🧹 [Modal Close] Current phase before reset: ${currentPhase}`);
|
||||
|
||||
// Reset to discovered phase (unless download actually completed successfully)
|
||||
if (currentPhase !== 'download_complete') {
|
||||
// Clear download-specific fields
|
||||
delete listenbrainzPlaylistStates[playlistMbid].download_process_id;
|
||||
delete listenbrainzPlaylistStates[playlistMbid].convertedSpotifyPlaylistId;
|
||||
|
||||
// Set back to discovered
|
||||
listenbrainzPlaylistStates[playlistMbid].phase = 'discovered';
|
||||
|
||||
// Update backend state
|
||||
try {
|
||||
await fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phase: 'discovered' })
|
||||
});
|
||||
console.log(`✅ [Modal Close] Updated backend phase for ListenBrainz playlist ${playlistMbid} to 'discovered'`);
|
||||
} catch (error) {
|
||||
console.error(`❌ [Modal Close] Error updating backend phase for ListenBrainz playlist ${playlistMbid}:`, error);
|
||||
}
|
||||
|
||||
console.log(`🔄 [Modal Close] Reset ListenBrainz playlist ${playlistMbid} to discovered phase`);
|
||||
}
|
||||
} else {
|
||||
console.error(`❌ [Modal Close] No ListenBrainz state found for mbid: ${playlistMbid}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear wishlist modal state when modal is fully closed
|
||||
if (playlistId === 'wishlist') {
|
||||
WishlistModalState.clear(); // Clear all tracking since modal is fully closed
|
||||
|
|
@ -5511,6 +5559,30 @@ async function startMissingTracksProcess(playlistId) {
|
|||
console.log(`🔄 Updated Beatport chart ${chartHash} to downloading phase`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update ListenBrainz playlist phase to 'downloading' if this is a ListenBrainz playlist
|
||||
if (playlistId.startsWith('listenbrainz_')) {
|
||||
const playlistMbid = playlistId.replace('listenbrainz_', '');
|
||||
const state = listenbrainzPlaylistStates[playlistMbid];
|
||||
|
||||
if (state) {
|
||||
// Update frontend state
|
||||
state.phase = 'downloading';
|
||||
|
||||
// Update backend state
|
||||
try {
|
||||
fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phase: 'downloading' })
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Error updating backend ListenBrainz phase to downloading:', error);
|
||||
}
|
||||
|
||||
console.log(`🔄 Updated ListenBrainz playlist ${playlistMbid} to downloading phase`);
|
||||
}
|
||||
}
|
||||
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
||||
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
||||
|
||||
|
|
@ -5605,6 +5677,33 @@ async function startMissingTracksProcess(playlistId) {
|
|||
}
|
||||
}
|
||||
|
||||
// Update ListenBrainz backend state with download_process_id and convertedSpotifyPlaylistId
|
||||
if (playlistId.startsWith('listenbrainz_')) {
|
||||
const playlistMbid = playlistId.replace('listenbrainz_', '');
|
||||
const state = listenbrainzPlaylistStates[playlistMbid];
|
||||
if (state) {
|
||||
// Store in frontend state
|
||||
state.download_process_id = data.batch_id;
|
||||
state.convertedSpotifyPlaylistId = playlistId;
|
||||
|
||||
// Update backend state
|
||||
try {
|
||||
fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
phase: 'downloading',
|
||||
download_process_id: data.batch_id,
|
||||
converted_spotify_playlist_id: playlistId
|
||||
})
|
||||
});
|
||||
console.log(`🔄 Updated ListenBrainz backend with download_process_id: ${data.batch_id}`);
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Error updating ListenBrainz backend with download_process_id:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startModalDownloadPolling(playlistId);
|
||||
} catch (error) {
|
||||
showToast(`Failed to start process: ${error.message}`, 'error');
|
||||
|
|
@ -16819,7 +16918,19 @@ function startListenBrainzDiscoveryPolling(playlistMbid) {
|
|||
clearInterval(pollInterval);
|
||||
delete activeYouTubePollers[playlistMbid];
|
||||
|
||||
// Update phase
|
||||
// Update phase in backend for persistence (like Beatport does)
|
||||
try {
|
||||
await fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phase: 'discovered' })
|
||||
});
|
||||
console.log('✅ Updated ListenBrainz backend phase to discovered');
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Failed to update backend phase:', error);
|
||||
}
|
||||
|
||||
// Update phase in frontend state
|
||||
if (listenbrainzPlaylistStates[playlistMbid]) {
|
||||
listenbrainzPlaylistStates[playlistMbid].phase = 'discovered';
|
||||
}
|
||||
|
|
@ -16827,6 +16938,14 @@ function startListenBrainzDiscoveryPolling(playlistMbid) {
|
|||
// Update modal buttons to show sync and download buttons
|
||||
updateYouTubeModalButtons(playlistMbid, 'discovered');
|
||||
|
||||
// Show sync button in playlist listing (hidden by default until discovered)
|
||||
const playlistId = `discover-lb-playlist-${playlistMbid}`;
|
||||
const syncBtn = document.getElementById(`${playlistId}-sync-btn`);
|
||||
if (syncBtn) {
|
||||
syncBtn.style.display = 'inline-block';
|
||||
console.log('✅ Showing sync button after discovery completion');
|
||||
}
|
||||
|
||||
console.log('✅ ListenBrainz discovery complete:', playlistMbid);
|
||||
showToast('ListenBrainz discovery complete!', 'success');
|
||||
}
|
||||
|
|
@ -16962,6 +17081,22 @@ async function startListenBrainzPlaylistSync(playlistMbid) {
|
|||
try {
|
||||
console.log('🔄 Starting ListenBrainz sync for:', state.playlist.name);
|
||||
|
||||
// Check if being called from playlist listing (has UI elements) or modal
|
||||
const listingPlaylistId = `discover-lb-playlist-${playlistMbid}`;
|
||||
const statusDisplay = document.getElementById(`${listingPlaylistId}-sync-status`);
|
||||
const isFromListing = statusDisplay !== null;
|
||||
|
||||
if (isFromListing) {
|
||||
console.log('🔄 Sync initiated from playlist listing');
|
||||
// Show status display in listing
|
||||
statusDisplay.style.display = 'block';
|
||||
const syncButton = document.getElementById(`${listingPlaylistId}-sync-btn`);
|
||||
if (syncButton) {
|
||||
syncButton.disabled = true;
|
||||
syncButton.style.opacity = '0.5';
|
||||
}
|
||||
}
|
||||
|
||||
// Call backend to start sync
|
||||
const response = await fetch(`/api/listenbrainz/sync/start/${playlistMbid}`, {
|
||||
method: 'POST'
|
||||
|
|
@ -16976,10 +17111,12 @@ async function startListenBrainzPlaylistSync(playlistMbid) {
|
|||
state.phase = 'syncing';
|
||||
|
||||
// Start polling for sync progress
|
||||
startListenBrainzSyncPolling(playlistMbid);
|
||||
|
||||
// Update modal
|
||||
updateYouTubeModalButtons(playlistMbid, 'syncing');
|
||||
if (isFromListing) {
|
||||
startListenBrainzListingSyncPolling(playlistMbid, listingPlaylistId);
|
||||
} else {
|
||||
startListenBrainzSyncPolling(playlistMbid);
|
||||
updateYouTubeModalButtons(playlistMbid, 'syncing');
|
||||
}
|
||||
|
||||
showToast('Starting ListenBrainz sync...', 'info');
|
||||
|
||||
|
|
@ -16989,6 +17126,86 @@ async function startListenBrainzPlaylistSync(playlistMbid) {
|
|||
}
|
||||
}
|
||||
|
||||
function startListenBrainzListingSyncPolling(playlistMbid, listingPlaylistId) {
|
||||
console.log(`🔄 Starting listing sync polling for: ${playlistMbid} (UI: ${listingPlaylistId})`);
|
||||
|
||||
// Stop any existing polling
|
||||
if (activeYouTubePollers[playlistMbid]) {
|
||||
clearInterval(activeYouTubePollers[playlistMbid]);
|
||||
}
|
||||
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/listenbrainz/sync/status/${playlistMbid}`);
|
||||
const status = await response.json();
|
||||
|
||||
if (status.error) {
|
||||
console.error('❌ Error polling ListenBrainz sync status:', status.error);
|
||||
clearInterval(pollInterval);
|
||||
delete activeYouTubePollers[playlistMbid];
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI elements in listing
|
||||
const totalEl = document.getElementById(`${listingPlaylistId}-sync-total`);
|
||||
const matchedEl = document.getElementById(`${listingPlaylistId}-sync-matched`);
|
||||
const failedEl = document.getElementById(`${listingPlaylistId}-sync-failed`);
|
||||
const percentageEl = document.getElementById(`${listingPlaylistId}-sync-percentage`);
|
||||
|
||||
console.log(`📊 ListenBrainz listing sync progress:`, {
|
||||
total: status.progress?.total_tracks,
|
||||
matched: status.progress?.matched_tracks,
|
||||
failed: status.progress?.failed_tracks,
|
||||
complete: status.complete
|
||||
});
|
||||
|
||||
if (totalEl) totalEl.textContent = status.progress?.total_tracks || 0;
|
||||
if (matchedEl) matchedEl.textContent = status.progress?.matched_tracks || 0;
|
||||
if (failedEl) failedEl.textContent = status.progress?.failed_tracks || 0;
|
||||
|
||||
const percentage = status.progress?.total_tracks > 0
|
||||
? Math.round(((status.progress?.matched_tracks || 0) / status.progress.total_tracks) * 100)
|
||||
: 0;
|
||||
if (percentageEl) percentageEl.textContent = percentage;
|
||||
|
||||
// Check if complete
|
||||
if (status.complete) {
|
||||
clearInterval(pollInterval);
|
||||
delete activeYouTubePollers[playlistMbid];
|
||||
|
||||
const statusDisplay = document.getElementById(`${listingPlaylistId}-sync-status`);
|
||||
const syncButton = document.getElementById(`${listingPlaylistId}-sync-btn`);
|
||||
|
||||
if (statusDisplay) {
|
||||
setTimeout(() => {
|
||||
statusDisplay.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
if (syncButton) {
|
||||
syncButton.disabled = false;
|
||||
syncButton.style.opacity = '1';
|
||||
}
|
||||
|
||||
// Update state
|
||||
if (listenbrainzPlaylistStates[playlistMbid]) {
|
||||
listenbrainzPlaylistStates[playlistMbid].phase = 'sync_complete';
|
||||
}
|
||||
|
||||
showToast(`Sync complete: ${status.progress?.matched_tracks || 0}/${status.progress?.total_tracks || 0} tracks matched`, 'success');
|
||||
console.log('✅ ListenBrainz listing sync complete:', playlistMbid);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error polling ListenBrainz listing sync:', error);
|
||||
clearInterval(pollInterval);
|
||||
delete activeYouTubePollers[playlistMbid];
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
activeYouTubePollers[playlistMbid] = pollInterval;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ARTISTS PAGE FUNCTIONALITY - ELEGANT SEARCH & DISCOVERY
|
||||
// ============================================================================
|
||||
|
|
@ -28026,7 +28243,7 @@ async function loadListenBrainzTabContent(tabId) {
|
|||
trackCount = playlistData.track.length;
|
||||
}
|
||||
|
||||
const playlistId = `lb-${tabId}-${index}`;
|
||||
const playlistId = `discover-lb-playlist-${identifier}`; // Use consistent MBID-based ID
|
||||
const virtualPlaylistId = `discover_lb_${tabId}_${identifier}`;
|
||||
|
||||
html += `
|
||||
|
|
@ -28045,8 +28262,9 @@ async function loadListenBrainzTabContent(tabId) {
|
|||
</button>
|
||||
<button class="action-button primary"
|
||||
id="${playlistId}-sync-btn"
|
||||
onclick="startListenBrainzPlaylistSync('${identifier}', '${escapeHtml(title)}', '${playlistId}')"
|
||||
title="Sync to media server">
|
||||
onclick="startListenBrainzPlaylistSync('${identifier}')"
|
||||
title="Sync to media server"
|
||||
style="display: none;">
|
||||
<span class="button-icon">⟳</span>
|
||||
<span class="button-text">Sync</span>
|
||||
</button>
|
||||
|
|
@ -28082,7 +28300,7 @@ async function loadListenBrainzTabContent(tabId) {
|
|||
playlists.forEach((playlist, index) => {
|
||||
const playlistData = playlist.playlist || playlist;
|
||||
const identifier = playlistData.identifier?.split('/').pop() || '';
|
||||
const playlistId = `lb-${tabId}-${index}`;
|
||||
const playlistId = `discover-lb-playlist-${identifier}`; // Use consistent MBID-based ID
|
||||
loadListenBrainzPlaylistTracks(identifier, playlistId);
|
||||
});
|
||||
}
|
||||
|
|
@ -28208,147 +28426,6 @@ function displayListenBrainzTracks(tracks, playlistId) {
|
|||
playlistContainer.innerHTML = html;
|
||||
}
|
||||
|
||||
async function startListenBrainzPlaylistSync(identifier, title, playlistId) {
|
||||
try {
|
||||
console.log(`🔄 Starting sync for ListenBrainz playlist:`, { identifier, title, playlistId });
|
||||
|
||||
const tracks = listenbrainzTracksCache[identifier];
|
||||
console.log(`📊 Cached tracks for ${identifier}:`, tracks?.length || 0);
|
||||
|
||||
if (!tracks || tracks.length === 0) {
|
||||
console.error('❌ No tracks found in cache');
|
||||
showToast('No tracks to sync', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Found ${tracks.length} tracks to sync`);
|
||||
|
||||
// Convert ListenBrainz tracks to Spotify format (same as other discover playlists)
|
||||
const spotifyTracks = tracks.map(track => ({
|
||||
id: null, // No Spotify ID for ListenBrainz tracks
|
||||
name: track.track_name,
|
||||
artists: [cleanArtistName(track.artist_name)], // Clean featured artists, array of strings for sync compatibility
|
||||
album: {
|
||||
name: track.album_name,
|
||||
images: track.album_cover_url ? [{ url: track.album_cover_url }] : []
|
||||
},
|
||||
duration_ms: track.duration_ms || 0,
|
||||
mbid: track.mbid // Include MusicBrainz ID for matching
|
||||
}));
|
||||
|
||||
const virtualPlaylistId = `discover_lb_${identifier}`;
|
||||
console.log(`🆔 Virtual playlist ID: ${virtualPlaylistId}`);
|
||||
|
||||
// Store in cache for sync function (CRITICAL - same as other discover playlists)
|
||||
playlistTrackCache[virtualPlaylistId] = spotifyTracks;
|
||||
|
||||
// Create virtual playlist object (CRITICAL - same as other discover playlists)
|
||||
const virtualPlaylist = {
|
||||
id: virtualPlaylistId,
|
||||
name: title,
|
||||
track_count: spotifyTracks.length
|
||||
};
|
||||
|
||||
// Add to spotify playlists array if not already there (CRITICAL)
|
||||
if (!spotifyPlaylists.find(p => p.id === virtualPlaylistId)) {
|
||||
spotifyPlaylists.push(virtualPlaylist);
|
||||
}
|
||||
|
||||
// Show sync status display
|
||||
const statusDisplay = document.getElementById(`${playlistId}-sync-status`);
|
||||
const syncButton = document.getElementById(`${playlistId}-sync-btn`);
|
||||
|
||||
if (statusDisplay) statusDisplay.style.display = 'block';
|
||||
if (syncButton) {
|
||||
syncButton.disabled = true;
|
||||
syncButton.style.opacity = '0.5';
|
||||
syncButton.style.cursor = 'not-allowed';
|
||||
}
|
||||
|
||||
// Use the same sync function as all other discover playlists
|
||||
await startPlaylistSync(virtualPlaylistId);
|
||||
|
||||
// Extract image URL from first track for download bar bubble
|
||||
let imageUrl = null;
|
||||
if (spotifyTracks && spotifyTracks.length > 0) {
|
||||
const firstTrack = spotifyTracks[0];
|
||||
if (firstTrack.album && firstTrack.album.images && firstTrack.album.images.length > 0) {
|
||||
imageUrl = firstTrack.album.images[0].url;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to discover download bar
|
||||
addDiscoverDownload(virtualPlaylistId, title, 'listenbrainz', imageUrl);
|
||||
|
||||
// Start polling for progress updates (using discover playlist pattern)
|
||||
startListenBrainzSyncPolling(playlistId, virtualPlaylistId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting ListenBrainz playlist sync:', error);
|
||||
showToast('Failed to start sync', 'error');
|
||||
|
||||
const statusDisplay = document.getElementById(`${playlistId}-sync-status`);
|
||||
const syncButton = document.getElementById(`${playlistId}-sync-btn`);
|
||||
if (statusDisplay) statusDisplay.style.display = 'none';
|
||||
if (syncButton) {
|
||||
syncButton.disabled = false;
|
||||
syncButton.style.opacity = '1';
|
||||
syncButton.style.cursor = 'pointer';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startListenBrainzSyncPolling(playlistId, virtualPlaylistId) {
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/sync/status/${virtualPlaylistId}`);
|
||||
if (!response.ok) {
|
||||
clearInterval(pollInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
const status = await response.json();
|
||||
|
||||
// Update UI
|
||||
const totalEl = document.getElementById(`${playlistId}-sync-total`);
|
||||
const matchedEl = document.getElementById(`${playlistId}-sync-matched`);
|
||||
const failedEl = document.getElementById(`${playlistId}-sync-failed`);
|
||||
const percentageEl = document.getElementById(`${playlistId}-sync-percentage`);
|
||||
|
||||
if (totalEl) totalEl.textContent = status.total_tracks || 0;
|
||||
if (matchedEl) matchedEl.textContent = status.matched_tracks || 0;
|
||||
if (failedEl) failedEl.textContent = status.failed_tracks || 0;
|
||||
|
||||
const percentage = status.total_tracks > 0
|
||||
? Math.round(((status.matched_tracks || 0) / status.total_tracks) * 100)
|
||||
: 0;
|
||||
if (percentageEl) percentageEl.textContent = percentage;
|
||||
|
||||
// Check if complete
|
||||
if (status.is_complete) {
|
||||
clearInterval(pollInterval);
|
||||
|
||||
const statusDisplay = document.getElementById(`${playlistId}-sync-status`);
|
||||
const syncButton = document.getElementById(`${playlistId}-sync-btn`);
|
||||
|
||||
if (statusDisplay) {
|
||||
setTimeout(() => {
|
||||
statusDisplay.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
if (syncButton) syncButton.disabled = false;
|
||||
|
||||
showToast(`Sync complete: ${status.matched_tracks}/${status.total_tracks} tracks matched`, 'success');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error polling sync status:', error);
|
||||
clearInterval(pollInterval);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async function openDownloadModalForListenBrainzPlaylist(identifier, title) {
|
||||
try {
|
||||
const tracks = listenbrainzTracksCache[identifier];
|
||||
|
|
@ -28358,9 +28435,12 @@ async function openDownloadModalForListenBrainzPlaylist(identifier, title) {
|
|||
}
|
||||
|
||||
console.log(`🎵 Opening ListenBrainz discovery modal: ${title}`);
|
||||
console.log(`🔍 Looking for existing state with identifier: ${identifier}`);
|
||||
console.log(`📋 All ListenBrainz states:`, Object.keys(listenbrainzPlaylistStates));
|
||||
|
||||
// Check if state already exists from backend hydration (like Beatport does)
|
||||
const existingState = listenbrainzPlaylistStates[identifier];
|
||||
console.log(`🔍 Existing state found:`, existingState ? `Phase: ${existingState.phase}` : 'None');
|
||||
|
||||
if (existingState && existingState.phase !== 'fresh') {
|
||||
// State exists - rehydrate the modal with existing data
|
||||
|
|
@ -28376,66 +28456,77 @@ async function openDownloadModalForListenBrainzPlaylist(identifier, title) {
|
|||
const convertedPlaylistId = existingState.convertedSpotifyPlaylistId;
|
||||
|
||||
try {
|
||||
// Check if modal already exists (user just closed it)
|
||||
if (activeDownloadProcesses[convertedPlaylistId]) {
|
||||
console.log(`✅ Download modal already exists, just showing it`);
|
||||
const process = activeDownloadProcesses[convertedPlaylistId];
|
||||
if (process.modalElement) {
|
||||
process.modalElement.style.display = 'flex';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the download modal using the ListenBrainz state
|
||||
if (!activeDownloadProcesses[convertedPlaylistId]) {
|
||||
// Get tracks from the existing state
|
||||
let spotifyTracks = [];
|
||||
console.log(`🆕 Creating new download modal for rehydration`);
|
||||
// Get tracks from the existing state
|
||||
let spotifyTracks = [];
|
||||
|
||||
if (existingState && existingState.discovery_results) {
|
||||
spotifyTracks = existingState.discovery_results
|
||||
.filter(result => result.spotify_data)
|
||||
.map(result => {
|
||||
const track = result.spotify_data;
|
||||
// Ensure artists is an array of strings
|
||||
if (track.artists && Array.isArray(track.artists)) {
|
||||
track.artists = track.artists.map(artist =>
|
||||
typeof artist === 'string' ? artist : (artist.name || artist)
|
||||
);
|
||||
} else if (track.artists && typeof track.artists === 'string') {
|
||||
track.artists = [track.artists];
|
||||
} else {
|
||||
track.artists = ['Unknown Artist'];
|
||||
}
|
||||
return {
|
||||
id: track.id,
|
||||
name: track.name,
|
||||
artists: track.artists,
|
||||
album: track.album || 'Unknown Album',
|
||||
duration_ms: track.duration_ms || 0,
|
||||
external_urls: track.external_urls || {}
|
||||
};
|
||||
});
|
||||
}
|
||||
if (existingState && existingState.discovery_results) {
|
||||
spotifyTracks = existingState.discovery_results
|
||||
.filter(result => result.spotify_data)
|
||||
.map(result => {
|
||||
const track = result.spotify_data;
|
||||
// Ensure artists is an array of strings
|
||||
if (track.artists && Array.isArray(track.artists)) {
|
||||
track.artists = track.artists.map(artist =>
|
||||
typeof artist === 'string' ? artist : (artist.name || artist)
|
||||
);
|
||||
} else if (track.artists && typeof track.artists === 'string') {
|
||||
track.artists = [track.artists];
|
||||
} else {
|
||||
track.artists = ['Unknown Artist'];
|
||||
}
|
||||
return {
|
||||
id: track.id,
|
||||
name: track.name,
|
||||
artists: track.artists,
|
||||
album: track.album || 'Unknown Album',
|
||||
duration_ms: track.duration_ms || 0,
|
||||
external_urls: track.external_urls || {}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (spotifyTracks.length > 0) {
|
||||
await openDownloadMissingModalForYouTube(
|
||||
convertedPlaylistId,
|
||||
`[ListenBrainz] ${title}`,
|
||||
spotifyTracks
|
||||
);
|
||||
if (spotifyTracks.length > 0) {
|
||||
await openDownloadMissingModalForYouTube(
|
||||
convertedPlaylistId,
|
||||
`[ListenBrainz] ${title}`,
|
||||
spotifyTracks
|
||||
);
|
||||
|
||||
// Set the modal to running state with the correct batch ID
|
||||
const process = activeDownloadProcesses[convertedPlaylistId];
|
||||
if (process) {
|
||||
process.status = existingState.phase === 'download_complete' ? 'complete' : 'running';
|
||||
process.batchId = existingState.download_process_id;
|
||||
// Set the modal to running state with the correct batch ID
|
||||
const process = activeDownloadProcesses[convertedPlaylistId];
|
||||
if (process) {
|
||||
process.status = existingState.phase === 'download_complete' ? 'complete' : 'running';
|
||||
process.batchId = existingState.download_process_id;
|
||||
|
||||
// Update UI to running state
|
||||
const beginBtn = document.getElementById(`begin-analysis-btn-${convertedPlaylistId}`);
|
||||
const cancelBtn = document.getElementById(`cancel-all-btn-${convertedPlaylistId}`);
|
||||
if (beginBtn) beginBtn.style.display = 'none';
|
||||
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||
// Update UI to running state
|
||||
const beginBtn = document.getElementById(`begin-analysis-btn-${convertedPlaylistId}`);
|
||||
const cancelBtn = document.getElementById(`cancel-all-btn-${convertedPlaylistId}`);
|
||||
if (beginBtn) beginBtn.style.display = 'none';
|
||||
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||
|
||||
// Start polling for this process
|
||||
startModalDownloadPolling(convertedPlaylistId);
|
||||
// Start polling for this process
|
||||
startModalDownloadPolling(convertedPlaylistId);
|
||||
|
||||
// Hide modal since this is background rehydration
|
||||
process.modalElement.style.display = 'none';
|
||||
console.log(`✅ Rehydrated download modal for ListenBrainz playlist: ${title}`);
|
||||
// Show modal since user clicked the download button (different from background rehydration)
|
||||
if (process.modalElement) {
|
||||
process.modalElement.style.display = 'flex';
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ No Spotify tracks found for ListenBrainz download modal: ${title}`);
|
||||
console.log(`✅ Rehydrated download modal for ListenBrainz playlist: ${title}`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ No Spotify tracks found for ListenBrainz download modal: ${title}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ Error setting up download process for ListenBrainz playlist "${title}":`, error.message);
|
||||
|
|
|
|||
Loading…
Reference in a new issue