diff --git a/data/download_queue.json b/data/download_queue.json index b1fb23a..574ceac 100644 --- a/data/download_queue.json +++ b/data/download_queue.json @@ -1,138 +1,5 @@ { "pending": [ - { - "id": "16bd4821-59f8-4071-a094-552db51af475", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 3, - "episode": 12, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882168Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, - { - "id": "0c4997aa-7d08-4290-83f1-85eceb5366c3", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 4, - "episode": 1, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882195Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, - { - "id": "f8c2dda3-51e4-4748-b282-e72258f2be14", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 4, - "episode": 2, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882222Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, - { - "id": "c123676c-6539-4d79-b785-0b670f78fae3", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 4, - "episode": 3, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882250Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, - { - "id": "3299eb0c-b275-444d-a951-86004cfe4f84", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 4, - "episode": 4, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882277Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, - { - "id": "22f9ccd9-46ff-4964-ad84-a4f525a2d1f4", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 4, - "episode": 5, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882304Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, - { - "id": "03cbbe56-1a9d-4736-bc5d-7a698771ab2a", - "serie_id": "highschool-dxd", - "serie_name": "Highschool DxD", - "episode": { - "season": 4, - "episode": 6, - "title": null - }, - "status": "pending", - "priority": "normal", - "added_at": "2025-11-01T14:30:56.882331Z", - "started_at": null, - "completed_at": null, - "progress": null, - "error": null, - "retry_count": 0, - "source_url": null - }, { "id": "54533241-ed24-482f-85d7-c9218352ae7f", "serie_id": "highschool-dxd", @@ -1443,10 +1310,7 @@ "error": "Download failed", "retry_count": 0, "source_url": null - } - ], - "active": [], - "failed": [ + }, { "id": "e02d144f-9939-41fe-bd02-0d52d581df4a", "serie_id": "highschool-dxd", @@ -1456,7 +1320,7 @@ "episode": 9, "title": null }, - "status": "failed", + "status": "pending", "priority": "normal", "added_at": "2025-11-01T14:30:56.882082Z", "started_at": "2025-11-01T15:26:12.854334Z", @@ -1475,7 +1339,7 @@ "episode": 10, "title": null }, - "status": "failed", + "status": "pending", "priority": "normal", "added_at": "2025-11-01T14:30:56.882111Z", "started_at": "2025-11-01T15:26:25.815381Z", @@ -1494,7 +1358,7 @@ "episode": 11, "title": null }, - "status": "failed", + "status": "pending", "priority": "normal", "added_at": "2025-11-01T14:30:56.882139Z", "started_at": "2025-11-01T15:26:35.243481Z", @@ -1503,7 +1367,123 @@ "error": "Download failed", "retry_count": 0, "source_url": null + }, + { + "id": "16bd4821-59f8-4071-a094-552db51af475", + "serie_id": "highschool-dxd", + "serie_name": "Highschool DxD", + "episode": { + "season": 3, + "episode": 12, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-11-01T14:30:56.882168Z", + "started_at": "2025-11-01T15:29:33.025257Z", + "completed_at": "2025-11-01T15:29:39.632532Z", + "progress": null, + "error": "Download failed", + "retry_count": 0, + "source_url": null + }, + { + "id": "0c4997aa-7d08-4290-83f1-85eceb5366c3", + "serie_id": "highschool-dxd", + "serie_name": "Highschool DxD", + "episode": { + "season": 4, + "episode": 1, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-11-01T14:30:56.882195Z", + "started_at": "2025-11-01T15:29:40.635705Z", + "completed_at": "2025-11-01T15:29:46.127792Z", + "progress": null, + "error": "Download failed", + "retry_count": 0, + "source_url": null + }, + { + "id": "f8c2dda3-51e4-4748-b282-e72258f2be14", + "serie_id": "highschool-dxd", + "serie_name": "Highschool DxD", + "episode": { + "season": 4, + "episode": 2, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-11-01T14:30:56.882222Z", + "started_at": "2025-11-01T15:29:47.131371Z", + "completed_at": "2025-11-01T15:29:53.137111Z", + "progress": null, + "error": "Download failed", + "retry_count": 0, + "source_url": null + }, + { + "id": "c123676c-6539-4d79-b785-0b670f78fae3", + "serie_id": "highschool-dxd", + "serie_name": "Highschool DxD", + "episode": { + "season": 4, + "episode": 3, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-11-01T14:30:56.882250Z", + "started_at": "2025-11-01T15:29:54.140657Z", + "completed_at": "2025-11-01T15:30:00.057079Z", + "progress": null, + "error": "Download failed", + "retry_count": 0, + "source_url": null + }, + { + "id": "3299eb0c-b275-444d-a951-86004cfe4f84", + "serie_id": "highschool-dxd", + "serie_name": "Highschool DxD", + "episode": { + "season": 4, + "episode": 4, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-11-01T14:30:56.882277Z", + "started_at": "2025-11-01T15:34:02.748613Z", + "completed_at": "2025-11-01T15:34:20.898926Z", + "progress": null, + "error": "Download returned False", + "retry_count": 0, + "source_url": null + }, + { + "id": "22f9ccd9-46ff-4964-ad84-a4f525a2d1f4", + "serie_id": "highschool-dxd", + "serie_name": "Highschool DxD", + "episode": { + "season": 4, + "episode": 5, + "title": null + }, + "status": "pending", + "priority": "normal", + "added_at": "2025-11-01T14:30:56.882304Z", + "started_at": "2025-11-01T15:34:21.901795Z", + "completed_at": "2025-11-01T15:34:44.840985Z", + "progress": null, + "error": "Download returned False", + "retry_count": 0, + "source_url": null } ], - "timestamp": "2025-11-01T15:26:48.942408+00:00" + "active": [], + "failed": [], + "timestamp": "2025-11-01T15:42:52.117823+00:00" } \ No newline at end of file diff --git a/src/core/SeriesApp.py b/src/core/SeriesApp.py index d5f37b7..b1bdaf6 100644 --- a/src/core/SeriesApp.py +++ b/src/core/SeriesApp.py @@ -244,10 +244,25 @@ class SeriesApp: # Wrap callback to enforce cancellation checks and bridge the new # event-driven progress reporting with the legacy callback API that # the CLI still relies on. - def wrapped_callback(progress: float): + def wrapped_callback(progress_info): if self._is_cancelled(): raise InterruptedError("Download cancelled by user") + # yt-dlp passes a dict with progress information + # Extract percentage from the dict + 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) + + if total > 0: + progress = (downloaded / total) * 100 + else: + progress = 0 + else: + # Fallback for old-style float progress + progress = float(progress_info) + # Notify progress via new callback system self._callback_manager.notify_progress( ProgressContext( @@ -284,7 +299,7 @@ class SeriesApp: )) # Perform download - self.loader.download( + download_success = self.loader.download( self.directory_to_search, serieFolder, season, @@ -294,6 +309,12 @@ class SeriesApp: wrapped_callback ) + # Check if download was successful + if not download_success: + raise RuntimeError( + f"Download failed for S{season:02d}E{episode:02d}" + ) + self._operation_status = OperationStatus.COMPLETED logger.info( "Download completed: %s S%02dE%02d", diff --git a/src/core/providers/aniworld_provider.py b/src/core/providers/aniworld_provider.py index f4348b0..d4bd769 100644 --- a/src/core/providers/aniworld_provider.py +++ b/src/core/providers/aniworld_provider.py @@ -247,7 +247,7 @@ class AniworldLoader(Loader): link, header = self._get_direct_link_from_provider( season, episode, key, language ) - logging.debug(f"Direct link obtained from provider") + logging.debug("Direct link obtained from provider") ydl_opts = { 'fragment_retries': float('inf'), 'outtmpl': temp_path, @@ -259,22 +259,36 @@ class AniworldLoader(Loader): if header: ydl_opts['http_headers'] = header - logging.debug(f"Using custom headers for download") + logging.debug("Using custom headers for download") if progress_callback: ydl_opts['progress_hooks'] = [progress_callback] - logging.debug(f"Starting YoutubeDL download") - with YoutubeDL(ydl_opts) as ydl: - ydl.download([link]) + try: + logging.debug("Starting YoutubeDL download") + with YoutubeDL(ydl_opts) as ydl: + ydl.download([link]) - if os.path.exists(temp_path): - logging.debug(f"Moving file from temp to final destination") - shutil.copy(temp_path, output_path) - os.remove(temp_path) - logging.info(f"Download completed successfully: {output_file}") + if os.path.exists(temp_path): + logging.debug("Moving file from temp to final destination") + shutil.copy(temp_path, output_path) + os.remove(temp_path) + logging.info(f"Download completed successfully: {output_file}") + self.clear_cache() + return True + else: + logging.error(f"Download failed: temp file not found at {temp_path}") + self.clear_cache() + return False + except Exception as e: + logging.error(f"YoutubeDL download failed: {e}") + # Try next provider if available + continue break + + # If we get here, all providers failed + logging.error("All download providers failed") self.clear_cache() - return True + return False def get_site_key(self) -> str: """Get the site key for this provider.""" diff --git a/src/server/services/anime_service.py b/src/server/services/anime_service.py index 4fccf31..412a441 100644 --- a/src/server/services/anime_service.py +++ b/src/server/services/anime_service.py @@ -162,7 +162,18 @@ class AnimeService: Returns True on success or raises AnimeServiceError on failure. """ try: - result = await self._run_in_executor(self._app.download, serie_folder, season, episode, key, callback) + result = await self._run_in_executor( + self._app.download, serie_folder, season, episode, + key, callback + ) + # OperationResult has a success attribute + if hasattr(result, 'success'): + logger.debug( + "Download result", + success=result.success, + message=result.message + ) + return result.success return bool(result) except Exception as e: logger.exception("download failed")