"""Download queue Pydantic models for the Aniworld web application. This module defines request/response models used by the download queue API and the download service. Models are intentionally lightweight and focused on serialization, validation, and OpenAPI documentation. """ from __future__ import annotations from datetime import datetime, timezone from enum import Enum from typing import List, Optional from pydantic import BaseModel, Field, HttpUrl, field_validator class DownloadStatus(str, Enum): """Status of a download item in the queue.""" PENDING = "pending" DOWNLOADING = "downloading" PAUSED = "paused" COMPLETED = "completed" FAILED = "failed" CANCELLED = "cancelled" class DownloadPriority(str, Enum): """Priority level for download queue items.""" LOW = "LOW" NORMAL = "NORMAL" HIGH = "HIGH" class EpisodeIdentifier(BaseModel): """Episode identification information for a download item.""" season: int = Field(..., ge=1, description="Season number (1-based)") episode: int = Field( ..., ge=1, description="Episode number within season (1-based)" ) title: Optional[str] = Field(None, description="Episode title if known") class DownloadProgress(BaseModel): """Real-time progress information for an active download.""" percent: float = Field( 0.0, ge=0.0, le=100.0, description="Download progress percentage" ) downloaded_mb: float = Field( 0.0, ge=0.0, description="Downloaded size in megabytes" ) total_mb: Optional[float] = Field( None, ge=0.0, description="Total size in megabytes if known" ) speed_mbps: Optional[float] = Field( None, ge=0.0, description="Download speed in MB/s" ) eta_seconds: Optional[int] = Field( None, ge=0, description="Estimated time remaining in seconds" ) class DownloadItem(BaseModel): """Represents a single download item in the queue.""" id: str = Field(..., description="Unique download item identifier") serie_id: str = Field(..., description="Series identifier (provider key)") serie_folder: Optional[str] = Field( None, description="Series folder name on disk" ) serie_name: str = Field(..., min_length=1, description="Series name") episode: EpisodeIdentifier = Field( ..., description="Episode identification" ) status: DownloadStatus = Field( DownloadStatus.PENDING, description="Current download status" ) priority: DownloadPriority = Field( DownloadPriority.NORMAL, description="Queue priority" ) # Timestamps added_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc), description="When item was added to queue", ) started_at: Optional[datetime] = Field( None, description="When download started" ) completed_at: Optional[datetime] = Field( None, description="When download completed/failed" ) # Progress tracking progress: Optional[DownloadProgress] = Field( None, description="Current progress if downloading" ) # Error handling error: Optional[str] = Field(None, description="Error message if failed") retry_count: int = Field(0, ge=0, description="Number of retry attempts") # Download source source_url: Optional[HttpUrl] = Field( None, description="Source URL for download" ) class QueueStatus(BaseModel): """Overall status of the download queue system.""" is_running: bool = Field( False, description="Whether the queue processor is running" ) is_paused: bool = Field(False, description="Whether downloads are paused") active_downloads: List[DownloadItem] = Field( default_factory=list, description="Currently downloading items" ) pending_queue: List[DownloadItem] = Field( default_factory=list, description="Items waiting to be downloaded" ) completed_downloads: List[DownloadItem] = Field( default_factory=list, description="Recently completed downloads" ) failed_downloads: List[DownloadItem] = Field( default_factory=list, description="Failed download items" ) class QueueStats(BaseModel): """Statistics about the download queue.""" total_items: int = Field( 0, ge=0, description="Total number of items in all queues" ) pending_count: int = Field(0, ge=0, description="Number of pending items") active_count: int = Field( 0, ge=0, description="Number of active downloads" ) completed_count: int = Field( 0, ge=0, description="Number of completed downloads" ) failed_count: int = Field( 0, ge=0, description="Number of failed downloads" ) total_downloaded_mb: float = Field( 0.0, ge=0.0, description="Total megabytes downloaded" ) average_speed_mbps: Optional[float] = Field( None, ge=0.0, description="Average download speed in MB/s" ) estimated_time_remaining: Optional[int] = Field( None, ge=0, description="Estimated time to complete queue in seconds" ) class DownloadRequest(BaseModel): """Request to add episode(s) to the download queue.""" serie_id: str = Field(..., description="Series identifier (provider key)") serie_folder: Optional[str] = Field( None, description="Series folder name on disk" ) serie_name: str = Field( ..., min_length=1, description="Series name for display" ) episodes: List[EpisodeIdentifier] = Field( ..., description="List of episodes to download" ) priority: DownloadPriority = Field( DownloadPriority.NORMAL, description="Priority level for queue items" ) @field_validator('priority', mode='before') @classmethod def normalize_priority(cls, v): """Normalize priority to uppercase for case-insensitive matching.""" if isinstance(v, str): return v.upper() return v class DownloadResponse(BaseModel): """Response after adding items to the download queue.""" status: str = Field(..., description="Status of the request") message: str = Field(..., description="Human-readable status message") added_items: List[str] = Field( default_factory=list, description="IDs of successfully added download items" ) failed_items: List[str] = Field( default_factory=list, description="Episodes that failed to be added" ) class QueueOperationRequest(BaseModel): """Request to perform operations on queue items.""" item_ids: List[str] = Field( ..., description="List of download item IDs" ) class QueueReorderRequest(BaseModel): """Request to reorder items in the pending queue.""" item_id: str = Field(..., description="Download item ID to move") new_position: int = Field( ..., ge=0, description="New position in queue (0-based)" ) class QueueStatusResponse(BaseModel): """Complete response for queue status endpoint.""" status: QueueStatus = Field(..., description="Current queue status") statistics: QueueStats = Field(..., description="Queue statistics")