feat: implement WebSocket real-time progress updates

- Add ProgressService for centralized progress tracking and broadcasting
- Integrate ProgressService with DownloadService for download progress
- Integrate ProgressService with AnimeService for scan progress
- Add progress-related WebSocket message models (ScanProgress, ErrorNotification, etc.)
- Initialize ProgressService with WebSocket callback in application startup
- Add comprehensive unit tests for ProgressService
- Update infrastructure.md with ProgressService documentation
- Remove completed WebSocket Real-time Updates task from instructions.md

The ProgressService provides:
- Real-time progress tracking for downloads, scans, and queue operations
- Automatic progress percentage calculation
- Progress lifecycle management (start, update, complete, fail, cancel)
- WebSocket integration for instant client updates
- Progress history with size limits
- Thread-safe operations using asyncio locks
- Support for metadata and custom messages

Benefits:
- Decoupled progress tracking from WebSocket broadcasting
- Single reusable service across all components
- Supports multiple concurrent operations efficiently
- Centralized progress tracking simplifies monitoring
- Instant feedback to users on long-running operations
This commit is contained in:
2025-10-17 11:12:06 +02:00
parent 42a07be4cb
commit 94de91ffa0
7 changed files with 1375 additions and 4 deletions

View File

@@ -30,6 +30,11 @@ class WebSocketMessageType(str, Enum):
QUEUE_PAUSED = "queue_paused"
QUEUE_RESUMED = "queue_resumed"
# Progress-related messages
SCAN_PROGRESS = "scan_progress"
SCAN_COMPLETE = "scan_complete"
SCAN_FAILED = "scan_failed"
# System messages
SYSTEM_INFO = "system_info"
SYSTEM_WARNING = "system_warning"
@@ -188,3 +193,93 @@ class RoomSubscriptionRequest(BaseModel):
room: str = Field(
..., min_length=1, description="Room name to join or leave"
)
class ScanProgressMessage(BaseModel):
"""Scan progress update message."""
type: WebSocketMessageType = Field(
default=WebSocketMessageType.SCAN_PROGRESS,
description="Message type",
)
timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(),
description="ISO 8601 timestamp",
)
data: Dict[str, Any] = Field(
...,
description="Scan progress data including current, total, percent",
)
class ScanCompleteMessage(BaseModel):
"""Scan completion message."""
type: WebSocketMessageType = Field(
default=WebSocketMessageType.SCAN_COMPLETE,
description="Message type",
)
timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(),
description="ISO 8601 timestamp",
)
data: Dict[str, Any] = Field(
...,
description="Scan completion data including series_found, duration",
)
class ScanFailedMessage(BaseModel):
"""Scan failure message."""
type: WebSocketMessageType = Field(
default=WebSocketMessageType.SCAN_FAILED,
description="Message type",
)
timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(),
description="ISO 8601 timestamp",
)
data: Dict[str, Any] = Field(
..., description="Scan error data including error_message"
)
class ErrorNotificationMessage(BaseModel):
"""Error notification message for critical errors."""
type: WebSocketMessageType = Field(
default=WebSocketMessageType.SYSTEM_ERROR,
description="Message type",
)
timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(),
description="ISO 8601 timestamp",
)
data: Dict[str, Any] = Field(
...,
description=(
"Error notification data including severity, message, details"
),
)
class ProgressUpdateMessage(BaseModel):
"""Generic progress update message.
Can be used for any type of progress (download, scan, queue, etc.)
"""
type: WebSocketMessageType = Field(
..., description="Type of progress message"
)
timestamp: str = Field(
default_factory=lambda: datetime.utcnow().isoformat(),
description="ISO 8601 timestamp",
)
data: Dict[str, Any] = Field(
...,
description=(
"Progress data including id, status, percent, current, total"
),
)