toggle to force the download of all tracks in modal
This commit is contained in:
parent
688aa6581f
commit
180413677c
3 changed files with 195 additions and 19 deletions
|
|
@ -6094,6 +6094,9 @@ def start_wishlist_missing_downloads():
|
||||||
identical to playlist processing, maintaining exactly 3 concurrent downloads.
|
identical to playlist processing, maintaining exactly 3 concurrent downloads.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
data = request.get_json() or {}
|
||||||
|
force_download_all = data.get('force_download_all', False)
|
||||||
|
|
||||||
from core.wishlist_service import get_wishlist_service
|
from core.wishlist_service import get_wishlist_service
|
||||||
wishlist_service = get_wishlist_service()
|
wishlist_service = get_wishlist_service()
|
||||||
|
|
||||||
|
|
@ -6135,7 +6138,8 @@ def start_wishlist_missing_downloads():
|
||||||
'analysis_results': [],
|
'analysis_results': [],
|
||||||
# Track state management (replicating sync.py)
|
# Track state management (replicating sync.py)
|
||||||
'permanently_failed_tracks': [],
|
'permanently_failed_tracks': [],
|
||||||
'cancelled_tracks': set()
|
'cancelled_tracks': set(),
|
||||||
|
'force_download_all': force_download_all # Pass the force flag to the batch
|
||||||
}
|
}
|
||||||
|
|
||||||
# Submit the wishlist processing job using the same processing function
|
# Submit the wishlist processing job using the same processing function
|
||||||
|
|
@ -6992,25 +6996,39 @@ def _run_full_missing_tracks_process(batch_id, playlist_id, tracks_json):
|
||||||
active_server = config_manager.get_active_media_server()
|
active_server = config_manager.get_active_media_server()
|
||||||
analysis_results = []
|
analysis_results = []
|
||||||
|
|
||||||
|
# Get force download flag from batch
|
||||||
|
force_download_all = False
|
||||||
|
with tasks_lock:
|
||||||
|
if batch_id in download_batches:
|
||||||
|
force_download_all = download_batches[batch_id].get('force_download_all', False)
|
||||||
|
|
||||||
|
if force_download_all:
|
||||||
|
print(f"🔄 [Force Download] Force download mode enabled for batch {batch_id} - treating all tracks as missing")
|
||||||
|
|
||||||
for i, track_data in enumerate(tracks_json):
|
for i, track_data in enumerate(tracks_json):
|
||||||
track_name = track_data.get('name', '')
|
track_name = track_data.get('name', '')
|
||||||
artists = track_data.get('artists', [])
|
artists = track_data.get('artists', [])
|
||||||
found, confidence = False, 0.0
|
found, confidence = False, 0.0
|
||||||
|
|
||||||
for artist in artists:
|
# Skip database check if force download is enabled
|
||||||
# Handle both string format and Spotify API format {'name': 'Artist Name'}
|
if force_download_all:
|
||||||
if isinstance(artist, str):
|
print(f"🔄 [Force Download] Skipping database check for '{track_name}' - treating as missing")
|
||||||
artist_name = artist
|
found, confidence = False, 0.0
|
||||||
elif isinstance(artist, dict) and 'name' in artist:
|
else:
|
||||||
artist_name = artist['name']
|
for artist in artists:
|
||||||
else:
|
# Handle both string format and Spotify API format {'name': 'Artist Name'}
|
||||||
artist_name = str(artist)
|
if isinstance(artist, str):
|
||||||
db_track, track_confidence = db.check_track_exists(
|
artist_name = artist
|
||||||
track_name, artist_name, confidence_threshold=0.7, server_source=active_server
|
elif isinstance(artist, dict) and 'name' in artist:
|
||||||
)
|
artist_name = artist['name']
|
||||||
if db_track and track_confidence >= 0.7:
|
else:
|
||||||
found, confidence = True, track_confidence
|
artist_name = str(artist)
|
||||||
break
|
db_track, track_confidence = db.check_track_exists(
|
||||||
|
track_name, artist_name, confidence_threshold=0.7, server_source=active_server
|
||||||
|
)
|
||||||
|
if db_track and track_confidence >= 0.7:
|
||||||
|
found, confidence = True, track_confidence
|
||||||
|
break
|
||||||
|
|
||||||
analysis_results.append({
|
analysis_results.append({
|
||||||
'track_index': i, 'track': track_data, 'found': found, 'confidence': confidence
|
'track_index': i, 'track': track_data, 'found': found, 'confidence': confidence
|
||||||
|
|
@ -8818,6 +8836,7 @@ def start_missing_tracks_process(playlist_id):
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
tracks = data.get('tracks', [])
|
tracks = data.get('tracks', [])
|
||||||
playlist_name = data.get('playlist_name', 'Unknown Playlist')
|
playlist_name = data.get('playlist_name', 'Unknown Playlist')
|
||||||
|
force_download_all = data.get('force_download_all', False)
|
||||||
|
|
||||||
if not tracks:
|
if not tracks:
|
||||||
return jsonify({"success": False, "error": "No tracks provided"}), 400
|
return jsonify({"success": False, "error": "No tracks provided"}), 400
|
||||||
|
|
@ -8848,7 +8867,8 @@ def start_missing_tracks_process(playlist_id):
|
||||||
'queue_index': 0,
|
'queue_index': 0,
|
||||||
'analysis_total': len(tracks),
|
'analysis_total': len(tracks),
|
||||||
'analysis_processed': 0,
|
'analysis_processed': 0,
|
||||||
'analysis_results': []
|
'analysis_results': [],
|
||||||
|
'force_download_all': force_download_all # Pass the force flag to the batch
|
||||||
}
|
}
|
||||||
|
|
||||||
# Link YouTube playlist to download process if this is a YouTube playlist
|
# Link YouTube playlist to download process if this is a YouTube playlist
|
||||||
|
|
|
||||||
|
|
@ -3181,6 +3181,12 @@ async function openDownloadMissingModal(playlistId) {
|
||||||
|
|
||||||
<div class="download-missing-modal-footer">
|
<div class="download-missing-modal-footer">
|
||||||
<div class="download-phase-controls">
|
<div class="download-phase-controls">
|
||||||
|
<div class="force-download-toggle-container" style="margin-bottom: 0px;">
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="force-download-all-${playlistId}">
|
||||||
|
<span>Force Download All</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${playlistId}" onclick="startMissingTracksProcess('${playlistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${playlistId}" onclick="startMissingTracksProcess('${playlistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -3335,6 +3341,12 @@ async function openDownloadMissingModalForYouTube(virtualPlaylistId, playlistNam
|
||||||
|
|
||||||
<div class="download-missing-modal-footer">
|
<div class="download-missing-modal-footer">
|
||||||
<div class="download-phase-controls">
|
<div class="download-phase-controls">
|
||||||
|
<div class="force-download-toggle-container" style="margin-bottom: 0px;">
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="force-download-all-${virtualPlaylistId}">
|
||||||
|
<span>Force Download All</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -3636,6 +3648,12 @@ async function openDownloadMissingWishlistModal() {
|
||||||
|
|
||||||
<div class="download-missing-modal-footer">
|
<div class="download-missing-modal-footer">
|
||||||
<div class="download-phase-controls">
|
<div class="download-phase-controls">
|
||||||
|
<div class="force-download-toggle-container" style="margin-bottom: 0px;">
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="force-download-all-${playlistId}">
|
||||||
|
<span>Force Download All</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${playlistId}" onclick="startWishlistMissingTracksProcess('${playlistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${playlistId}" onclick="startWishlistMissingTracksProcess('${playlistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -3671,9 +3689,22 @@ async function startWishlistMissingTracksProcess(playlistId) {
|
||||||
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
||||||
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
||||||
|
|
||||||
|
// Check if force download toggle is enabled
|
||||||
|
const forceDownloadCheckbox = document.getElementById(`force-download-all-${playlistId}`);
|
||||||
|
const forceDownloadAll = forceDownloadCheckbox ? forceDownloadCheckbox.checked : false;
|
||||||
|
|
||||||
|
// Hide the force download toggle during processing
|
||||||
|
const forceToggleContainer = forceDownloadCheckbox ? forceDownloadCheckbox.closest('.force-download-toggle-container') : null;
|
||||||
|
if (forceToggleContainer) {
|
||||||
|
forceToggleContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch('/api/wishlist/download_missing', {
|
const response = await fetch('/api/wishlist/download_missing', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
force_download_all: forceDownloadAll
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
@ -3700,6 +3731,12 @@ async function startWishlistMissingTracksProcess(playlistId) {
|
||||||
// Note: Wishlist processes don't affect sync page refresh button state
|
// Note: Wishlist processes don't affect sync page refresh button state
|
||||||
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'inline-block';
|
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'inline-block';
|
||||||
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'none';
|
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'none';
|
||||||
|
|
||||||
|
// Show the force download toggle again
|
||||||
|
const forceToggleContainer = document.querySelector(`#force-download-all-${playlistId}`)?.closest('.force-download-toggle-container');
|
||||||
|
if (forceToggleContainer) {
|
||||||
|
forceToggleContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3744,12 +3781,23 @@ async function startMissingTracksProcess(playlistId) {
|
||||||
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
||||||
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
||||||
|
|
||||||
|
// Check if force download toggle is enabled
|
||||||
|
const forceDownloadCheckbox = document.getElementById(`force-download-all-${playlistId}`);
|
||||||
|
const forceDownloadAll = forceDownloadCheckbox ? forceDownloadCheckbox.checked : false;
|
||||||
|
|
||||||
|
// Hide the force download toggle during processing
|
||||||
|
const forceToggleContainer = forceDownloadCheckbox ? forceDownloadCheckbox.closest('.force-download-toggle-container') : null;
|
||||||
|
if (forceToggleContainer) {
|
||||||
|
forceToggleContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/playlists/${playlistId}/start-missing-process`, {
|
const response = await fetch(`/api/playlists/${playlistId}/start-missing-process`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
tracks: process.tracks,
|
tracks: process.tracks,
|
||||||
playlist_name: process.playlist.name
|
playlist_name: process.playlist.name,
|
||||||
|
force_download_all: forceDownloadAll
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -4187,6 +4235,12 @@ function processModalStatusUpdate(playlistId, data) {
|
||||||
process.status = 'complete';
|
process.status = 'complete';
|
||||||
updatePlaylistCardUI(playlistId);
|
updatePlaylistCardUI(playlistId);
|
||||||
|
|
||||||
|
// Show the force download toggle again
|
||||||
|
const forceToggleContainer = document.querySelector(`#force-download-all-${playlistId}`)?.closest('.force-download-toggle-container');
|
||||||
|
if (forceToggleContainer) {
|
||||||
|
forceToggleContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
// Set album to downloaded status if this is an artist album
|
// Set album to downloaded status if this is an artist album
|
||||||
if (playlistId.startsWith('artist_album_')) {
|
if (playlistId.startsWith('artist_album_')) {
|
||||||
const parts = playlistId.split('_');
|
const parts = playlistId.split('_');
|
||||||
|
|
@ -7207,6 +7261,12 @@ function resetWishlistModalToIdleState() {
|
||||||
if (cancelBtn) {
|
if (cancelBtn) {
|
||||||
cancelBtn.style.display = 'none';
|
cancelBtn.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show the force download toggle again
|
||||||
|
const forceToggleContainer = document.querySelector(`#force-download-all-${playlistId}`)?.closest('.force-download-toggle-container');
|
||||||
|
if (forceToggleContainer) {
|
||||||
|
forceToggleContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
// Reset progress displays
|
// Reset progress displays
|
||||||
const analysisText = document.getElementById(`analysis-progress-text-${playlistId}`);
|
const analysisText = document.getElementById(`analysis-progress-text-${playlistId}`);
|
||||||
|
|
@ -8731,6 +8791,12 @@ async function openDownloadMissingModalForTidal(virtualPlaylistId, playlistName,
|
||||||
|
|
||||||
<div class="download-missing-modal-footer">
|
<div class="download-missing-modal-footer">
|
||||||
<div class="download-phase-controls">
|
<div class="download-phase-controls">
|
||||||
|
<div class="force-download-toggle-container" style="margin-bottom: 0px;">
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="force-download-all-${virtualPlaylistId}">
|
||||||
|
<span>Force Download All</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -11676,6 +11742,12 @@ async function openDownloadMissingModalForArtistAlbum(virtualPlaylistId, playlis
|
||||||
|
|
||||||
<div class="download-missing-modal-footer">
|
<div class="download-missing-modal-footer">
|
||||||
<div class="download-phase-controls">
|
<div class="download-phase-controls">
|
||||||
|
<div class="force-download-toggle-container" style="margin-bottom: 0px;">
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="force-download-all-${virtualPlaylistId}">
|
||||||
|
<span>Force Download All</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -6493,6 +6493,90 @@ body {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Force Download Toggle Styling */
|
||||||
|
.force-download-toggle-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle-container:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-color: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle span {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle Switch */
|
||||||
|
.force-download-toggle input[type="checkbox"] {
|
||||||
|
position: relative;
|
||||||
|
width: 48px;
|
||||||
|
height: 24px;
|
||||||
|
appearance: none;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle input[type="checkbox"]:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle input[type="checkbox"]:checked {
|
||||||
|
background: linear-gradient(135deg, #1db954, #1ed760);
|
||||||
|
border-color: rgba(29, 185, 84, 0.4);
|
||||||
|
box-shadow: 0 0 12px rgba(29, 185, 84, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle input[type="checkbox"]:checked:before {
|
||||||
|
transform: translateX(24px);
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle input[type="checkbox"]:checked + span {
|
||||||
|
color: #1ed760;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle:hover input[type="checkbox"] {
|
||||||
|
border-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.force-download-toggle:hover input[type="checkbox"]:checked {
|
||||||
|
border-color: rgba(29, 185, 84, 0.6);
|
||||||
|
box-shadow: 0 0 16px rgba(29, 185, 84, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
.modal-close-section {
|
.modal-close-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue