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",
"priority": "NORMAL",
"added_at": "2025-11-20T17:12:34.485225Z",
"started_at": "2025-11-20T18:09:04.527701Z",
"completed_at": "2025-11-20T18:09:27.852314Z",
"started_at": "2025-11-20T18:15:48.216607Z",
"completed_at": "2025-11-20T18:16:38.929297Z",
"progress": null,
"error": null,
"retry_count": 0,
@ -943,5 +943,5 @@
],
"active": [],
"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,
eta=eta,
mbper_sec=mbper_sec,
item_id=item_id,
)
)
# Perform download in thread to avoid blocking event loop

View File

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

View File

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

View File

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