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.
|
||||
"""
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue