G: Wire YouTube progress hook to engine + drop dead threading imports

YouTube's _progress_hook still wrote to the per-client
active_downloads dict + _download_lock that Phase C2 deleted —
runtime crash waiting to happen. Rewritten to use
engine.update_record. Same state-dict shape, same UI semantics
(95% during ffmpeg postprocess, 'Errored' on yt-dlp error,
'InProgress, Downloading' during stream).

Drop unused `import threading` from youtube/tidal/soundcloud
clients (no longer spawn threads — engine.worker owns that).
Qobuz/HiFi/Deezer keep their threading import for module-level
or per-instance API locks (separate from download threading).

Suite still green (2050 passed).
This commit is contained in:
Broque Thomas 2026-05-04 15:21:32 -07:00
parent 4b4076b57f
commit 3a70f0453c
3 changed files with 26 additions and 39 deletions

View file

@ -27,7 +27,6 @@ import re
import asyncio import asyncio
import uuid import uuid
import time import time
import threading
from typing import List, Optional, Dict, Any, Tuple, Callable from typing import List, Optional, Dict, Any, Tuple, Callable
from pathlib import Path from pathlib import Path

View file

@ -14,7 +14,6 @@ import re
import asyncio import asyncio
import uuid import uuid
import time import time
import threading
import shutil import shutil
import subprocess import subprocess
from typing import List, Optional, Dict, Any, Tuple from typing import List, Optional, Dict, Any, Tuple

View file

@ -17,7 +17,6 @@ import time
import platform import platform
import asyncio import asyncio
import uuid import uuid
import threading
from typing import List, Optional, Dict, Any, Tuple from typing import List, Optional, Dict, Any, Tuple
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
@ -296,15 +295,14 @@ class YouTubeClient:
self.progress_callback = callback self.progress_callback = callback
def _progress_hook(self, d): def _progress_hook(self, d):
""" """yt-dlp progress hook — called during download to report
yt-dlp progress hook - called during download to report progress. progress. Writes to the engine record (Phase C2 lifted state
Updates the active_downloads dictionary for the current download. out of the per-client dict; this hook follows suit)."""
Mirrors Soulseek's transfer status updates.
"""
try: try:
# Only update if we have a current download ID
if not self.current_download_id: if not self.current_download_id:
return return
if self._engine is None:
return
status = d.get('status', 'unknown') status = d.get('status', 'unknown')
@ -313,24 +311,18 @@ class YouTubeClient:
total = d.get('total_bytes') or d.get('total_bytes_estimate', 0) total = d.get('total_bytes') or d.get('total_bytes_estimate', 0)
speed = d.get('speed', 0) or 0 speed = d.get('speed', 0) or 0
eta = d.get('eta', 0) or 0 eta = d.get('eta', 0) or 0
percent = (downloaded / total) * 100 if total > 0 else 0
if total > 0: self._engine.update_record('youtube', self.current_download_id, {
percent = (downloaded / total) * 100 'state': 'InProgress, Downloading',
else: 'progress': round(percent, 1),
percent = 0 'transferred': downloaded,
'size': total,
'speed': int(speed),
'time_remaining': int(eta) if eta > 0 else None,
})
# Update active downloads dictionary (thread-safe update with lock) # Legacy progress dict for any external listeners.
with self._download_lock:
if self.current_download_id in self.active_downloads:
download_info = self.active_downloads[self.current_download_id]
download_info['state'] = 'InProgress, Downloading' # Match Soulseek state format
download_info['progress'] = round(percent, 1)
download_info['transferred'] = downloaded
download_info['size'] = total
download_info['speed'] = int(speed)
download_info['time_remaining'] = int(eta) if eta > 0 else None
# Also update current_download_progress for legacy compatibility
self.current_download_progress = { self.current_download_progress = {
'status': 'downloading', 'status': 'downloading',
'percent': round(percent, 1), 'percent': round(percent, 1),
@ -340,30 +332,27 @@ class YouTubeClient:
'eta': int(eta), 'eta': int(eta),
'filename': d.get('filename', '') 'filename': d.get('filename', '')
} }
# Call progress callback if set (for UI updates)
if self.progress_callback: if self.progress_callback:
self.progress_callback(self.current_download_progress) self.progress_callback(self.current_download_progress)
elif status == 'finished': elif status == 'finished':
# Download finished, ffmpeg is converting to MP3 # Download finished — ffmpeg now converts to MP3. The
# Keep state as 'InProgress, Downloading' - the download thread will set final state # engine.worker thread flips to 'Completed, Succeeded'
with self._download_lock: # once _download_sync returns; this just bumps progress
if self.current_download_id in self.active_downloads: # to 95% so the UI doesn't sit at 99.9% during the
self.active_downloads[self.current_download_id]['progress'] = 95.0 # Almost done (converting) # ffmpeg post-process.
self._engine.update_record('youtube', self.current_download_id, {
'progress': 95.0,
})
self.current_download_progress['status'] = 'postprocessing' self.current_download_progress['status'] = 'postprocessing'
self.current_download_progress['percent'] = 95.0 self.current_download_progress['percent'] = 95.0
if self.progress_callback: if self.progress_callback:
self.progress_callback(self.current_download_progress) self.progress_callback(self.current_download_progress)
elif status == 'error': elif status == 'error':
# Mark as error (thread-safe) self._engine.update_record('youtube', self.current_download_id, {
with self._download_lock: 'state': 'Errored',
if self.current_download_id in self.active_downloads: })
self.active_downloads[self.current_download_id]['state'] = 'Errored'
self.current_download_progress['status'] = 'error' self.current_download_progress['status'] = 'error'
if self.progress_callback: if self.progress_callback:
self.progress_callback(self.current_download_progress) self.progress_callback(self.current_download_progress)