From 3a70f0453c11269565be7eabd24f7456d87a2743 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Mon, 4 May 2026 15:21:32 -0700 Subject: [PATCH] G: Wire YouTube progress hook to engine + drop dead threading imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- core/soundcloud_client.py | 1 - core/tidal_download_client.py | 1 - core/youtube_client.py | 63 +++++++++++++++-------------------- 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/core/soundcloud_client.py b/core/soundcloud_client.py index d1fb571a..c0362859 100644 --- a/core/soundcloud_client.py +++ b/core/soundcloud_client.py @@ -27,7 +27,6 @@ import re import asyncio import uuid import time -import threading from typing import List, Optional, Dict, Any, Tuple, Callable from pathlib import Path diff --git a/core/tidal_download_client.py b/core/tidal_download_client.py index 3a5649ed..725a6bee 100644 --- a/core/tidal_download_client.py +++ b/core/tidal_download_client.py @@ -14,7 +14,6 @@ import re import asyncio import uuid import time -import threading import shutil import subprocess from typing import List, Optional, Dict, Any, Tuple diff --git a/core/youtube_client.py b/core/youtube_client.py index 686c40f8..3bf0bbe7 100644 --- a/core/youtube_client.py +++ b/core/youtube_client.py @@ -17,7 +17,6 @@ import time import platform import asyncio import uuid -import threading from typing import List, Optional, Dict, Any, Tuple from dataclasses import dataclass from pathlib import Path @@ -296,15 +295,14 @@ class YouTubeClient: self.progress_callback = callback def _progress_hook(self, d): - """ - yt-dlp progress hook - called during download to report progress. - Updates the active_downloads dictionary for the current download. - Mirrors Soulseek's transfer status updates. - """ + """yt-dlp progress hook — called during download to report + progress. Writes to the engine record (Phase C2 lifted state + out of the per-client dict; this hook follows suit).""" try: - # Only update if we have a current download ID if not self.current_download_id: return + if self._engine is None: + return status = d.get('status', 'unknown') @@ -313,24 +311,18 @@ class YouTubeClient: total = d.get('total_bytes') or d.get('total_bytes_estimate', 0) speed = d.get('speed', 0) or 0 eta = d.get('eta', 0) or 0 + percent = (downloaded / total) * 100 if total > 0 else 0 - if total > 0: - percent = (downloaded / total) * 100 - else: - percent = 0 + self._engine.update_record('youtube', self.current_download_id, { + 'state': 'InProgress, Downloading', + 'progress': round(percent, 1), + '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) - 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 + # Legacy progress dict for any external listeners. self.current_download_progress = { 'status': 'downloading', 'percent': round(percent, 1), @@ -340,30 +332,27 @@ class YouTubeClient: 'eta': int(eta), 'filename': d.get('filename', '') } - - # Call progress callback if set (for UI updates) if self.progress_callback: self.progress_callback(self.current_download_progress) elif status == 'finished': - # Download finished, ffmpeg is converting to MP3 - # Keep state as 'InProgress, Downloading' - the download thread will set final state - with self._download_lock: - if self.current_download_id in self.active_downloads: - self.active_downloads[self.current_download_id]['progress'] = 95.0 # Almost done (converting) - + # Download finished — ffmpeg now converts to MP3. The + # engine.worker thread flips to 'Completed, Succeeded' + # once _download_sync returns; this just bumps progress + # to 95% so the UI doesn't sit at 99.9% during the + # 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['percent'] = 95.0 - if self.progress_callback: self.progress_callback(self.current_download_progress) elif status == 'error': - # Mark as error (thread-safe) - with self._download_lock: - if self.current_download_id in self.active_downloads: - self.active_downloads[self.current_download_id]['state'] = 'Errored' - + self._engine.update_record('youtube', self.current_download_id, { + 'state': 'Errored', + }) self.current_download_progress['status'] = 'error' if self.progress_callback: self.progress_callback(self.current_download_progress)