refactored callback

This commit is contained in:
2025-11-02 10:34:49 +01:00
parent 8a49db2a10
commit e414a1a358
5 changed files with 241 additions and 254 deletions

View File

@@ -27,11 +27,7 @@ from src.server.models.download import (
QueueStatus,
)
from src.server.services.anime_service import AnimeService, AnimeServiceError
from src.server.services.progress_service import (
ProgressService,
ProgressType,
get_progress_service,
)
from src.server.services.progress_service import ProgressService, get_progress_service
logger = structlog.get_logger(__name__)
@@ -92,10 +88,18 @@ class DownloadService:
# Statistics tracking
self._total_downloaded_mb: float = 0.0
self._download_speeds: deque[float] = deque(maxlen=10)
# Subscribe to SeriesApp download events for progress tracking
if hasattr(anime_service, '_app') and hasattr(
anime_service._app, 'download_status'
):
anime_service._app.download_status += (
self._on_seriesapp_download_status
)
# Load persisted queue
self._load_queue()
logger.info(
"DownloadService initialized",
max_retries=max_retries,
@@ -146,6 +150,69 @@ class DownloadService:
self._broadcast_callback = callback
logger.debug("Broadcast callback registered")
def _on_seriesapp_download_status(self, args) -> None:
"""Handle download status events from SeriesApp.
Updates the active download item with progress information.
Args:
args: DownloadStatusEventArgs from SeriesApp
"""
try:
# Only process if we have an active download
if not self._active_download:
return
# Match the event to the active download item
# SeriesApp events include serie_folder, season, episode
if (
self._active_download.serie_folder == args.serie_folder
and self._active_download.episode.season == args.season
and self._active_download.episode.episode == args.episode
):
if args.status == "progress":
# Update item progress
self._active_download.progress = DownloadProgress(
percent=args.progress,
downloaded_mb=(
args.progress * args.mbper_sec / 100
if args.mbper_sec
else 0.0
),
total_mb=None, # Not provided by SeriesApp
speed_mbps=args.mbper_sec,
eta_seconds=args.eta,
)
# Track speed
if args.mbper_sec:
self._download_speeds.append(args.mbper_sec)
# Broadcast update
asyncio.create_task(
self._broadcast_update(
"download_progress",
{
"download_id": self._active_download.id,
"item_id": self._active_download.id,
"serie_name": self._active_download.serie_name,
"season": args.season,
"episode": args.episode,
"progress": (
self._active_download.progress.model_dump(
mode="json"
)
),
},
)
)
except Exception as exc:
logger.error(
"Error handling SeriesApp download status",
error=str(exc)
)
async def _broadcast_update(self, update_type: str, data: dict) -> None:
"""Broadcast update to connected WebSocket clients.
@@ -689,107 +756,6 @@ class DownloadService:
f"Failed to retry: {str(e)}"
) from e
def _create_progress_callback(self, item: DownloadItem) -> Callable:
"""Create a progress callback for a download item.
Args:
item: Download item to track progress for
Returns:
Callback function for progress updates
"""
logger.info(
f"Creating progress callback for item {item.id}"
)
def progress_callback(progress_data: dict) -> None:
"""Update progress and broadcast to clients."""
try:
logger.debug(
f"Progress callback received: {progress_data}"
)
# Update item progress
item.progress = DownloadProgress(
percent=progress_data.get("percent", 0.0),
downloaded_mb=progress_data.get("downloaded_mb", 0.0),
total_mb=progress_data.get("total_mb"),
speed_mbps=progress_data.get("speed_mbps"),
eta_seconds=progress_data.get("eta_seconds"),
)
logger.debug(
f"Updated item progress: percent={item.progress.percent:.1f}%, "
f"downloaded={item.progress.downloaded_mb:.1f}MB, "
f"total={item.progress.total_mb}MB, "
f"speed={item.progress.speed_mbps}MB/s"
)
# Track speed for statistics
if item.progress.speed_mbps:
self._download_speeds.append(item.progress.speed_mbps)
# Update progress service
# Schedule coroutines in a thread-safe manner
# (callback may be called from executor thread)
if item.progress.total_mb and item.progress.total_mb > 0:
current_mb = int(item.progress.downloaded_mb)
total_mb = int(item.progress.total_mb)
logger.debug(
f"Updating progress service: current={current_mb}MB, "
f"total={total_mb}MB"
)
try:
loop = asyncio.get_event_loop()
asyncio.run_coroutine_threadsafe(
self._progress_service.update_progress(
progress_id=f"download_{item.id}",
current=current_mb,
total=total_mb,
metadata={
"speed_mbps": item.progress.speed_mbps,
"eta_seconds": item.progress.eta_seconds,
},
),
loop
)
except RuntimeError as e:
logger.warning(
f"Could not schedule progress update: {e}"
)
# Broadcast update (fire and forget)
logger.debug(
f"Broadcasting download_progress event for item {item.id}"
)
try:
loop = asyncio.get_event_loop()
asyncio.run_coroutine_threadsafe(
self._broadcast_update(
"download_progress",
{
"download_id": item.id,
"item_id": item.id,
"serie_name": item.serie_name,
"season": item.episode.season,
"episode": item.episode.episode,
"progress": item.progress.model_dump(mode="json"),
},
),
loop
)
except RuntimeError as e:
logger.warning(
f"Could not schedule broadcast: {e}"
)
except Exception as e:
logger.error("Progress callback error", error=str(e))
return progress_callback
async def _process_download(self, item: DownloadItem) -> None:
"""Process a single download item.
@@ -809,31 +775,10 @@ class DownloadService:
season=item.episode.season,
episode=item.episode.episode,
)
# Start progress tracking
await self._progress_service.start_progress(
progress_id=f"download_{item.id}",
progress_type=ProgressType.DOWNLOAD,
title=f"Downloading {item.serie_name}",
message=(
f"S{item.episode.season:02d}E{item.episode.episode:02d}"
),
metadata={
"item_id": item.id,
"serie_name": item.serie_name,
"season": item.episode.season,
"episode": item.episode.episode,
},
)
# Create progress callback
progress_callback = self._create_progress_callback(item)
logger.info(
f"Passing callback {progress_callback} to anime_service for "
f"item {item.id}"
)
# Execute download via anime service
# Note: AnimeService handles progress via SeriesApp events
# Progress updates received via _on_seriesapp_download_status
# Use serie_folder if available, otherwise fall back to serie_id
# for backwards compatibility with old queue items
folder = item.serie_folder if item.serie_folder else item.serie_id
@@ -842,7 +787,6 @@ class DownloadService:
season=item.episode.season,
episode=item.episode.episode,
key=item.serie_id,
callback=progress_callback,
)
# Handle result
@@ -860,17 +804,7 @@ class DownloadService:
"Download completed successfully", item_id=item.id
)
# Complete progress tracking
await self._progress_service.complete_progress(
progress_id=f"download_{item.id}",
message="Download completed successfully",
metadata={
"downloaded_mb": item.progress.downloaded_mb
if item.progress
else 0,
},
)
# Broadcast completion (progress already handled by events)
await self._broadcast_update(
"download_complete",
{
@@ -901,13 +835,7 @@ class DownloadService:
retry_count=item.retry_count,
)
# Fail progress tracking
await self._progress_service.fail_progress(
progress_id=f"download_{item.id}",
error_message=str(e),
metadata={"retry_count": item.retry_count},
)
# Broadcast failure (progress already handled by events)
await self._broadcast_update(
"download_failed",
{