toggle to force the download of all tracks in modal

This commit is contained in:
Broque Thomas 2025-09-23 10:42:10 -07:00
parent 688aa6581f
commit 180413677c
3 changed files with 195 additions and 19 deletions

View file

@ -6094,6 +6094,9 @@ def start_wishlist_missing_downloads():
identical to playlist processing, maintaining exactly 3 concurrent downloads.
"""
try:
data = request.get_json() or {}
force_download_all = data.get('force_download_all', False)
from core.wishlist_service import get_wishlist_service
wishlist_service = get_wishlist_service()
@ -6135,7 +6138,8 @@ def start_wishlist_missing_downloads():
'analysis_results': [],
# Track state management (replicating sync.py)
'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
@ -6992,25 +6996,39 @@ def _run_full_missing_tracks_process(batch_id, playlist_id, tracks_json):
active_server = config_manager.get_active_media_server()
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):
track_name = track_data.get('name', '')
artists = track_data.get('artists', [])
found, confidence = False, 0.0
for artist in artists:
# Handle both string format and Spotify API format {'name': 'Artist Name'}
if isinstance(artist, str):
artist_name = artist
elif isinstance(artist, dict) and 'name' in artist:
artist_name = artist['name']
else:
artist_name = str(artist)
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
# Skip database check if force download is enabled
if force_download_all:
print(f"🔄 [Force Download] Skipping database check for '{track_name}' - treating as missing")
found, confidence = False, 0.0
else:
for artist in artists:
# Handle both string format and Spotify API format {'name': 'Artist Name'}
if isinstance(artist, str):
artist_name = artist
elif isinstance(artist, dict) and 'name' in artist:
artist_name = artist['name']
else:
artist_name = str(artist)
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({
'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()
tracks = data.get('tracks', [])
playlist_name = data.get('playlist_name', 'Unknown Playlist')
force_download_all = data.get('force_download_all', False)
if not tracks:
return jsonify({"success": False, "error": "No tracks provided"}), 400
@ -8848,7 +8867,8 @@ def start_missing_tracks_process(playlist_id):
'queue_index': 0,
'analysis_total': len(tracks),
'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

View file

@ -3181,6 +3181,12 @@ async function openDownloadMissingModal(playlistId) {
<div class="download-missing-modal-footer">
<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}')">
Begin Analysis
</button>
@ -3335,6 +3341,12 @@ async function openDownloadMissingModalForYouTube(virtualPlaylistId, playlistNam
<div class="download-missing-modal-footer">
<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}')">
Begin Analysis
</button>
@ -3636,6 +3648,12 @@ async function openDownloadMissingWishlistModal() {
<div class="download-missing-modal-footer">
<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}')">
Begin Analysis
</button>
@ -3671,9 +3689,22 @@ async function startWishlistMissingTracksProcess(playlistId) {
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
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', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
force_download_all: forceDownloadAll
})
});
const data = await response.json();
@ -3700,6 +3731,12 @@ async function startWishlistMissingTracksProcess(playlistId) {
// Note: Wishlist processes don't affect sync page refresh button state
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'inline-block';
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(`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`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
body: JSON.stringify({
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';
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
if (playlistId.startsWith('artist_album_')) {
const parts = playlistId.split('_');
@ -7207,6 +7261,12 @@ function resetWishlistModalToIdleState() {
if (cancelBtn) {
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
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-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}')">
Begin Analysis
</button>
@ -11676,6 +11742,12 @@ async function openDownloadMissingModalForArtistAlbum(virtualPlaylistId, playlis
<div class="download-missing-modal-footer">
<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}')">
Begin Analysis
</button>

View file

@ -6493,6 +6493,90 @@ body {
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 {
display: flex;
align-items: center;