Implement async series data loading with background processing

- Add loading status fields to AnimeSeries model
- Create BackgroundLoaderService for async task processing
- Update POST /api/anime/add to return 202 Accepted immediately
- Add GET /api/anime/{key}/loading-status endpoint
- Integrate background loader with startup/shutdown lifecycle
- Create database migration script for loading status fields
- Add unit tests for BackgroundLoaderService (10 tests, all passing)
- Update AnimeSeriesService.create() to accept loading status fields

Architecture follows clean separation with no code duplication:
- BackgroundLoader orchestrates, doesn't reimplement
- Reuses existing AnimeService, NFOService, WebSocket patterns
- Database-backed status survives restarts
This commit is contained in:
2026-01-19 07:14:55 +01:00
parent df19f8ad95
commit f18c31a035
12 changed files with 3463 additions and 141 deletions

View File

@@ -27,6 +27,7 @@ logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from src.server.services.anime_service import AnimeService
from src.server.services.background_loader_service import BackgroundLoaderService
from src.server.services.download_service import DownloadService
# Security scheme for JWT authentication
@@ -40,6 +41,7 @@ _series_app: Optional[SeriesApp] = None
# Global service instances
_anime_service: Optional["AnimeService"] = None
_download_service: Optional["DownloadService"] = None
_background_loader_service: Optional["BackgroundLoaderService"] = None
@dataclass
@@ -452,3 +454,51 @@ def reset_download_service() -> None:
"""Reset global DownloadService instance (for testing/config changes)."""
global _download_service
_download_service = None
def get_background_loader_service() -> "BackgroundLoaderService":
"""
Dependency to get BackgroundLoaderService instance.
Returns:
BackgroundLoaderService: The background loader service for async data loading
Raises:
HTTPException: If BackgroundLoaderService initialization fails
"""
global _background_loader_service
if _background_loader_service is None:
try:
from src.server.services.background_loader_service import (
BackgroundLoaderService,
)
from src.server.services.websocket_service import get_websocket_service
anime_service = get_anime_service()
series_app = get_series_app()
websocket_service = get_websocket_service()
_background_loader_service = BackgroundLoaderService(
websocket_service=websocket_service,
anime_service=anime_service,
series_app=series_app
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=(
"Failed to initialize BackgroundLoaderService: "
f"{str(e)}"
),
) from e
return _background_loader_service
def reset_background_loader_service() -> None:
"""Reset global BackgroundLoaderService instance (for testing/config changes)."""
global _background_loader_service
_background_loader_service = None