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:
@@ -100,6 +100,37 @@ class AnimeSeries(Base, TimestampMixin):
|
||||
doc="TVDB (TheTVDB) ID for series metadata"
|
||||
)
|
||||
|
||||
# Loading status fields for asynchronous data loading
|
||||
loading_status: Mapped[str] = mapped_column(
|
||||
String(50), nullable=False, default="completed", server_default="completed",
|
||||
doc="Loading status: pending, loading_episodes, loading_nfo, loading_logo, "
|
||||
"loading_images, completed, failed"
|
||||
)
|
||||
episodes_loaded: Mapped[bool] = mapped_column(
|
||||
Boolean, nullable=False, default=True, server_default="1",
|
||||
doc="Whether episodes have been scanned and loaded"
|
||||
)
|
||||
logo_loaded: Mapped[bool] = mapped_column(
|
||||
Boolean, nullable=False, default=False, server_default="0",
|
||||
doc="Whether logo.png has been downloaded"
|
||||
)
|
||||
images_loaded: Mapped[bool] = mapped_column(
|
||||
Boolean, nullable=False, default=False, server_default="0",
|
||||
doc="Whether poster/fanart images have been downloaded"
|
||||
)
|
||||
loading_started_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True,
|
||||
doc="Timestamp when background loading started"
|
||||
)
|
||||
loading_completed_at: Mapped[Optional[datetime]] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True,
|
||||
doc="Timestamp when background loading completed"
|
||||
)
|
||||
loading_error: Mapped[Optional[str]] = mapped_column(
|
||||
String(1000), nullable=True,
|
||||
doc="Error message if loading failed"
|
||||
)
|
||||
|
||||
# Relationships
|
||||
episodes: Mapped[List["Episode"]] = relationship(
|
||||
"Episode",
|
||||
|
||||
@@ -65,6 +65,11 @@ class AnimeSeriesService:
|
||||
site: str,
|
||||
folder: str,
|
||||
year: int | None = None,
|
||||
loading_status: str = "completed",
|
||||
episodes_loaded: bool = True,
|
||||
logo_loaded: bool = False,
|
||||
images_loaded: bool = False,
|
||||
loading_started_at: datetime | None = None,
|
||||
) -> AnimeSeries:
|
||||
"""Create a new anime series.
|
||||
|
||||
@@ -75,6 +80,11 @@ class AnimeSeriesService:
|
||||
site: Provider site URL
|
||||
folder: Local filesystem path
|
||||
year: Release year (optional)
|
||||
loading_status: Initial loading status (default: "completed")
|
||||
episodes_loaded: Whether episodes are loaded (default: True for backward compat)
|
||||
logo_loaded: Whether logo is loaded (default: False)
|
||||
images_loaded: Whether images are loaded (default: False)
|
||||
loading_started_at: When loading started (optional)
|
||||
|
||||
Returns:
|
||||
Created AnimeSeries instance
|
||||
@@ -88,6 +98,11 @@ class AnimeSeriesService:
|
||||
site=site,
|
||||
folder=folder,
|
||||
year=year,
|
||||
loading_status=loading_status,
|
||||
episodes_loaded=episodes_loaded,
|
||||
logo_loaded=logo_loaded,
|
||||
images_loaded=images_loaded,
|
||||
loading_started_at=loading_started_at,
|
||||
)
|
||||
db.add(series)
|
||||
await db.flush()
|
||||
|
||||
Reference in New Issue
Block a user