better
This commit is contained in:
parent
21967113c6
commit
ffde9d07d8
4 changed files with 7078 additions and 40 deletions
|
|
@ -1 +1 @@
|
||||||
{"access_token": "BQB0uHOE6mtOpZRHpDmzgRLknfFzTQgH6w2HgrvJjmXydJBCG6UuiLswSY0PH_MkLm_2AlQIk-5YmvnOUZbuuHjRH-kzyJ2Rbh5MkTs4B0WXXg_Chvx3ijW-LTopxle6Vik4fnf6BHGfK71aXx68Gyq2vUo3KaE1-HMtfdiJPGebEDXnMUh-Jbpx2_1yuGQNB5qQLFVnp1q2m6jEtNWC1ZP-VJeDT4Fwt2-N13EFvQc9cm38SzeJSgqyfTOWGuFT", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752706612, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
{"access_token": "BQDMFbkA-dtq-QJ-vhjYwLpCBIZLIVCLyfkQCuRe2p3O4NVfD_nnJs9zkeCIRz91Dxi8InIERq-ZYki_aOAsbkBSF4_yUjtgjL4ygY14UbvHicIhtMwd33zP8Zj0ekdyK60mOIoOrAR4DHUZJW8B25GD4B6SgLTipTId_HzhwJATffuuQwCkz_8q8NN40CL3qXHuM9wKAtluf75GjovVTeSg_hHVWrP2mWQZPCsrpE5baIjK2Fkes3k1gPi0hd_d", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752710282, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
|
||||||
6944
logs/app.log
6944
logs/app.log
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
|
@ -4762,7 +4762,6 @@ class TabbedDownloadManager(QTabWidget):
|
||||||
|
|
||||||
if parent_widget and hasattr(parent_widget, 'update_download_manager_stats'):
|
if parent_widget and hasattr(parent_widget, 'update_download_manager_stats'):
|
||||||
parent_widget.update_download_manager_stats(active_count, finished_count)
|
parent_widget.update_download_manager_stats(active_count, finished_count)
|
||||||
print(f"[DEBUG] Batched update: download manager stats: Active={active_count}, Finished={finished_count}")
|
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG] Could not find parent with update_download_manager_stats method")
|
print(f"[DEBUG] Could not find parent with update_download_manager_stats method")
|
||||||
|
|
||||||
|
|
@ -8311,7 +8310,6 @@ class DownloadsPage(QWidget):
|
||||||
# Match by track title and artist
|
# Match by track title and artist
|
||||||
if (track_title == download_item.title and track_artist == download_item.artist):
|
if (track_title == download_item.title and track_artist == download_item.artist):
|
||||||
|
|
||||||
print(f"[DEBUG] ✅ MATCH FOUND! Updating button state for '{track_title}' to '{status}'")
|
|
||||||
|
|
||||||
# Update button state based on download status
|
# Update button state based on download status
|
||||||
if status == 'downloading':
|
if status == 'downloading':
|
||||||
|
|
@ -8329,7 +8327,6 @@ class DownloadsPage(QWidget):
|
||||||
else:
|
else:
|
||||||
print(f"[DEBUG] ⚠️ Unknown status '{status}' - no button update performed")
|
print(f"[DEBUG] ⚠️ Unknown status '{status}' - no button update performed")
|
||||||
|
|
||||||
print(f"[DEBUG] ✅ Successfully updated track button state for '{download_item.title}': {status}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9394,6 +9391,10 @@ class DownloadsPage(QWidget):
|
||||||
if new_status in ['cancelled', 'failed'] and hasattr(download_item, 'download_id'):
|
if new_status in ['cancelled', 'failed'] and hasattr(download_item, 'download_id'):
|
||||||
self._schedule_api_cleanup_v2(download_item)
|
self._schedule_api_cleanup_v2(download_item)
|
||||||
|
|
||||||
|
# Schedule fallback cleanup check for errored downloads
|
||||||
|
if new_status == 'failed':
|
||||||
|
self._schedule_fallback_cleanup_check()
|
||||||
|
|
||||||
def _handle_missing_transfer_v2(self, download_item):
|
def _handle_missing_transfer_v2(self, download_item):
|
||||||
"""Handle downloads missing from API with improved grace period"""
|
"""Handle downloads missing from API with improved grace period"""
|
||||||
if not hasattr(download_item, 'api_missing_count_v2'):
|
if not hasattr(download_item, 'api_missing_count_v2'):
|
||||||
|
|
@ -9407,26 +9408,110 @@ class DownloadsPage(QWidget):
|
||||||
self.download_queue.move_to_finished(download_item)
|
self.download_queue.move_to_finished(download_item)
|
||||||
|
|
||||||
def _schedule_api_cleanup_v2(self, download_item):
|
def _schedule_api_cleanup_v2(self, download_item):
|
||||||
"""Schedule API cleanup in background"""
|
"""Schedule API cleanup with robust retry mechanism"""
|
||||||
def cleanup_task():
|
def cleanup_task_with_retry():
|
||||||
try:
|
import time
|
||||||
if hasattr(download_item, 'download_id') and download_item.soulseek_client:
|
import asyncio
|
||||||
import asyncio
|
|
||||||
|
max_retries = 3
|
||||||
|
retry_delays = [2, 5, 10] # Progressive delays in seconds
|
||||||
|
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
if not (hasattr(download_item, 'download_id') and download_item.soulseek_client):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Wait before cleanup to let API stabilize
|
||||||
|
if attempt == 0:
|
||||||
|
time.sleep(1) # Initial delay for API stabilization
|
||||||
|
else:
|
||||||
|
time.sleep(retry_delays[attempt - 1])
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
success = loop.run_until_complete(
|
|
||||||
download_item.soulseek_client.cancel_download(
|
|
||||||
download_item.download_id,
|
|
||||||
getattr(download_item, 'username', ''),
|
|
||||||
remove=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
loop.close()
|
|
||||||
pass # Silent cleanup success
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ API cleanup failed: {e}")
|
|
||||||
|
|
||||||
self._optimized_api_pool.submit(cleanup_task)
|
try:
|
||||||
|
success = loop.run_until_complete(
|
||||||
|
download_item.soulseek_client.cancel_download(
|
||||||
|
download_item.download_id,
|
||||||
|
getattr(download_item, 'username', ''),
|
||||||
|
remove=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return # Success, exit retry loop
|
||||||
|
else:
|
||||||
|
if attempt == max_retries - 1:
|
||||||
|
print(f"❌ API cleanup failed after {max_retries} attempts: {download_item.title}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if attempt == max_retries - 1:
|
||||||
|
print(f"❌ API cleanup failed after {max_retries} attempts: {e}")
|
||||||
|
# Continue to next retry
|
||||||
|
|
||||||
|
self._optimized_api_pool.submit(cleanup_task_with_retry)
|
||||||
|
|
||||||
|
def _schedule_fallback_cleanup_check(self):
|
||||||
|
"""Schedule a fallback cleanup check for persistent errored entries"""
|
||||||
|
def fallback_cleanup_task():
|
||||||
|
import time
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
# Wait longer before fallback cleanup
|
||||||
|
time.sleep(30) # 30 second delay
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not self.soulseek_client:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get current transfers to find persistent errored entries
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
try:
|
||||||
|
transfers_data = loop.run_until_complete(
|
||||||
|
self.soulseek_client.get_all_downloads()
|
||||||
|
)
|
||||||
|
|
||||||
|
if transfers_data:
|
||||||
|
# Find errored entries that should have been cleaned up
|
||||||
|
persistent_errors = []
|
||||||
|
for user_data in transfers_data:
|
||||||
|
if 'directories' in user_data:
|
||||||
|
for directory in user_data['directories']:
|
||||||
|
if 'files' in directory:
|
||||||
|
for transfer in directory['files']:
|
||||||
|
state = transfer.get('state', '')
|
||||||
|
if any(error_state in state for error_state in ['Failed', 'Errored', 'Cancelled']):
|
||||||
|
persistent_errors.append(transfer)
|
||||||
|
|
||||||
|
# Clean up persistent errors (max 5 at a time to be conservative)
|
||||||
|
for transfer in persistent_errors[:5]:
|
||||||
|
try:
|
||||||
|
success = loop.run_until_complete(
|
||||||
|
self.soulseek_client.cancel_download(
|
||||||
|
transfer.get('id'),
|
||||||
|
transfer.get('username', ''),
|
||||||
|
remove=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
print(f"✅ Fallback cleanup successful for persistent error")
|
||||||
|
except Exception as e:
|
||||||
|
pass # Silent fallback failures
|
||||||
|
|
||||||
|
finally:
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
pass # Silent fallback failures
|
||||||
|
|
||||||
|
# Schedule fallback cleanup
|
||||||
|
self._optimized_api_pool.submit(fallback_cleanup_task)
|
||||||
|
|
||||||
def _cleanup_status_thread_v2(self, thread):
|
def _cleanup_status_thread_v2(self, thread):
|
||||||
"""Clean up status threads"""
|
"""Clean up status threads"""
|
||||||
|
|
@ -9486,7 +9571,6 @@ class DownloadsPage(QWidget):
|
||||||
if 'files' in directory:
|
if 'files' in directory:
|
||||||
all_transfers.extend(directory['files'])
|
all_transfers.extend(directory['files'])
|
||||||
|
|
||||||
print(f"[DEBUG] Processing {len(all_transfers)} active transfers from API")
|
|
||||||
|
|
||||||
# Track which transfers have been matched to prevent duplicate assignments
|
# Track which transfers have been matched to prevent duplicate assignments
|
||||||
matched_transfer_ids = set()
|
matched_transfer_ids = set()
|
||||||
|
|
@ -9496,7 +9580,6 @@ class DownloadsPage(QWidget):
|
||||||
if download_item.status.lower() in ['completed', 'finished', 'cancelled', 'failed']:
|
if download_item.status.lower() in ['completed', 'finished', 'cancelled', 'failed']:
|
||||||
continue # Skip completed items
|
continue # Skip completed items
|
||||||
|
|
||||||
print(f"[DEBUG] Looking for matches for download: '{download_item.title}' by '{download_item.artist}' (download_id: {getattr(download_item, 'download_id', 'None')})")
|
|
||||||
|
|
||||||
# Try to match by download_id first (most reliable)
|
# Try to match by download_id first (most reliable)
|
||||||
matching_transfer = None
|
matching_transfer = None
|
||||||
|
|
@ -9749,33 +9832,44 @@ class DownloadsPage(QWidget):
|
||||||
file_path=download_item.file_path
|
file_path=download_item.file_path
|
||||||
)
|
)
|
||||||
|
|
||||||
# IMMEDIATE CLEANUP: Remove errored download from API immediately
|
# IMPROVED CLEANUP: Remove errored download with retry mechanism
|
||||||
if download_item.download_id and download_item.username and download_item.soulseek_client:
|
if download_item.download_id and download_item.username and download_item.soulseek_client:
|
||||||
def cleanup_errored_download():
|
def cleanup_errored_download_with_retry():
|
||||||
try:
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
loop = asyncio.new_event_loop()
|
max_retries = 3
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
success = loop.run_until_complete(
|
|
||||||
download_item.soulseek_client.cancel_download(
|
|
||||||
download_item.download_id,
|
|
||||||
download_item.username,
|
|
||||||
remove=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
if success:
|
for attempt in range(max_retries):
|
||||||
print(f"✓ Immediately cleaned up errored download {download_item.download_id}")
|
try:
|
||||||
else:
|
# Add delay for API stabilization
|
||||||
print(f"⚠️ Failed to clean up errored download {download_item.download_id}")
|
if attempt == 0:
|
||||||
except Exception as e:
|
time.sleep(2) # Initial delay for errored downloads
|
||||||
print(f"⚠️ Error cleaning up errored download: {e}")
|
else:
|
||||||
|
time.sleep(5 * attempt) # Progressive delay
|
||||||
|
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
success = loop.run_until_complete(
|
||||||
|
download_item.soulseek_client.cancel_download(
|
||||||
|
download_item.download_id,
|
||||||
|
download_item.username,
|
||||||
|
remove=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return # Success, exit retry loop
|
||||||
|
elif attempt == max_retries - 1:
|
||||||
|
print(f"❌ Failed to clean up errored download after {max_retries} attempts: {download_item.title}")
|
||||||
|
except Exception as e:
|
||||||
|
if attempt == max_retries - 1:
|
||||||
|
print(f"❌ Error cleaning up errored download after {max_retries} attempts: {e}")
|
||||||
|
|
||||||
# Run cleanup in background thread
|
# Run cleanup in background thread
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
with ThreadPoolExecutor(max_workers=1) as executor:
|
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||||
executor.submit(cleanup_errored_download)
|
executor.submit(cleanup_errored_download_with_retry)
|
||||||
|
|
||||||
print(f"[DEBUG] Moving failed download '{download_item.title}' to finished queue")
|
print(f"[DEBUG] Moving failed download '{download_item.title}' to finished queue")
|
||||||
self.download_queue.move_to_finished(download_item)
|
self.download_queue.move_to_finished(download_item)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue