fix: progress part 1. percentage is working

This commit is contained in:
Lukas 2025-11-20 19:21:01 +01:00
parent 34019b7e65
commit 029abb9be2
5 changed files with 64 additions and 27 deletions

View File

@ -13,8 +13,8 @@
"status": "cancelled", "status": "cancelled",
"priority": "NORMAL", "priority": "NORMAL",
"added_at": "2025-11-20T17:12:34.485225Z", "added_at": "2025-11-20T17:12:34.485225Z",
"started_at": "2025-11-20T18:09:04.527701Z", "started_at": "2025-11-20T18:15:48.216607Z",
"completed_at": "2025-11-20T18:09:27.852314Z", "completed_at": "2025-11-20T18:16:38.929297Z",
"progress": null, "progress": null,
"error": null, "error": null,
"retry_count": 0, "retry_count": 0,
@ -943,5 +943,5 @@
], ],
"active": [], "active": [],
"failed": [], "failed": [],
"timestamp": "2025-11-20T18:09:27.852598+00:00" "timestamp": "2025-11-20T18:16:38.929570+00:00"
} }

View File

@ -260,6 +260,7 @@ class SeriesApp:
progress=(downloaded / total_bytes) * 100 if total_bytes else 0, progress=(downloaded / total_bytes) * 100 if total_bytes else 0,
eta=eta, eta=eta,
mbper_sec=mbper_sec, mbper_sec=mbper_sec,
item_id=item_id,
) )
) )
# Perform download in thread to avoid blocking event loop # Perform download in thread to avoid blocking event loop

View File

@ -37,6 +37,7 @@ class AnimeService:
self._app = series_app self._app = series_app
self._directory = series_app.directory_to_search self._directory = series_app.directory_to_search
self._progress_service = progress_service or get_progress_service() self._progress_service = progress_service or get_progress_service()
self._event_loop: Optional[asyncio.AbstractEventLoop] = None
# Subscribe to SeriesApp events # Subscribe to SeriesApp events
# Note: Events library uses assignment (=), not += operator # Note: Events library uses assignment (=), not += operator
try: try:
@ -54,13 +55,17 @@ class AnimeService:
args: DownloadStatusEventArgs from SeriesApp args: DownloadStatusEventArgs from SeriesApp
""" """
try: try:
# Check if there's a running event loop # Get event loop - try running loop first, then stored loop
loop = None
try: try:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
except RuntimeError: except RuntimeError:
# No running loop - log and skip # No running loop in this thread - use stored loop
loop = self._event_loop
if not loop:
logger.debug( logger.debug(
"No running event loop for download status event", "No event loop available for download status event",
status=args.status status=args.status
) )
return return
@ -74,38 +79,42 @@ class AnimeService:
# Map SeriesApp download events to progress service # Map SeriesApp download events to progress service
if args.status == "started": if args.status == "started":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.start_progress( self._progress_service.start_progress(
progress_id=progress_id, progress_id=progress_id,
progress_type=ProgressType.DOWNLOAD, progress_type=ProgressType.DOWNLOAD,
title=f"Downloading {args.serie_folder}", title=f"Downloading {args.serie_folder}",
message=f"S{args.season:02d}E{args.episode:02d}", message=f"S{args.season:02d}E{args.episode:02d}",
metadata={"item_id": args.item_id} if args.item_id else None, metadata={"item_id": args.item_id} if args.item_id else None,
) ),
loop
) )
elif args.status == "progress": elif args.status == "progress":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.update_progress( self._progress_service.update_progress(
progress_id=progress_id, progress_id=progress_id,
current=int(args.progress), current=int(args.progress),
total=100, total=100,
message=args.message or "Downloading...", message=args.message or "Downloading...",
metadata={"item_id": args.item_id} if args.item_id else None, metadata={"item_id": args.item_id} if args.item_id else None,
) ),
loop
) )
elif args.status == "completed": elif args.status == "completed":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.complete_progress( self._progress_service.complete_progress(
progress_id=progress_id, progress_id=progress_id,
message="Download completed", message="Download completed",
) ),
loop
) )
elif args.status == "failed": elif args.status == "failed":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.fail_progress( self._progress_service.fail_progress(
progress_id=progress_id, progress_id=progress_id,
error_message=args.message or str(args.error), error_message=args.message or str(args.error),
) ),
loop
) )
except Exception as exc: except Exception as exc:
logger.error( logger.error(
@ -122,56 +131,65 @@ class AnimeService:
try: try:
scan_id = "library_scan" scan_id = "library_scan"
# Check if there's a running event loop # Get event loop - try running loop first, then stored loop
loop = None
try: try:
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
except RuntimeError: except RuntimeError:
# No running loop - log and skip # No running loop in this thread - use stored loop
loop = self._event_loop
if not loop:
logger.debug( logger.debug(
"No running event loop for scan status event", "No event loop available for scan status event",
status=args.status status=args.status
) )
return return
# Map SeriesApp scan events to progress service # Map SeriesApp scan events to progress service
if args.status == "started": if args.status == "started":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.start_progress( self._progress_service.start_progress(
progress_id=scan_id, progress_id=scan_id,
progress_type=ProgressType.SCAN, progress_type=ProgressType.SCAN,
title="Scanning anime library", title="Scanning anime library",
message=args.message or "Initializing scan...", message=args.message or "Initializing scan...",
) ),
loop
) )
elif args.status == "progress": elif args.status == "progress":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.update_progress( self._progress_service.update_progress(
progress_id=scan_id, progress_id=scan_id,
current=args.current, current=args.current,
total=args.total, total=args.total,
message=args.message or f"Scanning: {args.folder}", message=args.message or f"Scanning: {args.folder}",
) ),
loop
) )
elif args.status == "completed": elif args.status == "completed":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.complete_progress( self._progress_service.complete_progress(
progress_id=scan_id, progress_id=scan_id,
message=args.message or "Scan completed", message=args.message or "Scan completed",
) ),
loop
) )
elif args.status == "failed": elif args.status == "failed":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.fail_progress( self._progress_service.fail_progress(
progress_id=scan_id, progress_id=scan_id,
error_message=args.message or str(args.error), error_message=args.message or str(args.error),
) ),
loop
) )
elif args.status == "cancelled": elif args.status == "cancelled":
loop.create_task( asyncio.run_coroutine_threadsafe(
self._progress_service.fail_progress( self._progress_service.fail_progress(
progress_id=scan_id, progress_id=scan_id,
error_message=args.message or "Scan cancelled", error_message=args.message or "Scan cancelled",
) ),
loop
) )
except Exception as exc: except Exception as exc:
logger.error("Error handling scan status event", error=str(exc)) logger.error("Error handling scan status event", error=str(exc))
@ -228,6 +246,9 @@ class AnimeService:
forwarded to the ProgressService through event handlers. forwarded to the ProgressService through event handlers.
""" """
try: try:
# Store event loop for event handlers
self._event_loop = asyncio.get_running_loop()
# SeriesApp.rescan is now async and handles events internally # SeriesApp.rescan is now async and handles events internally
await self._app.rescan() await self._app.rescan()
@ -247,21 +268,33 @@ class AnimeService:
season: int, season: int,
episode: int, episode: int,
key: str, key: str,
item_id: Optional[str] = None,
) -> bool: ) -> bool:
"""Start a download. """Start a download.
The SeriesApp now handles progress tracking via events which are The SeriesApp now handles progress tracking via events which are
forwarded to the ProgressService through event handlers. forwarded to the ProgressService through event handlers.
Args:
serie_folder: Serie folder name
season: Season number
episode: Episode number
key: Serie key
item_id: Optional download queue item ID for tracking
Returns True on success or raises AnimeServiceError on failure. Returns True on success or raises AnimeServiceError on failure.
""" """
try: try:
# Store event loop for event handlers
self._event_loop = asyncio.get_running_loop()
# SeriesApp.download is now async and handles events internally # SeriesApp.download is now async and handles events internally
return await self._app.download( return await self._app.download(
serie_folder=serie_folder, serie_folder=serie_folder,
season=season, season=season,
episode=episode, episode=episode,
key=key, key=key,
item_id=item_id,
) )
except Exception as exc: except Exception as exc:
logger.exception("download failed") logger.exception("download failed")

View File

@ -808,6 +808,7 @@ class DownloadService:
season=item.episode.season, season=item.episode.season,
episode=item.episode.episode, episode=item.episode.episode,
key=item.serie_id, key=item.serie_id,
item_id=item.id,
) )
# Handle result # Handle result

View File

@ -247,6 +247,7 @@ class TestDownload:
season=1, season=1,
episode=1, episode=1,
key="test_key", key="test_key",
item_id=None,
) )
@pytest.mark.asyncio @pytest.mark.asyncio
@ -272,6 +273,7 @@ class TestDownload:
season=1, season=1,
episode=1, episode=1,
key="test_key", key="test_key",
item_id=None,
) )
@pytest.mark.asyncio @pytest.mark.asyncio