diff --git a/.spotify_cache b/.spotify_cache index 0ca3eb59..a04fbc00 100644 --- a/.spotify_cache +++ b/.spotify_cache @@ -1 +1 @@ -{"access_token": "BQAx5uqD8QJ7K2LzND9FXXb07OJ9t85iKLs3hzKOzJ0dP4L6xWrX-qdiF5Lh7pAW1GAsYLFxAgzad7Vi0_Db7RQNQOvKydHbFaxQkudzPIsQlDN3tqW9N6NK_5Y0NkkT4uB_LnVgkqxFif0dE6Khw9a0yJBpOwU8caZxf5-gWqXXmc2q4edbAo_IyxgoQ2Qvw5X7ywgQZhxeD3Z-QfbojQEfpEjUrXk71haRs0oLmcxsNi4wMB77iORpAO38zsEg", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753656578, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"} \ No newline at end of file +{"access_token": "BQCi9oKx-56eoC1PB6pL-sW7IS-3RPGj-3ZvpsHmT5ZJXW4Qj16sldlovl05GbrmwSXwr9xnO_T1UxIu-E3mrl9HvtBVCJjZ3Oj7iIZX5mfwKc6Vhx18a0yimYN2mxm1YChHO5Pe8mNyI_ilSYZI1fqg6sNxz_ZvJERSwFGDVIdOxcQDYpTIMQnSDPdK7xs4WpWvbwpWLq9PmhkePeVQtW-UXP_jrjzK5kRWyu-AdnsjtMGFuJaQqv9F4hSJEUK8", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753660130, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"} \ No newline at end of file diff --git a/ui/pages/__pycache__/artists.cpython-312.pyc b/ui/pages/__pycache__/artists.cpython-312.pyc index 8444fa48..c2f2d4bb 100644 Binary files a/ui/pages/__pycache__/artists.cpython-312.pyc and b/ui/pages/__pycache__/artists.cpython-312.pyc differ diff --git a/ui/pages/__pycache__/sync.cpython-312.pyc b/ui/pages/__pycache__/sync.cpython-312.pyc index f5b9fac5..8c37f49b 100644 Binary files a/ui/pages/__pycache__/sync.cpython-312.pyc and b/ui/pages/__pycache__/sync.cpython-312.pyc differ diff --git a/ui/pages/artists.py b/ui/pages/artists.py index bc8b58ee..fad746e0 100644 --- a/ui/pages/artists.py +++ b/ui/pages/artists.py @@ -1953,7 +1953,9 @@ class DownloadMissingAlbumTracksModal(QDialog): download_info['downloading_start_time'] = time.time() # 90-second timeout for being stuck at 0% elif time.time() - download_info['downloading_start_time'] > 90: - print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck at 0%. Retrying.") + print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck at 0%. Cancelling and retrying.") + # Cancel the old download before retry + self.cancel_download_before_retry(download_info) if download_info in self.active_downloads: self.active_downloads.remove(download_info) self.retry_parallel_download_with_fallback(download_info) @@ -1968,13 +1970,50 @@ class DownloadMissingAlbumTracksModal(QDialog): if 'queued_start_time' not in download_info: download_info['queued_start_time'] = time.time() elif time.time() - download_info['queued_start_time'] > 90: # 90-second timeout - print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck in queue. Retrying.") + print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck in queue. Cancelling and retrying.") + # Cancel the old download before retry + self.cancel_download_before_retry(download_info) if download_info in self.active_downloads: self.active_downloads.remove(download_info) self.retry_parallel_download_with_fallback(download_info) self._is_status_update_running = False + def cancel_download_before_retry(self, download_info): + """Cancel the current download before retrying with alternative source""" + try: + slskd_result = download_info.get('slskd_result') + if not slskd_result: + print("⚠️ No slskd_result found in download_info for cancellation") + return + + # Extract download details for cancellation + download_id = download_info.get('download_id') + username = getattr(slskd_result, 'username', None) + + if download_id and username: + print(f"🚫 Cancelling timed-out album download: {download_id} from {username}") + + # Use asyncio to call the async cancel method + import asyncio + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + success = loop.run_until_complete( + self.soulseek_client.cancel_download(download_id, username, remove=False) + ) + if success: + print(f"✅ Successfully cancelled album download {download_id}") + else: + print(f"⚠️ Failed to cancel album download {download_id}") + finally: + loop.close() + else: + print(f"⚠️ Missing download_id ({download_id}) or username ({username}) for album cancellation") + + except Exception as e: + print(f"❌ Error cancelling album download: {e}") + def retry_parallel_download_with_fallback(self, failed_download_info): """Retries a failed download by selecting the next-best cached candidate""" download_index = failed_download_info['download_index'] diff --git a/ui/pages/sync.py b/ui/pages/sync.py index 1c970d9c..c8fdb646 100644 --- a/ui/pages/sync.py +++ b/ui/pages/sync.py @@ -4190,7 +4190,9 @@ class DownloadMissingTracksModal(QDialog): download_info['downloading_start_time'] = time.time() # 90-second timeout for being stuck at 0% elif time.time() - download_info['downloading_start_time'] > 90: - print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck at 0%. Retrying.") + print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck at 0%. Cancelling and retrying.") + # Cancel the old download before retry + self.cancel_download_before_retry(download_info) if download_info in self.active_downloads: self.active_downloads.remove(download_info) self.retry_parallel_download_with_fallback(download_info) @@ -4206,13 +4208,49 @@ class DownloadMissingTracksModal(QDialog): if 'queued_start_time' not in download_info: download_info['queued_start_time'] = time.time() elif time.time() - download_info['queued_start_time'] > 90: # 90-second timeout - print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck in queue. Retrying.") + print(f"⚠️ Download for '{download_info['slskd_result'].filename}' is stuck in queue. Cancelling and retrying.") + # Cancel the old download before retry + self.cancel_download_before_retry(download_info) if download_info in self.active_downloads: self.active_downloads.remove(download_info) self.retry_parallel_download_with_fallback(download_info) self._is_status_update_running = False + def cancel_download_before_retry(self, download_info): + """Cancel the current download before retrying with alternative source""" + try: + slskd_result = download_info.get('slskd_result') + if not slskd_result: + print("⚠️ No slskd_result found in download_info for cancellation") + return + + # Extract download details for cancellation + download_id = download_info.get('download_id') + username = getattr(slskd_result, 'username', None) + + if download_id and username: + print(f"🚫 Cancelling timed-out download: {download_id} from {username}") + + # Use asyncio to call the async cancel method + import asyncio + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + success = loop.run_until_complete( + self.soulseek_client.cancel_download(download_id, username, remove=False) + ) + if success: + print(f"✅ Successfully cancelled download {download_id}") + else: + print(f"⚠️ Failed to cancel download {download_id}") + finally: + loop.close() + else: + print(f"⚠️ Missing download_id ({download_id}) or username ({username}) for cancellation") + + except Exception as e: + print(f"❌ Error cancelling download: {e}") def retry_parallel_download_with_fallback(self, failed_download_info): """Retries a failed download by selecting the next-best cached candidate."""