better
This commit is contained in:
parent
183a1a623c
commit
3920d7a1c6
4 changed files with 41240 additions and 193 deletions
|
|
@ -1 +1 @@
|
||||||
{"access_token": "BQBenQfvlSw6GKfKdG8opa34ekCbRDYZJdf0hNgUCAJsa9ql6C-7eh6-9cpPOE-ABH-MD6UYS9knFFYs9x-hupI0MYh66PQdTjw_uLJ1lYx43eA4odsccJFTGJTlWaNR0SUC83bWVdzuh-m1glVzDVzJTv6Gap8yUHbA-EwcQjOI7fPA8pgQqyU8RoF06xBsyk9bLqG9g0VnElkk7BlMDvkWxFoIUhiP3Lt2DnC3QeMsT8LYCQvEZWSvqopuUppN", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753299305, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
{"access_token": "BQA5kusm3wT-O4gvbF1tQA8jM0VZuA8joZHW3jBVJVsu8HFwtNZ2BxH3vDuXAtp_W2-M4v71rfRU9Uy7LYMIPPoo-vPddXtmNpiPDwvO4cETXsKnIRzV3vlQyGkMP9J4UQ2E7Z-McaPPtuoKyC7C_rRgzdoAD8IGPHSYdR4ABu3thOmAP_MyEtO2P6W8WArtjXHNRFL4h1UUejx2fQbyMgz56YMiWF2VxfQMS7W1-8Fyq7Yz--cA18E8v3FD_15x", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753302850, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
||||||
41177
logs/app.log
41177
logs/app.log
File diff suppressed because it is too large
Load diff
Binary file not shown.
238
ui/pages/sync.py
238
ui/pages/sync.py
|
|
@ -5152,7 +5152,6 @@ class DownloadMissingTracksModal(QDialog):
|
||||||
|
|
||||||
def on_search_query_completed_parallel(self, results, queries, spotify_track, track_index, table_index, query_index, query, download_index):
|
def on_search_query_completed_parallel(self, results, queries, spotify_track, track_index, table_index, query_index, query, download_index):
|
||||||
"""Handle completion of a parallel search query"""
|
"""Handle completion of a parallel search query"""
|
||||||
# Check for cancellation
|
|
||||||
if hasattr(self, 'cancel_requested') and self.cancel_requested:
|
if hasattr(self, 'cancel_requested') and self.cancel_requested:
|
||||||
print("🛑 Parallel search query completed after cancellation - ignoring results")
|
print("🛑 Parallel search query completed after cancellation - ignoring results")
|
||||||
self.on_parallel_track_completed(download_index, False)
|
self.on_parallel_track_completed(download_index, False)
|
||||||
|
|
@ -5160,43 +5159,33 @@ class DownloadMissingTracksModal(QDialog):
|
||||||
|
|
||||||
print(f"✅ Parallel search query '{query}' returned {len(results)} results for download {download_index + 1}")
|
print(f"✅ Parallel search query '{query}' returned {len(results)} results for download {download_index + 1}")
|
||||||
|
|
||||||
# Update console with result count
|
|
||||||
if hasattr(self.parent_page, 'log_area'):
|
if hasattr(self.parent_page, 'log_area'):
|
||||||
self.parent_page.log_area.append(f" ✅ Found {len(results)} tracks for '{query}' (parallel #{download_index + 1})")
|
self.parent_page.log_area.append(f" ✅ Found {len(results)} tracks for '{query}' (parallel #{download_index + 1})")
|
||||||
|
|
||||||
if results and len(results) > 0:
|
if results and len(results) > 0:
|
||||||
# Debug: Show first result details
|
|
||||||
print(f"🔍 DEBUG: Processing {len(results)} results for parallel download {download_index + 1}")
|
|
||||||
if len(results) > 0:
|
|
||||||
first_result = results[0]
|
|
||||||
print(f"🔍 DEBUG: First result type: {type(first_result)}")
|
|
||||||
print(f"🔍 DEBUG: First result attributes: {[attr for attr in dir(first_result) if not attr.startswith('_')]}")
|
|
||||||
if hasattr(first_result, 'filename'):
|
|
||||||
print(f"🔍 DEBUG: First result filename: '{first_result.filename}'")
|
|
||||||
if hasattr(first_result, 'title'):
|
|
||||||
print(f"🔍 DEBUG: First result title: '{first_result.title}'")
|
|
||||||
|
|
||||||
# Found results - process using existing validation infrastructure
|
|
||||||
valid_candidates = self.get_valid_candidates(results, spotify_track, query)
|
valid_candidates = self.get_valid_candidates(results, spotify_track, query)
|
||||||
|
|
||||||
if valid_candidates:
|
if valid_candidates:
|
||||||
print(f"🎯 Found {len(valid_candidates)} valid candidates for parallel download {download_index + 1}")
|
print(f"🎯 Found {len(valid_candidates)} valid candidates for parallel download {download_index + 1}")
|
||||||
|
|
||||||
# Start validation and download using existing infrastructure
|
# --- NEW CODE: Cache the candidates and the original spotify track ---
|
||||||
|
if hasattr(self, 'parallel_search_tracking') and download_index in self.parallel_search_tracking:
|
||||||
|
self.parallel_search_tracking[download_index]['candidates'] = valid_candidates
|
||||||
|
self.parallel_search_tracking[download_index]['spotify_track'] = spotify_track
|
||||||
|
# --- END OF NEW CODE ---
|
||||||
|
|
||||||
best_match = valid_candidates[0]
|
best_match = valid_candidates[0]
|
||||||
print(f"🎯 Selected best match for parallel download: {best_match.filename}")
|
print(f"🎯 Selected best match for parallel download: {best_match.filename}")
|
||||||
|
|
||||||
# Update console with selection
|
|
||||||
if hasattr(self.parent_page, 'log_area'):
|
if hasattr(self.parent_page, 'log_area'):
|
||||||
self.parent_page.log_area.append(f" 🎯 Best match (parallel #{download_index + 1}): {best_match.filename}")
|
self.parent_page.log_area.append(f" 🎯 Best match (parallel #{download_index + 1}): {best_match.filename}")
|
||||||
|
|
||||||
# Use existing validation system with parallel completion handling
|
|
||||||
self.validate_slskd_result_with_spotify_parallel(best_match, spotify_track, track_index, table_index, valid_candidates, download_index)
|
self.validate_slskd_result_with_spotify_parallel(best_match, spotify_track, track_index, table_index, valid_candidates, download_index)
|
||||||
return
|
return
|
||||||
|
|
||||||
# No results or failed - handle failure
|
|
||||||
self.on_parallel_track_failed(download_index, f"No valid results for query: {query}")
|
self.on_parallel_track_failed(download_index, f"No valid results for query: {query}")
|
||||||
|
|
||||||
|
|
||||||
def validate_slskd_result_with_spotify_parallel(self, slskd_result, original_spotify_track, track_index, table_index, valid_candidates, download_index):
|
def validate_slskd_result_with_spotify_parallel(self, slskd_result, original_spotify_track, track_index, table_index, valid_candidates, download_index):
|
||||||
"""Parallel version of Spotify validation - SIMPLIFIED for speed"""
|
"""Parallel version of Spotify validation - SIMPLIFIED for speed"""
|
||||||
print(f"🎯 Starting parallel validation for download {download_index + 1}")
|
print(f"🎯 Starting parallel validation for download {download_index + 1}")
|
||||||
|
|
@ -5340,8 +5329,9 @@ class DownloadMissingTracksModal(QDialog):
|
||||||
# Remove from active list so we don't check it again
|
# Remove from active list so we don't check it again
|
||||||
self.active_downloads.remove(download_info)
|
self.active_downloads.remove(download_info)
|
||||||
|
|
||||||
# This is the crucial part: call your retry logic
|
# CRITICAL FIX: Do NOT call on_parallel_track_failed here.
|
||||||
self.on_parallel_track_failed(download_index, f"Download {new_status}")
|
# The retry logic now takes responsibility for the download slot.
|
||||||
|
# It will call on_parallel_track_failed itself only if all retries are exhausted.
|
||||||
self.retry_parallel_download_with_fallback(download_index, f"Download {new_status}")
|
self.retry_parallel_download_with_fallback(download_index, f"Download {new_status}")
|
||||||
|
|
||||||
elif new_status == 'completed':
|
elif new_status == 'completed':
|
||||||
|
|
@ -5363,6 +5353,7 @@ class DownloadMissingTracksModal(QDialog):
|
||||||
|
|
||||||
self._is_status_update_running = False
|
self._is_status_update_running = False
|
||||||
|
|
||||||
|
|
||||||
def check_transfer_folder_for_parallel_download(self, download_index):
|
def check_transfer_folder_for_parallel_download(self, download_index):
|
||||||
"""Check if a parallel download completed and exists in Transfer folder"""
|
"""Check if a parallel download completed and exists in Transfer folder"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -5419,189 +5410,68 @@ class DownloadMissingTracksModal(QDialog):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def retry_parallel_download_with_fallback(self, download_index, reason):
|
def retry_parallel_download_with_fallback(self, download_index, reason):
|
||||||
"""Retry a failed parallel download with different source and quality fallback"""
|
"""
|
||||||
|
Retries a failed download by selecting the next-best candidate from the
|
||||||
|
original cached search results.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
print(f"🔄 Retrying parallel download {download_index + 1} - reason: {reason}")
|
print(f"🔄 Retrying parallel download {download_index + 1} from cached results - reason: {reason}")
|
||||||
|
|
||||||
# Get track info
|
|
||||||
if not (hasattr(self, 'parallel_search_tracking') and
|
if not (hasattr(self, 'parallel_search_tracking') and
|
||||||
download_index in self.parallel_search_tracking):
|
download_index in self.parallel_search_tracking):
|
||||||
print(f"❌ No tracking info for retry of download {download_index + 1}")
|
print(f"❌ No tracking info for retry of download {download_index + 1}")
|
||||||
self.on_parallel_track_failed(download_index, f"Retry failed: {reason}")
|
self.on_parallel_track_failed(download_index, f"Retry failed: No tracking info")
|
||||||
return
|
return
|
||||||
|
|
||||||
track_info = self.parallel_search_tracking[download_index]
|
track_info = self.parallel_search_tracking[download_index]
|
||||||
|
candidates = track_info.get('candidates', [])
|
||||||
|
used_sources = track_info.get('used_sources', set())
|
||||||
|
|
||||||
|
# Find the next unused candidate
|
||||||
|
next_candidate = None
|
||||||
|
for candidate in candidates:
|
||||||
|
source_key = f"{getattr(candidate, 'username', getattr(candidate, 'user', 'unknown'))}_{getattr(candidate, 'filename', 'unknown')}"
|
||||||
|
if source_key not in used_sources:
|
||||||
|
next_candidate = candidate
|
||||||
|
break
|
||||||
|
|
||||||
|
if not next_candidate:
|
||||||
|
print(f"❌ No alternative sources left in cache for download {download_index + 1}")
|
||||||
|
self.on_parallel_track_failed(download_index, "No alternative sources in cache")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"🎯 Found next candidate from cache: {next_candidate.filename}")
|
||||||
|
|
||||||
track_index = track_info['track_index']
|
track_index = track_info['track_index']
|
||||||
table_index = track_info['table_index']
|
table_index = track_info['table_index']
|
||||||
spotify_track = track_info['spotify_track']
|
spotify_track = track_info['spotify_track']
|
||||||
|
|
||||||
# Initialize retry tracking if not exists
|
# Update UI to show retrying status
|
||||||
if 'retry_count' not in track_info:
|
retry_count = len(used_sources)
|
||||||
track_info['retry_count'] = 0
|
|
||||||
track_info['used_sources'] = set() # Track used sources to avoid repeats
|
|
||||||
|
|
||||||
track_info['retry_count'] += 1
|
|
||||||
|
|
||||||
# Max 2 retries per track
|
|
||||||
if track_info['retry_count'] > 2:
|
|
||||||
print(f"❌ Max retries exceeded for parallel download {download_index + 1}")
|
|
||||||
self.on_parallel_track_failed(download_index, f"Max retries exceeded after {reason}")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"🔄 Retry attempt {track_info['retry_count']}/2 for: {spotify_track.name}")
|
|
||||||
|
|
||||||
# Update UI to show retrying
|
|
||||||
if table_index is not None and table_index < self.track_table.rowCount():
|
if table_index is not None and table_index < self.track_table.rowCount():
|
||||||
retrying_item = QTableWidgetItem(f"🔄 Retry {track_info['retry_count']}")
|
retrying_item = QTableWidgetItem(f"🔄 Retry {retry_count}")
|
||||||
retrying_item.setFlags(retrying_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
retrying_item.setFlags(retrying_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
||||||
retrying_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
retrying_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.track_table.setItem(table_index, 4, retrying_item)
|
self.track_table.setItem(table_index, 4, retrying_item)
|
||||||
|
|
||||||
# Start retry search with fallback quality preferences
|
# Kick off the download process with the new candidate
|
||||||
self.start_retry_search_worker(download_index, track_index, table_index, spotify_track, track_info)
|
self.on_predownload_validation_completed_parallel(
|
||||||
|
is_valid=True,
|
||||||
|
reason="Retrying with next best cached candidate",
|
||||||
|
slskd_result=next_candidate,
|
||||||
|
original_track=spotify_track,
|
||||||
|
track_index=track_index,
|
||||||
|
table_index=table_index,
|
||||||
|
valid_candidates=candidates,
|
||||||
|
download_index=download_index,
|
||||||
|
spotify_metadata=spotify_track
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error setting up retry for parallel download {download_index + 1}: {str(e)}")
|
print(f"❌ Error during retry from cache for download {download_index + 1}: {str(e)}")
|
||||||
self.on_parallel_track_failed(download_index, f"Retry setup failed: {str(e)}")
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
def start_retry_search_worker(self, download_index, track_index, table_index, spotify_track, track_info):
|
self.on_parallel_track_failed(download_index, f"Retry from cache failed: {str(e)}")
|
||||||
"""Start background worker to search for alternative sources with quality fallback"""
|
|
||||||
from PyQt6.QtCore import QRunnable, QObject, pyqtSignal
|
|
||||||
|
|
||||||
class RetrySearchWorkerSignals(QObject):
|
|
||||||
retry_results_ready = pyqtSignal(int, int, int, list, object) # download_index, track_index, table_index, results, track_info
|
|
||||||
retry_search_failed = pyqtSignal(int, int, int, str, object) # download_index, track_index, table_index, error, track_info
|
|
||||||
|
|
||||||
class RetrySearchWorker(QRunnable):
|
|
||||||
def __init__(self, soulseek_client, spotify_track, track_info):
|
|
||||||
super().__init__()
|
|
||||||
self.soulseek_client = soulseek_client
|
|
||||||
self.spotify_track = spotify_track
|
|
||||||
self.track_info = track_info
|
|
||||||
self.signals = RetrySearchWorkerSignals()
|
|
||||||
|
|
||||||
def clean_track_name_for_retry_search(self, track_name):
|
|
||||||
"""Clean track name for retry searches (same as main cleaning)"""
|
|
||||||
import re
|
|
||||||
|
|
||||||
cleaned_name = track_name
|
|
||||||
|
|
||||||
# Remove content in parentheses - covers feat., remix, remaster, etc.
|
|
||||||
cleaned_name = re.sub(r'\s*\([^)]*\)', '', cleaned_name)
|
|
||||||
|
|
||||||
# Remove content in square brackets
|
|
||||||
cleaned_name = re.sub(r'\s*\[[^\]]*\]', '', cleaned_name)
|
|
||||||
|
|
||||||
# Remove common standalone suffixes after dashes
|
|
||||||
suffixes_to_remove = [
|
|
||||||
r'\s*-\s*remix\s*$', r'\s*-\s*radio\s+edit\s*$', r'\s*-\s*extended\s+version\s*$',
|
|
||||||
r'\s*-\s*remaster\s*$', r'\s*-\s*remastered\s*$', r'\s*-\s*live\s*$',
|
|
||||||
r'\s*-\s*acoustic\s*$', r'\s*-\s*instrumental\s*$'
|
|
||||||
]
|
|
||||||
|
|
||||||
for suffix_pattern in suffixes_to_remove:
|
|
||||||
cleaned_name = re.sub(suffix_pattern, '', cleaned_name, flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
# Clean up extra whitespace
|
|
||||||
cleaned_name = ' '.join(cleaned_name.split())
|
|
||||||
|
|
||||||
# Log retry cleaning if significant changes were made
|
|
||||||
if cleaned_name != track_name:
|
|
||||||
print(f"🧹 Retry cleaned: '{track_name}' → '{cleaned_name}'")
|
|
||||||
|
|
||||||
return cleaned_name
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
print(f"🔍 Retry search for: {self.spotify_track.name} by {self.spotify_track.artists[0] if self.spotify_track.artists else 'Unknown'}")
|
|
||||||
|
|
||||||
# Create search queries with quality fallback preferences
|
|
||||||
raw_track_name = self.spotify_track.name
|
|
||||||
artist_name = self.spotify_track.artists[0] if self.spotify_track.artists else ""
|
|
||||||
|
|
||||||
# Clean track name for better search results
|
|
||||||
track_name = self.clean_track_name_for_retry_search(raw_track_name)
|
|
||||||
|
|
||||||
# Search strategies with quality preferences (try FLAC first, then MP3)
|
|
||||||
search_queries = []
|
|
||||||
|
|
||||||
# For retry attempts, be more flexible with formats
|
|
||||||
if self.track_info['retry_count'] == 1:
|
|
||||||
# First retry: still prefer FLAC but accept MP3
|
|
||||||
search_queries = [
|
|
||||||
f"{track_name} flac",
|
|
||||||
f"{artist_name} {track_name} flac",
|
|
||||||
f"{track_name} mp3",
|
|
||||||
f"{artist_name} {track_name} mp3",
|
|
||||||
f"{track_name}", # Any format
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# Second retry: accept any quality, focus on different sources
|
|
||||||
search_queries = [
|
|
||||||
f"{track_name} mp3",
|
|
||||||
f"{artist_name} {track_name}",
|
|
||||||
f"{track_name}",
|
|
||||||
# Try with fewer keywords if artist name is complex
|
|
||||||
f"{track_name} {artist_name.split()[0]}" if len(artist_name.split()) > 1 else f"{track_name}",
|
|
||||||
]
|
|
||||||
|
|
||||||
all_results = []
|
|
||||||
|
|
||||||
# Search with each query
|
|
||||||
for query in search_queries:
|
|
||||||
if len(all_results) >= 20: # Limit total results
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
print(f" 🔍 Retry query: '{query}'")
|
|
||||||
search_result = self.soulseek_client.search(query, timeout=30)
|
|
||||||
|
|
||||||
if search_result and len(search_result) > 0:
|
|
||||||
# Handle tuple format (tracks, albums)
|
|
||||||
results_list = search_result[0] if isinstance(search_result, tuple) else search_result
|
|
||||||
|
|
||||||
if results_list:
|
|
||||||
# Filter out sources we already tried
|
|
||||||
filtered_results = []
|
|
||||||
for result in results_list[:10]: # Top 10 per query
|
|
||||||
source_key = f"{getattr(result, 'username', getattr(result, 'user', 'unknown'))}_{getattr(result, 'filename', 'unknown')}"
|
|
||||||
if source_key not in self.track_info.get('used_sources', set()):
|
|
||||||
filtered_results.append(result)
|
|
||||||
|
|
||||||
all_results.extend(filtered_results)
|
|
||||||
print(f" ✅ Found {len(filtered_results)} new sources from '{query}'")
|
|
||||||
|
|
||||||
except Exception as search_error:
|
|
||||||
print(f" ❌ Retry search query failed '{query}': {search_error}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if all_results:
|
|
||||||
print(f"🎯 Retry search found {len(all_results)} alternative sources")
|
|
||||||
self.signals.retry_results_ready.emit(download_index, track_index, table_index, all_results, self.track_info)
|
|
||||||
else:
|
|
||||||
print(f"❌ Retry search found no alternative sources")
|
|
||||||
self.signals.retry_search_failed.emit(download_index, track_index, table_index, "No alternative sources found", self.track_info)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Retry search worker error: {str(e)}")
|
|
||||||
self.signals.retry_search_failed.emit(download_index, track_index, table_index, str(e), self.track_info)
|
|
||||||
|
|
||||||
# Create and start worker
|
|
||||||
worker = RetrySearchWorker(self.parent_page.soulseek_client, spotify_track, track_info)
|
|
||||||
worker.signals.retry_results_ready.connect(self.handle_retry_search_results)
|
|
||||||
worker.signals.retry_search_failed.connect(self.handle_retry_search_failed)
|
|
||||||
|
|
||||||
# Store worker reference
|
|
||||||
self.active_workers.append(worker)
|
|
||||||
|
|
||||||
# Submit to thread pool
|
|
||||||
if hasattr(self.parent_page, 'thread_pool'):
|
|
||||||
self.parent_page.thread_pool.start(worker)
|
|
||||||
else:
|
|
||||||
from PyQt6.QtCore import QThreadPool
|
|
||||||
thread_pool = QThreadPool()
|
|
||||||
self.fallback_pools.append(thread_pool)
|
|
||||||
thread_pool.start(worker)
|
|
||||||
|
|
||||||
def handle_retry_search_results(self, download_index, track_index, table_index, results, track_info):
|
def handle_retry_search_results(self, download_index, track_index, table_index, results, track_info):
|
||||||
"""Handle retry search results and start best alternative download"""
|
"""Handle retry search results and start best alternative download"""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue