|
|
|
@ -3242,12 +3242,33 @@ def start_download():
|
|
|
|
started_downloads = 0
|
|
|
|
started_downloads = 0
|
|
|
|
for track_data in tracks:
|
|
|
|
for track_data in tracks:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
|
|
|
|
username = track_data.get('username')
|
|
|
|
|
|
|
|
filename = track_data.get('filename')
|
|
|
|
|
|
|
|
file_size = track_data.get('size', 0)
|
|
|
|
|
|
|
|
|
|
|
|
download_id = asyncio.run(soulseek_client.download(
|
|
|
|
download_id = asyncio.run(soulseek_client.download(
|
|
|
|
track_data.get('username'),
|
|
|
|
username,
|
|
|
|
track_data.get('filename'),
|
|
|
|
filename,
|
|
|
|
track_data.get('size', 0)
|
|
|
|
file_size
|
|
|
|
))
|
|
|
|
))
|
|
|
|
if download_id:
|
|
|
|
if download_id:
|
|
|
|
|
|
|
|
# Register download for post-processing (simple transfer to /Transfer)
|
|
|
|
|
|
|
|
context_key = f"{username}::{filename}"
|
|
|
|
|
|
|
|
with matched_context_lock:
|
|
|
|
|
|
|
|
matched_downloads_context[context_key] = {
|
|
|
|
|
|
|
|
'search_result': {
|
|
|
|
|
|
|
|
'username': username,
|
|
|
|
|
|
|
|
'filename': filename,
|
|
|
|
|
|
|
|
'size': file_size,
|
|
|
|
|
|
|
|
'title': track_data.get('title', 'Unknown'),
|
|
|
|
|
|
|
|
'artist': track_data.get('artist', 'Unknown'),
|
|
|
|
|
|
|
|
'quality': track_data.get('quality', 'Unknown'),
|
|
|
|
|
|
|
|
'is_simple_download': True # Flag for simple processing
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'spotify_artist': None, # No Spotify metadata
|
|
|
|
|
|
|
|
'spotify_album': None,
|
|
|
|
|
|
|
|
'track_info': None
|
|
|
|
|
|
|
|
}
|
|
|
|
started_downloads += 1
|
|
|
|
started_downloads += 1
|
|
|
|
except Exception as e:
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(f"Failed to start track download: {e}")
|
|
|
|
logger.error(f"Failed to start track download: {e}")
|
|
|
|
@ -3255,7 +3276,7 @@ def start_download():
|
|
|
|
|
|
|
|
|
|
|
|
# Add activity for album download start
|
|
|
|
# Add activity for album download start
|
|
|
|
album_name = data.get('album_name', 'Unknown Album')
|
|
|
|
album_name = data.get('album_name', 'Unknown Album')
|
|
|
|
logger.info(f"📥 Starting album download: '{album_name}' with {started_downloads}/{len(tracks)} tracks")
|
|
|
|
logger.info(f"📥 Starting simple album download: '{album_name}' with {started_downloads}/{len(tracks)} tracks")
|
|
|
|
add_activity_item("📥", "Album Download Started", f"'{album_name}' - {started_downloads} tracks", "Now")
|
|
|
|
add_activity_item("📥", "Album Download Started", f"'{album_name}' - {started_downloads} tracks", "Now")
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
return jsonify({
|
|
|
|
@ -3275,9 +3296,28 @@ def start_download():
|
|
|
|
download_id = asyncio.run(soulseek_client.download(username, filename, file_size))
|
|
|
|
download_id = asyncio.run(soulseek_client.download(username, filename, file_size))
|
|
|
|
|
|
|
|
|
|
|
|
if download_id:
|
|
|
|
if download_id:
|
|
|
|
|
|
|
|
# Register download for post-processing (simple transfer to /Transfer)
|
|
|
|
|
|
|
|
context_key = f"{username}::{filename}"
|
|
|
|
|
|
|
|
with matched_context_lock:
|
|
|
|
|
|
|
|
matched_downloads_context[context_key] = {
|
|
|
|
|
|
|
|
'search_result': {
|
|
|
|
|
|
|
|
'username': username,
|
|
|
|
|
|
|
|
'filename': filename,
|
|
|
|
|
|
|
|
'size': file_size,
|
|
|
|
|
|
|
|
'title': data.get('title', 'Unknown'),
|
|
|
|
|
|
|
|
'artist': data.get('artist', 'Unknown'),
|
|
|
|
|
|
|
|
'quality': data.get('quality', 'Unknown'),
|
|
|
|
|
|
|
|
'is_simple_download': True # Flag for simple processing
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'spotify_artist': None, # No Spotify metadata
|
|
|
|
|
|
|
|
'spotify_album': None,
|
|
|
|
|
|
|
|
'track_info': None
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info(f"Registered simple download for post-processing: {context_key}")
|
|
|
|
|
|
|
|
|
|
|
|
# Extract track name from filename for activity
|
|
|
|
# Extract track name from filename for activity
|
|
|
|
track_name = filename.split('/')[-1] if '/' in filename else filename.split('\\')[-1] if '\\' in filename else filename
|
|
|
|
track_name = filename.split('/')[-1] if '/' in filename else filename.split('\\')[-1] if '\\' in filename else filename
|
|
|
|
logger.info(f"📥 Starting single track download: '{track_name}'")
|
|
|
|
logger.info(f"📥 Starting simple track download: '{track_name}'")
|
|
|
|
add_activity_item("📥", "Track Download Started", f"'{track_name}'", "Now")
|
|
|
|
add_activity_item("📥", "Track Download Started", f"'{track_name}'", "Now")
|
|
|
|
return jsonify({"success": True, "message": "Download started"})
|
|
|
|
return jsonify({"success": True, "message": "Download started"})
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
@ -5449,7 +5489,8 @@ def _start_album_download_tasks(album_result, spotify_artist, spotify_album):
|
|
|
|
"spotify_artist": spotify_artist,
|
|
|
|
"spotify_artist": spotify_artist,
|
|
|
|
"spotify_album": spotify_album,
|
|
|
|
"spotify_album": spotify_album,
|
|
|
|
"original_search_result": enhanced_context, # Contains corrected data + clean title
|
|
|
|
"original_search_result": enhanced_context, # Contains corrected data + clean title
|
|
|
|
"is_album_download": True
|
|
|
|
"is_album_download": True,
|
|
|
|
|
|
|
|
"is_search_page_matched_download": True # Flag for simple transfer (search page only)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
print(f" + Queued track: {filename} (Matched to: '{corrected_meta.get('title')}')")
|
|
|
|
print(f" + Queued track: {filename} (Matched to: '{corrected_meta.get('title')}')")
|
|
|
|
started_count += 1
|
|
|
|
started_count += 1
|
|
|
|
@ -5520,8 +5561,10 @@ def start_matched_download():
|
|
|
|
"spotify_artist": spotify_artist,
|
|
|
|
"spotify_artist": spotify_artist,
|
|
|
|
"spotify_album": spotify_album, # PRESERVE album context
|
|
|
|
"spotify_album": spotify_album, # PRESERVE album context
|
|
|
|
"original_search_result": enhanced_payload,
|
|
|
|
"original_search_result": enhanced_payload,
|
|
|
|
"is_album_download": False # It's a single track download, not a full album job.
|
|
|
|
"is_album_download": False, # It's a single track download, not a full album job.
|
|
|
|
|
|
|
|
"is_search_page_matched_download": True # Flag for simple transfer (search page only)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info(f"Registered search page matched download for simple transfer: {context_key}")
|
|
|
|
return jsonify({"success": True, "message": "Matched download started"})
|
|
|
|
return jsonify({"success": True, "message": "Matched download started"})
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
return jsonify({"success": False, "error": "Failed to start download via slskd"}), 500
|
|
|
|
return jsonify({"success": False, "error": "Failed to start download via slskd"}), 500
|
|
|
|
@ -7151,6 +7194,9 @@ def _post_process_matched_download(context_key, context, file_path):
|
|
|
|
This is the final, corrected post-processing function. It now mirrors the
|
|
|
|
This is the final, corrected post-processing function. It now mirrors the
|
|
|
|
GUI's logic by trusting the pre-matched context for album downloads, which
|
|
|
|
GUI's logic by trusting the pre-matched context for album downloads, which
|
|
|
|
solves the track numbering issue.
|
|
|
|
solves the track numbering issue.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Also handles simple downloads (from search page "Download" button) which
|
|
|
|
|
|
|
|
just move files to /Transfer without metadata enhancement.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
import os
|
|
|
|
import os
|
|
|
|
@ -7165,6 +7211,78 @@ def _post_process_matched_download(context_key, context, file_path):
|
|
|
|
time.sleep(1)
|
|
|
|
time.sleep(1)
|
|
|
|
# --- END OF FIX ---
|
|
|
|
# --- END OF FIX ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- SIMPLE DOWNLOAD HANDLING ---
|
|
|
|
|
|
|
|
# Check if this is a simple download (search page "Download ⬇" button)
|
|
|
|
|
|
|
|
search_result = context.get('search_result', {})
|
|
|
|
|
|
|
|
is_simple_download = search_result.get('is_simple_download', False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Check if this is a search page matched download (search page "Matched Download 🎯" button)
|
|
|
|
|
|
|
|
is_search_page_matched = context.get('is_search_page_matched_download', False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if is_simple_download or is_search_page_matched:
|
|
|
|
|
|
|
|
# Simple transfer: move to Transfer/AlbumName/ folder, no metadata enhancement
|
|
|
|
|
|
|
|
download_type = "simple download" if is_simple_download else "search page matched download"
|
|
|
|
|
|
|
|
logger.info(f"Processing {download_type} (no metadata enhancement): {file_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
transfer_path = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Extract album name from context
|
|
|
|
|
|
|
|
album_name = "Unknown Album"
|
|
|
|
|
|
|
|
if is_search_page_matched:
|
|
|
|
|
|
|
|
# Matched downloads have Spotify metadata
|
|
|
|
|
|
|
|
spotify_album = context.get('spotify_album')
|
|
|
|
|
|
|
|
if spotify_album and spotify_album.get('name'):
|
|
|
|
|
|
|
|
album_name = spotify_album['name']
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# Fall back to original search result
|
|
|
|
|
|
|
|
original_search = context.get('original_search_result', {})
|
|
|
|
|
|
|
|
album_name = original_search.get('album', 'Unknown Album')
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# Simple downloads - extract from filename path (parent directory)
|
|
|
|
|
|
|
|
original_filename = search_result.get('filename', '')
|
|
|
|
|
|
|
|
if '/' in original_filename or '\\' in original_filename:
|
|
|
|
|
|
|
|
# Get parent directory as album name
|
|
|
|
|
|
|
|
path_parts = original_filename.replace('\\', '/').split('/')
|
|
|
|
|
|
|
|
if len(path_parts) >= 2:
|
|
|
|
|
|
|
|
album_name = path_parts[-2] # Parent directory
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
# No path info, use album from search result if available
|
|
|
|
|
|
|
|
album_name = search_result.get('album', 'Unknown Album')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Sanitize album name for file system
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
album_name = re.sub(r'[<>:"/\\|?*]', '_', album_name).strip()
|
|
|
|
|
|
|
|
if not album_name:
|
|
|
|
|
|
|
|
album_name = "Unknown Album"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Create album folder structure
|
|
|
|
|
|
|
|
album_folder = Path(transfer_path) / album_name
|
|
|
|
|
|
|
|
album_folder.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Move file to album folder
|
|
|
|
|
|
|
|
filename = Path(file_path).name
|
|
|
|
|
|
|
|
destination = album_folder / filename
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shutil.move(str(file_path), str(destination))
|
|
|
|
|
|
|
|
logger.info(f"✅ Moved {download_type} to: {destination}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Clean up context
|
|
|
|
|
|
|
|
with matched_context_lock:
|
|
|
|
|
|
|
|
if context_key in matched_downloads_context:
|
|
|
|
|
|
|
|
del matched_downloads_context[context_key]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Trigger library scan (using correct method name)
|
|
|
|
|
|
|
|
if web_scan_manager:
|
|
|
|
|
|
|
|
threading.Thread(
|
|
|
|
|
|
|
|
target=lambda: web_scan_manager.request_scan(f"{download_type.capitalize()} completed"),
|
|
|
|
|
|
|
|
daemon=True
|
|
|
|
|
|
|
|
).start()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
add_activity_item("✅", "Download Complete", f"{album_name}/{filename}", "Now")
|
|
|
|
|
|
|
|
logger.info(f"✅ {download_type.capitalize()} post-processing complete: {album_name}/{filename}")
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
# --- END SIMPLE DOWNLOAD HANDLING ---
|
|
|
|
|
|
|
|
|
|
|
|
print(f"🎯 Starting robust post-processing for: {context_key}")
|
|
|
|
print(f"🎯 Starting robust post-processing for: {context_key}")
|
|
|
|
|
|
|
|
|
|
|
|
spotify_artist = context.get("spotify_artist")
|
|
|
|
spotify_artist = context.get("spotify_artist")
|
|
|
|
|