diff --git a/web_server.py b/web_server.py index bcc4640f..49091a96 100644 --- a/web_server.py +++ b/web_server.py @@ -312,7 +312,14 @@ class WebUIDownloadMonitor: return False else: queue_time = current_time - task['queued_start_time'] - if queue_time > 120: # Increased timeout to reduce retry spam + + # Use context-aware timeouts like GUI: + # - 15 seconds for artist album downloads (streaming context) + # - 90 seconds for background playlist downloads + is_streaming_context = task.get('track_info', {}).get('is_album_download', False) + timeout_threshold = 15.0 if is_streaming_context else 90.0 + + if queue_time > timeout_threshold: # Track retry attempts to prevent rapid loops retry_count = task.get('stuck_retry_count', 0) last_retry = task.get('last_retry_time', 0) @@ -343,7 +350,14 @@ class WebUIDownloadMonitor: return False else: download_time = current_time - task['downloading_start_time'] - if download_time > 120: # Increased timeout to reduce retry spam + + # Use context-aware timeouts like GUI: + # - 15 seconds for artist album downloads (streaming context) + # - 90 seconds for background playlist downloads + is_streaming_context = task.get('track_info', {}).get('is_album_download', False) + timeout_threshold = 15.0 if is_streaming_context else 90.0 + + if download_time > timeout_threshold: retry_count = task.get('stuck_retry_count', 0) last_retry = task.get('last_retry_time', 0) @@ -408,21 +422,36 @@ class WebUIDownloadMonitor: # Only attempt cancellation if we have what looks like a proper download ID # (not a filename fallback which would be much longer) - if download_id and username and isinstance(download_id, str) and len(download_id) < 100: - print(f"🚫 Attempting to cancel stuck download: {os.path.basename(str(download_id))} from {username} (task: {task_id[:8]}...)") - try: - success = asyncio.run(soulseek_client.cancel_download(download_id, username, remove=False)) - if success: - print(f"✅ Successfully cancelled download {str(download_id)[:8]}... from {username}") - # Clear any stored download info to prevent status conflicts - task.pop('soulseek_download_id', None) - task.pop('soulseek_username', None) + # Handle cancellation attempts based on download ID type + if download_id and username and isinstance(download_id, str): + # Check if this is a synthetic download ID (generated by web server) + if download_id.startswith('web_dl_'): + print(f"⚠️ Skipping cancellation for synthetic download ID {download_id} - cannot cancel via slskd API") + # Use original download ID for potential cancellation if available + original_id = task.get('original_download_id') + if original_id and len(str(original_id)) > 100: + # This is still a filename, can't cancel + print(f"⚠️ Original download ID is filename, cannot cancel: {str(original_id)[:50]}...") else: - print(f"⚠️ Cancel request failed for {str(download_id)[:8]}... - all API endpoints returned errors, proceeding with retry anyway") - except Exception as e: - print(f"⚠️ Cancel exception for {str(download_id)[:8]}...: {str(e)[:150]}, proceeding with retry anyway") + print(f"⚠️ No usable original download ID for cancellation") + elif len(download_id) < 100: + # This looks like a proper slskd download ID, try to cancel it + print(f"🚫 Attempting to cancel stuck download: {download_id[:8]}... from {username} (task: {task_id[:8]}...)") + try: + success = asyncio.run(soulseek_client.cancel_download(download_id, username, remove=False)) + if success: + print(f"✅ Successfully cancelled download {download_id[:8]}... from {username}") + # Clear any stored download info to prevent status conflicts + task.pop('soulseek_download_id', None) + task.pop('soulseek_username', None) + else: + print(f"⚠️ Cancel request failed for {download_id[:8]}... - API error, proceeding with retry anyway") + except Exception as e: + print(f"⚠️ Cancel exception for {download_id[:8]}...: {str(e)[:100]}, proceeding with retry anyway") + else: + print(f"⚠️ Download ID too long ({len(download_id)} chars) - likely filename, cannot cancel") else: - print(f"⚠️ Skipping cancellation - invalid data (ID len={len(str(download_id)) if download_id else 0}, username='{username}'), proceeding with retry") + print(f"⚠️ Missing download ID or username for cancellation, proceeding with retry") except Exception as e: print(f"⚠️ Error in cancellation logic, proceeding with retry: {e}") @@ -5437,7 +5466,18 @@ def _attempt_download_with_candidates(task_id, candidates, track, batch_id=None) _on_download_completed(batch_id, task_id, success=False) return False - download_tasks[task_id]['download_id'] = download_id + # Store download information with proper ID handling + # If download_id is actually a filename (long path), generate a proper ID + if download_id and len(str(download_id)) > 100: + # This is likely a filename fallback, generate a proper download ID + import uuid + actual_download_id = f"web_dl_{uuid.uuid4().hex[:8]}" + print(f"⚠️ Generated synthetic download ID {actual_download_id} for filename fallback: {str(download_id)[:50]}...") + download_tasks[task_id]['download_id'] = actual_download_id + download_tasks[task_id]['original_download_id'] = download_id # Keep original for reference + else: + download_tasks[task_id]['download_id'] = download_id + download_tasks[task_id]['username'] = username download_tasks[task_id]['filename'] = filename