From b76ffbf656e1ccb02eb22a5a1ea85d9b7de7bfa1 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sat, 1 Nov 2025 16:49:12 +0100 Subject: [PATCH] fixed percentage and mb/s view --- data/download_queue.json | 21 +------- src/core/SeriesApp.py | 50 ++++++++++++++++--- tests/unit/test_download_service.py | 76 +++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 28 deletions(-) diff --git a/data/download_queue.json b/data/download_queue.json index 574ceac..a016e93 100644 --- a/data/download_queue.json +++ b/data/download_queue.json @@ -1,24 +1,5 @@ { "pending": [ - { - "id": "54533241-ed24-482f-85d7-c9218352ae7f", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 4, - "episode": 7, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882358Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, { "id": "7e80b3c4-6837-4af7-bea3-ca037df594ce", "serie_id": "highschool-dxd", @@ -1485,5 +1466,5 @@ ], "active": [], "failed": [], - "timestamp": "2025-11-01T15:42:52.117823+00:00" + "timestamp": "2025-11-01T15:48:49.007428+00:00" } \ No newline at end of file diff --git a/src/core/SeriesApp.py b/src/core/SeriesApp.py index b1bdaf6..a9c0fb9 100644 --- a/src/core/SeriesApp.py +++ b/src/core/SeriesApp.py @@ -253,15 +253,48 @@ class SeriesApp: if isinstance(progress_info, dict): # Calculate percentage based on downloaded/total bytes downloaded = progress_info.get('downloaded_bytes', 0) - total = progress_info.get('total_bytes') or progress_info.get('total_bytes_estimate', 0) + total_bytes = ( + progress_info.get('total_bytes') + or progress_info.get('total_bytes_estimate', 0) + ) - if total > 0: - progress = (downloaded / total) * 100 + if total_bytes > 0: + progress = (downloaded / total_bytes) * 100 else: progress = 0 + + # Extract speed and ETA from yt-dlp progress dict + speed = progress_info.get('speed', 0) # bytes/sec + eta = progress_info.get('eta') # seconds + + # Convert to expected format for web API callback + # Web API expects: percent, downloaded_mb, total_mb, + # speed_mbps, eta_seconds + web_progress_dict = { + 'percent': progress, + # Convert bytes to MB + 'downloaded_mb': downloaded / (1024 * 1024), + 'total_mb': ( + total_bytes / (1024 * 1024) + if total_bytes > 0 + else None + ), + # Convert bytes/sec to MB/sec + 'speed_mbps': ( + speed / (1024 * 1024) if speed else None + ), + 'eta_seconds': eta, + } else: # Fallback for old-style float progress progress = float(progress_info) + web_progress_dict = { + 'percent': progress, + 'downloaded_mb': 0.0, + 'total_mb': None, + 'speed_mbps': None, + 'eta_seconds': None, + } # Notify progress via new callback system self._callback_manager.notify_progress( @@ -281,13 +314,14 @@ class SeriesApp: ) ) - # Call legacy callback if provided + # Call callback with web API format + # (dict with detailed progress info) if callback: - callback(progress) + callback(web_progress_dict) - # Propagate progress into the legacy callback chain so existing - # UI surfaces continue to receive updates without rewriting the - # old interfaces. + # Propagate progress into the legacy callback chain so + # existing UI surfaces continue to receive updates without + # rewriting the old interfaces. # Call legacy progress_callback if provided if self.progress_callback: self.progress_callback(ProgressInfo( diff --git a/tests/unit/test_download_service.py b/tests/unit/test_download_service.py index ff14809..c1179f8 100644 --- a/tests/unit/test_download_service.py +++ b/tests/unit/test_download_service.py @@ -470,6 +470,82 @@ class TestBroadcastCallbacks: # Verify callback was called mock_callback.assert_called() + @pytest.mark.asyncio + async def test_progress_callback_format(self, download_service): + """Test that progress callback receives correct data format.""" + # Set up a mock callback to capture progress updates + progress_updates = [] + + def capture_progress(progress_data: dict): + progress_updates.append(progress_data) + + # Mock download to simulate progress + async def mock_download_with_progress(*args, **kwargs): + # Get the callback from kwargs + callback = kwargs.get('callback') + if callback: + # Simulate progress updates with the expected format + callback({ + 'percent': 50.0, + 'downloaded_mb': 250.5, + 'total_mb': 501.0, + 'speed_mbps': 5.2, + 'eta_seconds': 48, + }) + return True + + download_service._anime_service.download = mock_download_with_progress + + # Add an item to the queue + await download_service.add_to_queue( + serie_id="series-1", + serie_name="Test Series", + episodes=[EpisodeIdentifier(season=1, episode=1)], + ) + + # Process the download + item = download_service._pending_queue.popleft() + del download_service._pending_items_by_id[item.id] + + # Replace the progress callback with our capture function + original_callback = download_service._create_progress_callback + + def wrapper(item): + callback = original_callback(item) + + def wrapped_callback(data): + capture_progress(data) + callback(data) + + return wrapped_callback + + download_service._create_progress_callback = wrapper + + await download_service._process_download(item) + + # Verify progress callback was called with correct format + assert len(progress_updates) > 0 + progress_data = progress_updates[0] + + # Check all expected keys are present + assert 'percent' in progress_data + assert 'downloaded_mb' in progress_data + assert 'total_mb' in progress_data + assert 'speed_mbps' in progress_data + assert 'eta_seconds' in progress_data + + # Verify values are of correct type + assert isinstance(progress_data['percent'], (int, float)) + assert isinstance(progress_data['downloaded_mb'], (int, float)) + assert ( + progress_data['total_mb'] is None + or isinstance(progress_data['total_mb'], (int, float)) + ) + assert ( + progress_data['speed_mbps'] is None + or isinstance(progress_data['speed_mbps'], (int, float)) + ) + class TestServiceLifecycle: """Test service start and stop operations."""