Added new 'Save as playlist' functionality for sync page.
This commit is contained in:
parent
bbc7fdbfd4
commit
06a3328b65
2 changed files with 164 additions and 44 deletions
167
web_server.py
167
web_server.py
|
|
@ -6525,46 +6525,70 @@ def _post_process_matched_download_with_verification(context_key, context, file_
|
||||||
if not spotify_artist:
|
if not spotify_artist:
|
||||||
raise Exception("Missing spotify_artist context for verification")
|
raise Exception("Missing spotify_artist context for verification")
|
||||||
|
|
||||||
is_album_download = context.get("is_album_download", False)
|
# Check if playlist folder mode is enabled
|
||||||
has_clean_spotify_data = context.get("has_clean_spotify_data", False)
|
track_info = context.get("track_info", {})
|
||||||
|
playlist_folder_mode = track_info.get("_playlist_folder_mode", False)
|
||||||
|
|
||||||
# Reconstruct the final path logic (mirrors the logic in _post_process_matched_download)
|
# Handle playlist folder mode verification
|
||||||
if is_album_download and has_clean_spotify_data:
|
if playlist_folder_mode:
|
||||||
original_search = context.get("original_search_result", {})
|
playlist_name = track_info.get("_playlist_name", "Unknown Playlist")
|
||||||
spotify_album = context.get("spotify_album", {})
|
playlist_name_sanitized = _sanitize_filename(playlist_name)
|
||||||
clean_track_name = original_search.get('spotify_clean_title', 'Unknown Track')
|
|
||||||
clean_album_name = original_search.get('spotify_clean_album', 'Unknown Album')
|
|
||||||
track_number = original_search.get('track_number', 1)
|
|
||||||
|
|
||||||
# Construct the expected final path
|
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer'))
|
||||||
artist_name_sanitized = spotify_artist.name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
playlist_dir = os.path.join(transfer_dir, playlist_name_sanitized)
|
||||||
album_name_sanitized = clean_album_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
|
||||||
track_name_sanitized = clean_track_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
|
||||||
|
|
||||||
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './transfers'))
|
|
||||||
artist_dir = os.path.join(transfer_dir, artist_name_sanitized)
|
|
||||||
album_folder_name = f"{clean_album_name} ({spotify_album.get('release_date', '').split('-')[0] if spotify_album.get('release_date') else 'Unknown'})"
|
|
||||||
album_folder_name = album_folder_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
|
||||||
album_dir = os.path.join(artist_dir, album_folder_name)
|
|
||||||
|
|
||||||
file_ext = os.path.splitext(file_path)[1]
|
|
||||||
new_filename = f"{track_number:02d} - {track_name_sanitized}{file_ext}"
|
|
||||||
expected_final_path = os.path.join(album_dir, new_filename)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# For singles or fallback logic
|
|
||||||
original_search = context.get("original_search_result", {})
|
original_search = context.get("original_search_result", {})
|
||||||
track_name = original_search.get('spotify_clean_title') or original_search.get('title', 'Unknown Track')
|
track_name = original_search.get('spotify_clean_title') or original_search.get('title', 'Unknown Track')
|
||||||
track_name_sanitized = track_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
track_name_sanitized = _sanitize_filename(track_name)
|
||||||
|
artist_name_sanitized = _sanitize_filename(spotify_artist.get("name", "Unknown Artist") if isinstance(spotify_artist, dict) else spotify_artist.name)
|
||||||
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './transfers'))
|
|
||||||
artist_name_sanitized = spotify_artist.name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
|
||||||
artist_dir = os.path.join(transfer_dir, artist_name_sanitized)
|
|
||||||
single_dir = os.path.join(artist_dir, "Singles")
|
|
||||||
|
|
||||||
file_ext = os.path.splitext(file_path)[1]
|
file_ext = os.path.splitext(file_path)[1]
|
||||||
new_filename = f"{track_name_sanitized}{file_ext}"
|
new_filename = f"{artist_name_sanitized} - {track_name_sanitized}{file_ext}"
|
||||||
expected_final_path = os.path.join(single_dir, new_filename)
|
expected_final_path = os.path.join(playlist_dir, new_filename)
|
||||||
|
|
||||||
|
print(f"📁 [Verification - Playlist Mode] Expected path: {expected_final_path}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Original album/artist folder verification logic
|
||||||
|
is_album_download = context.get("is_album_download", False)
|
||||||
|
has_clean_spotify_data = context.get("has_clean_spotify_data", False)
|
||||||
|
|
||||||
|
if is_album_download and has_clean_spotify_data:
|
||||||
|
original_search = context.get("original_search_result", {})
|
||||||
|
spotify_album = context.get("spotify_album", {})
|
||||||
|
clean_track_name = original_search.get('spotify_clean_title', 'Unknown Track')
|
||||||
|
clean_album_name = original_search.get('spotify_clean_album', 'Unknown Album')
|
||||||
|
track_number = original_search.get('track_number', 1)
|
||||||
|
|
||||||
|
# Construct the expected final path
|
||||||
|
artist_name_sanitized = spotify_artist.name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
||||||
|
album_name_sanitized = clean_album_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
||||||
|
track_name_sanitized = clean_track_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
||||||
|
|
||||||
|
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './transfers'))
|
||||||
|
artist_dir = os.path.join(transfer_dir, artist_name_sanitized)
|
||||||
|
album_folder_name = f"{clean_album_name} ({spotify_album.get('release_date', '').split('-')[0] if spotify_album.get('release_date') else 'Unknown'})"
|
||||||
|
album_folder_name = album_folder_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
||||||
|
album_dir = os.path.join(artist_dir, album_folder_name)
|
||||||
|
|
||||||
|
file_ext = os.path.splitext(file_path)[1]
|
||||||
|
new_filename = f"{track_number:02d} - {track_name_sanitized}{file_ext}"
|
||||||
|
expected_final_path = os.path.join(album_dir, new_filename)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# For singles or fallback logic
|
||||||
|
original_search = context.get("original_search_result", {})
|
||||||
|
track_name = original_search.get('spotify_clean_title') or original_search.get('title', 'Unknown Track')
|
||||||
|
track_name_sanitized = track_name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
||||||
|
|
||||||
|
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './transfers'))
|
||||||
|
artist_name_sanitized = spotify_artist.name.replace('/', '_').replace('\\', '_').replace(':', '_').replace('*', '_').replace('?', '_').replace('"', '_').replace('<', '_').replace('>', '_').replace('|', '_')
|
||||||
|
artist_dir = os.path.join(transfer_dir, artist_name_sanitized)
|
||||||
|
single_dir = os.path.join(artist_dir, "Singles")
|
||||||
|
|
||||||
|
file_ext = os.path.splitext(file_path)[1]
|
||||||
|
new_filename = f"{track_name_sanitized}{file_ext}"
|
||||||
|
expected_final_path = os.path.join(single_dir, new_filename)
|
||||||
|
|
||||||
# VERIFICATION: Check if file exists at expected final path
|
# VERIFICATION: Check if file exists at expected final path
|
||||||
if os.path.exists(expected_final_path):
|
if os.path.exists(expected_final_path):
|
||||||
|
|
@ -6644,6 +6668,63 @@ def _post_process_matched_download(context_key, context, file_path):
|
||||||
print(f"❌ Post-processing failed: Missing spotify_artist context.")
|
print(f"❌ Post-processing failed: Missing spotify_artist context.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check if playlist folder mode is enabled (sync page playlists only)
|
||||||
|
track_info = context.get("track_info", {})
|
||||||
|
playlist_folder_mode = track_info.get("_playlist_folder_mode", False)
|
||||||
|
|
||||||
|
print(f"🔍 [Debug] Post-processing - track_info type: {type(track_info)}, is None: {track_info is None}, is empty: {not track_info}")
|
||||||
|
print(f"🔍 [Debug] Post-processing - playlist_folder_mode: {playlist_folder_mode}")
|
||||||
|
if track_info:
|
||||||
|
print(f"🔍 [Debug] Post-processing - track_info keys: {list(track_info.keys())}")
|
||||||
|
|
||||||
|
if playlist_folder_mode:
|
||||||
|
# Use playlist folder structure: Transfer/Playlist Name/Artist - Track.ext
|
||||||
|
playlist_name = track_info.get("_playlist_name", "Unknown Playlist")
|
||||||
|
print(f"📁 [Playlist Folder Mode] Organizing in playlist folder: {playlist_name}")
|
||||||
|
|
||||||
|
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer'))
|
||||||
|
playlist_name_sanitized = _sanitize_filename(playlist_name)
|
||||||
|
playlist_dir = os.path.join(transfer_dir, playlist_name_sanitized)
|
||||||
|
os.makedirs(playlist_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Get track name from context
|
||||||
|
original_search = context.get("original_search_result", {})
|
||||||
|
track_name = original_search.get('spotify_clean_title') or original_search.get('title', 'Unknown Track')
|
||||||
|
track_name_sanitized = _sanitize_filename(track_name)
|
||||||
|
artist_name_sanitized = _sanitize_filename(spotify_artist["name"])
|
||||||
|
|
||||||
|
# Create filename: Artist - Track.ext
|
||||||
|
file_ext = os.path.splitext(file_path)[1]
|
||||||
|
new_filename = f"{artist_name_sanitized} - {track_name_sanitized}{file_ext}"
|
||||||
|
final_path = os.path.join(playlist_dir, new_filename)
|
||||||
|
|
||||||
|
print(f"📁 Playlist folder: '{playlist_name_sanitized}'")
|
||||||
|
print(f"🎵 Track filename: '{new_filename}'")
|
||||||
|
|
||||||
|
# Move file to playlist folder
|
||||||
|
print(f"🚚 Moving '{os.path.basename(file_path)}' to '{final_path}'")
|
||||||
|
if os.path.exists(final_path):
|
||||||
|
print(f"⚠️ File already exists, overwriting: {os.path.basename(final_path)}")
|
||||||
|
os.remove(final_path)
|
||||||
|
|
||||||
|
shutil.move(file_path, final_path)
|
||||||
|
|
||||||
|
# Clean up empty directories in downloads folder
|
||||||
|
downloads_path = docker_resolve_path(config_manager.get('soulseek.download_path', './downloads'))
|
||||||
|
_cleanup_empty_directories(downloads_path, file_path)
|
||||||
|
|
||||||
|
print(f"✅ [Playlist Folder Mode] Post-processing complete: {final_path}")
|
||||||
|
|
||||||
|
# WISHLIST REMOVAL: Check if this track should be removed from wishlist
|
||||||
|
try:
|
||||||
|
_check_and_remove_from_wishlist(context)
|
||||||
|
except Exception as wishlist_error:
|
||||||
|
print(f"⚠️ [Playlist Folder] Error checking wishlist removal: {wishlist_error}")
|
||||||
|
|
||||||
|
# NOTE: Don't call callbacks here - let verification function handle completion
|
||||||
|
# The verification function will check file exists and then call callbacks
|
||||||
|
return # Skip normal album/artist folder structure processing
|
||||||
|
|
||||||
is_album_download = context.get("is_album_download", False)
|
is_album_download = context.get("is_album_download", False)
|
||||||
has_clean_spotify_data = context.get("has_clean_spotify_data", False)
|
has_clean_spotify_data = context.get("has_clean_spotify_data", False)
|
||||||
|
|
||||||
|
|
@ -8792,6 +8873,8 @@ def _run_full_missing_tracks_process(batch_id, playlist_id, tracks_json):
|
||||||
batch_album_context = batch.get('album_context')
|
batch_album_context = batch.get('album_context')
|
||||||
batch_artist_context = batch.get('artist_context')
|
batch_artist_context = batch.get('artist_context')
|
||||||
batch_is_album = batch.get('is_album_download', False)
|
batch_is_album = batch.get('is_album_download', False)
|
||||||
|
batch_playlist_folder_mode = batch.get('playlist_folder_mode', False)
|
||||||
|
batch_playlist_name = batch.get('playlist_name', 'Unknown Playlist')
|
||||||
|
|
||||||
for res in missing_tracks:
|
for res in missing_tracks:
|
||||||
task_id = str(uuid.uuid4())
|
task_id = str(uuid.uuid4())
|
||||||
|
|
@ -8804,6 +8887,14 @@ def _run_full_missing_tracks_process(batch_id, playlist_id, tracks_json):
|
||||||
track_info['_is_explicit_album_download'] = True
|
track_info['_is_explicit_album_download'] = True
|
||||||
print(f"🎵 [Task Creation] Added explicit album context for: {track_info.get('name')}")
|
print(f"🎵 [Task Creation] Added explicit album context for: {track_info.get('name')}")
|
||||||
|
|
||||||
|
# Add playlist folder mode flag for sync page playlists
|
||||||
|
if batch_playlist_folder_mode:
|
||||||
|
track_info['_playlist_folder_mode'] = True
|
||||||
|
track_info['_playlist_name'] = batch_playlist_name
|
||||||
|
print(f"📁 [Task Creation] Added playlist folder mode for: {track_info.get('name')} → {batch_playlist_name}")
|
||||||
|
else:
|
||||||
|
print(f"🔍 [Debug] Task Creation - playlist folder mode NOT enabled for: {track_info.get('name')}")
|
||||||
|
|
||||||
download_tasks[task_id] = {
|
download_tasks[task_id] = {
|
||||||
'status': 'pending', 'track_info': track_info,
|
'status': 'pending', 'track_info': track_info,
|
||||||
'playlist_id': playlist_id, 'batch_id': batch_id,
|
'playlist_id': playlist_id, 'batch_id': batch_id,
|
||||||
|
|
@ -9543,10 +9634,12 @@ def _attempt_download_with_candidates(task_id, candidates, track, batch_id=None)
|
||||||
"is_album_download": is_album_context, # Critical fix: Use actual album context
|
"is_album_download": is_album_context, # Critical fix: Use actual album context
|
||||||
"has_clean_spotify_data": has_clean_spotify_data, # Flag for post-processing
|
"has_clean_spotify_data": has_clean_spotify_data, # Flag for post-processing
|
||||||
"task_id": task_id, # Add task_id for completion callbacks
|
"task_id": task_id, # Add task_id for completion callbacks
|
||||||
"batch_id": batch_id # Add batch_id for completion callbacks
|
"batch_id": batch_id, # Add batch_id for completion callbacks
|
||||||
|
"track_info": track_info # Add track_info for playlist folder mode
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"🎯 [Context] Set is_album_download: {is_album_context} (has clean data: {has_clean_spotify_data})")
|
print(f"🎯 [Context] Set is_album_download: {is_album_context} (has clean data: {has_clean_spotify_data})")
|
||||||
|
print(f"🔍 [Debug] Context creation - track_info: {track_info is not None}, playlist_folder_mode: {track_info.get('_playlist_folder_mode', False) if track_info else False}")
|
||||||
|
|
||||||
# Update task with successful download info
|
# Update task with successful download info
|
||||||
with tasks_lock:
|
with tasks_lock:
|
||||||
|
|
@ -10619,6 +10712,7 @@ def start_missing_tracks_process(playlist_id):
|
||||||
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)
|
force_download_all = data.get('force_download_all', False)
|
||||||
|
playlist_folder_mode = data.get('playlist_folder_mode', False)
|
||||||
|
|
||||||
# Get album/artist context for artist album downloads
|
# Get album/artist context for artist album downloads
|
||||||
is_album_download = data.get('is_album_download', False)
|
is_album_download = data.get('is_album_download', False)
|
||||||
|
|
@ -10633,6 +10727,10 @@ def start_missing_tracks_process(playlist_id):
|
||||||
print(f"🎵 [Artist Album] Received album context: '{album_context.get('name')}' by '{artist_context.get('name')}' ({album_context.get('album_type', 'album')})")
|
print(f"🎵 [Artist Album] Received album context: '{album_context.get('name')}' by '{artist_context.get('name')}' ({album_context.get('album_type', 'album')})")
|
||||||
print(f" Release: {album_context.get('release_date', 'Unknown')}, Tracks: {album_context.get('total_tracks', len(tracks))}")
|
print(f" Release: {album_context.get('release_date', 'Unknown')}, Tracks: {album_context.get('total_tracks', len(tracks))}")
|
||||||
|
|
||||||
|
# Log playlist folder mode if enabled
|
||||||
|
if playlist_folder_mode:
|
||||||
|
print(f"📁 [Playlist Folder] Enabled for playlist: '{playlist_name}'")
|
||||||
|
|
||||||
# Limit concurrent analysis processes to prevent resource exhaustion
|
# Limit concurrent analysis processes to prevent resource exhaustion
|
||||||
with tasks_lock:
|
with tasks_lock:
|
||||||
active_analysis_count = sum(1 for batch in download_batches.values()
|
active_analysis_count = sum(1 for batch in download_batches.values()
|
||||||
|
|
@ -10661,6 +10759,7 @@ def start_missing_tracks_process(playlist_id):
|
||||||
'analysis_processed': 0,
|
'analysis_processed': 0,
|
||||||
'analysis_results': [],
|
'analysis_results': [],
|
||||||
'force_download_all': force_download_all, # Pass the force flag to the batch
|
'force_download_all': force_download_all, # Pass the force flag to the batch
|
||||||
|
'playlist_folder_mode': playlist_folder_mode, # Organize downloads by playlist folder
|
||||||
# Album context for artist album downloads (explicit folder structure)
|
# Album context for artist album downloads (explicit folder structure)
|
||||||
'is_album_download': is_album_download,
|
'is_album_download': is_album_download,
|
||||||
'album_context': album_context,
|
'album_context': album_context,
|
||||||
|
|
|
||||||
|
|
@ -3850,11 +3850,15 @@ 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;">
|
<div class="force-download-toggle-container" style="margin-bottom: 0px; display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
|
||||||
<label class="force-download-toggle">
|
<label class="force-download-toggle">
|
||||||
<input type="checkbox" id="force-download-all-${playlistId}">
|
<input type="checkbox" id="force-download-all-${playlistId}">
|
||||||
<span>Force Download All</span>
|
<span>Force Download All</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="playlist-folder-mode-${playlistId}">
|
||||||
|
<span>Organize by Playlist (Downloads/Playlist/Artist - Track.ext)</span>
|
||||||
|
</label>
|
||||||
</div>
|
</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
|
||||||
|
|
@ -4016,11 +4020,15 @@ 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;">
|
<div class="force-download-toggle-container" style="margin-bottom: 0px; display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
|
||||||
<label class="force-download-toggle">
|
<label class="force-download-toggle">
|
||||||
<input type="checkbox" id="force-download-all-${virtualPlaylistId}">
|
<input type="checkbox" id="force-download-all-${virtualPlaylistId}">
|
||||||
<span>Force Download All</span>
|
<span>Force Download All</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="playlist-folder-mode-${virtualPlaylistId}">
|
||||||
|
<span>Organize by Playlist (Downloads/Playlist/Artist - Track.ext)</span>
|
||||||
|
</label>
|
||||||
</div>
|
</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
|
||||||
|
|
@ -4530,6 +4538,10 @@ async function startMissingTracksProcess(playlistId) {
|
||||||
const forceDownloadCheckbox = document.getElementById(`force-download-all-${playlistId}`);
|
const forceDownloadCheckbox = document.getElementById(`force-download-all-${playlistId}`);
|
||||||
const forceDownloadAll = forceDownloadCheckbox ? forceDownloadCheckbox.checked : false;
|
const forceDownloadAll = forceDownloadCheckbox ? forceDownloadCheckbox.checked : false;
|
||||||
|
|
||||||
|
// Check if playlist folder mode toggle is enabled (only for sync page playlists)
|
||||||
|
const playlistFolderModeCheckbox = document.getElementById(`playlist-folder-mode-${playlistId}`);
|
||||||
|
const playlistFolderMode = playlistFolderModeCheckbox ? playlistFolderModeCheckbox.checked : false;
|
||||||
|
|
||||||
// Hide the force download toggle during processing
|
// Hide the force download toggle during processing
|
||||||
const forceToggleContainer = forceDownloadCheckbox ? forceDownloadCheckbox.closest('.force-download-toggle-container') : null;
|
const forceToggleContainer = forceDownloadCheckbox ? forceDownloadCheckbox.closest('.force-download-toggle-container') : null;
|
||||||
if (forceToggleContainer) {
|
if (forceToggleContainer) {
|
||||||
|
|
@ -4552,6 +4564,11 @@ async function startMissingTracksProcess(playlistId) {
|
||||||
} else {
|
} else {
|
||||||
// For playlists/wishlists, use the virtual playlist name
|
// For playlists/wishlists, use the virtual playlist name
|
||||||
requestBody.playlist_name = process.playlist.name;
|
requestBody.playlist_name = process.playlist.name;
|
||||||
|
// Add playlist folder mode flag for sync page playlists
|
||||||
|
requestBody.playlist_folder_mode = playlistFolderMode;
|
||||||
|
if (playlistFolderMode) {
|
||||||
|
console.log(`📁 [Playlist Folder] Enabled for playlist: ${process.playlist.name}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/playlists/${playlistId}/start-missing-process`, {
|
const response = await fetch(`/api/playlists/${playlistId}/start-missing-process`, {
|
||||||
|
|
@ -10376,11 +10393,15 @@ 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;">
|
<div class="force-download-toggle-container" style="margin-bottom: 0px; display: flex; flex-direction: column; gap: 8px; align-items: flex-start;">
|
||||||
<label class="force-download-toggle">
|
<label class="force-download-toggle">
|
||||||
<input type="checkbox" id="force-download-all-${virtualPlaylistId}">
|
<input type="checkbox" id="force-download-all-${virtualPlaylistId}">
|
||||||
<span>Force Download All</span>
|
<span>Force Download All</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="force-download-toggle">
|
||||||
|
<input type="checkbox" id="playlist-folder-mode-${virtualPlaylistId}">
|
||||||
|
<span>Organize by Playlist (Downloads/Playlist/Artist - Track.ext)</span>
|
||||||
|
</label>
|
||||||
</div>
|
</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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue