Remove redundant episode loading step
- Merged _load_episodes() functionality into _scan_missing_episodes() - _scan_missing_episodes() already queries provider and compares with filesystem - Eliminates duplicate filesystem scanning during series add - Simplifies background loading flow: NFO → Episode Discovery
This commit is contained in:
@@ -120,3 +120,4 @@ For each task completed:
|
||||
## TODO List:
|
||||
|
||||
✅ **COMPLETED:** anime not showing issue - Fixed by loading series from database on every startup
|
||||
✅ **COMPLETED:** Load missing episodes for newly added series - Implemented episode scanning and database sync after NFO creation
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
|
||||
@@ -733,6 +734,98 @@ class AnimeService:
|
||||
# Load into SeriesApp
|
||||
self._app.load_series_from_list(series_list)
|
||||
|
||||
async def sync_episodes_to_db(self, series_key: str) -> int:
|
||||
"""
|
||||
Sync episodes from in-memory SeriesApp to database for a specific series.
|
||||
|
||||
This method reads the episodeDict from the in-memory series (populated
|
||||
by scanner) and syncs it to the database. Called after scanning for
|
||||
missing episodes.
|
||||
|
||||
Args:
|
||||
series_key: The series key to sync episodes for
|
||||
|
||||
Returns:
|
||||
Number of episodes synced to database
|
||||
"""
|
||||
from src.server.database.connection import get_db_session
|
||||
from src.server.database.service import AnimeSeriesService, EpisodeService
|
||||
|
||||
# Get the serie from in-memory cache
|
||||
if not hasattr(self._app, 'list') or not hasattr(self._app.list, 'keyDict'):
|
||||
logger.warning(f"Series list not available for episode sync: {series_key}")
|
||||
return 0
|
||||
|
||||
serie = self._app.list.keyDict.get(series_key)
|
||||
if not serie:
|
||||
logger.warning(f"Series not found in memory for episode sync: {series_key}")
|
||||
return 0
|
||||
|
||||
episodes_added = 0
|
||||
|
||||
async with get_db_session() as db:
|
||||
# Get series from database
|
||||
series_db = await AnimeSeriesService.get_by_key(db, series_key)
|
||||
if not series_db:
|
||||
logger.warning(f"Series not found in database: {series_key}")
|
||||
return 0
|
||||
|
||||
# Get existing episodes from database
|
||||
existing_episodes = await EpisodeService.get_by_series(db, series_db.id)
|
||||
|
||||
# Build dict of existing episodes: {season: {ep_num: episode_id}}
|
||||
existing_dict: dict[int, dict[int, int]] = {}
|
||||
for ep in existing_episodes:
|
||||
if ep.season not in existing_dict:
|
||||
existing_dict[ep.season] = {}
|
||||
existing_dict[ep.season][ep.episode_number] = ep.id
|
||||
|
||||
# Get new missing episodes from in-memory serie
|
||||
new_dict = serie.episodeDict or {}
|
||||
|
||||
# Add new missing episodes that are not in the database
|
||||
for season, episode_numbers in new_dict.items():
|
||||
existing_season_eps = existing_dict.get(season, {})
|
||||
for ep_num in episode_numbers:
|
||||
if ep_num not in existing_season_eps:
|
||||
await EpisodeService.create(
|
||||
db=db,
|
||||
series_id=series_db.id,
|
||||
season=season,
|
||||
episode_number=ep_num,
|
||||
)
|
||||
episodes_added += 1
|
||||
logger.debug(
|
||||
f"Added missing episode to database: {series_key} S{season:02d}E{ep_num:02d}"
|
||||
)
|
||||
|
||||
if episodes_added > 0:
|
||||
logger.info(
|
||||
f"Synced {episodes_added} missing episodes to database for {series_key}"
|
||||
)
|
||||
|
||||
# Broadcast update to frontend to refresh series list
|
||||
try:
|
||||
await self._broadcast_series_updated(series_key)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to broadcast series update: {e}")
|
||||
|
||||
return episodes_added
|
||||
|
||||
async def _broadcast_series_updated(self, series_key: str) -> None:
|
||||
"""Broadcast series update event to WebSocket clients."""
|
||||
if not self._websocket_service:
|
||||
return
|
||||
|
||||
payload = {
|
||||
"type": "series_updated",
|
||||
"key": series_key,
|
||||
"message": "Episodes updated",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
|
||||
await self._websocket_service.broadcast(payload)
|
||||
|
||||
async def add_series_to_db(
|
||||
self,
|
||||
serie,
|
||||
|
||||
@@ -289,12 +289,6 @@ class BackgroundLoaderService:
|
||||
db
|
||||
)
|
||||
|
||||
# Load episodes if missing
|
||||
if missing["episodes"]:
|
||||
await self._load_episodes(task, db)
|
||||
else:
|
||||
task.progress["episodes"] = True
|
||||
|
||||
# Load NFO and images if missing
|
||||
if missing["nfo"] or missing["logo"] or missing["images"]:
|
||||
await self._load_nfo_and_images(task, db)
|
||||
@@ -303,6 +297,11 @@ class BackgroundLoaderService:
|
||||
task.progress["logo"] = True
|
||||
task.progress["images"] = True
|
||||
|
||||
# Scan for missing episodes
|
||||
# This discovers seasons/episodes from provider and compares with filesystem
|
||||
# to populate episodeDict with episodes available for download
|
||||
await self._scan_missing_episodes(task, db)
|
||||
|
||||
# Mark as completed
|
||||
task.status = LoadingStatus.COMPLETED
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
@@ -451,12 +450,15 @@ class BackgroundLoaderService:
|
||||
logger.exception(f"Failed to load episodes for {task.key}: {e}")
|
||||
raise
|
||||
|
||||
async def _load_nfo_and_images(self, task: SeriesLoadingTask, db: Any) -> None:
|
||||
async def _load_nfo_and_images(self, task: SeriesLoadingTask, db: Any) -> bool:
|
||||
"""Load NFO file and images for a series by reusing NFOService.
|
||||
|
||||
Args:
|
||||
task: The loading task
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
bool: True if NFO was created, False if it already existed or failed
|
||||
"""
|
||||
task.status = LoadingStatus.LOADING_NFO
|
||||
await self._broadcast_status(task, "Checking NFO file...")
|
||||
@@ -470,7 +472,7 @@ class BackgroundLoaderService:
|
||||
task.progress["nfo"] = False
|
||||
task.progress["logo"] = False
|
||||
task.progress["images"] = False
|
||||
return
|
||||
return False
|
||||
|
||||
# Check if NFO already exists
|
||||
if self.series_app.nfo_service.has_nfo(task.folder):
|
||||
@@ -497,7 +499,7 @@ class BackgroundLoaderService:
|
||||
await db.commit()
|
||||
|
||||
logger.info(f"Existing NFO found and database updated for series: {task.key}")
|
||||
return
|
||||
return False
|
||||
|
||||
# NFO doesn't exist, create it
|
||||
await self._broadcast_status(task, "Generating NFO file...")
|
||||
@@ -531,6 +533,7 @@ class BackgroundLoaderService:
|
||||
await db.commit()
|
||||
|
||||
logger.info(f"NFO and images created and loaded for series: {task.key}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to load NFO/images for {task.key}: {e}")
|
||||
@@ -538,6 +541,69 @@ class BackgroundLoaderService:
|
||||
task.progress["nfo"] = False
|
||||
task.progress["logo"] = False
|
||||
task.progress["images"] = False
|
||||
return False
|
||||
|
||||
async def _scan_missing_episodes(self, task: SeriesLoadingTask, db: Any) -> None:
|
||||
"""Scan for missing episodes after NFO creation.
|
||||
|
||||
This method calls SerieScanner.scan_single_series() to populate
|
||||
the episodeDict with available episodes that can be downloaded.
|
||||
|
||||
Args:
|
||||
task: The loading task
|
||||
db: Database session
|
||||
"""
|
||||
task.status = LoadingStatus.LOADING_EPISODES
|
||||
await self._broadcast_status(task, "Scanning for missing episodes...")
|
||||
|
||||
try:
|
||||
# Get scanner from SeriesApp
|
||||
if not hasattr(self.series_app, 'serie_scanner'):
|
||||
logger.warning(
|
||||
f"Scanner not available, skipping episode scan for {task.key}"
|
||||
)
|
||||
return
|
||||
|
||||
# Scan for missing episodes using the targeted scan method
|
||||
# This populates the episodeDict without triggering a full rescan
|
||||
logger.info(f"Scanning missing episodes for {task.key}")
|
||||
missing_episodes = self.series_app.serie_scanner.scan_single_series(
|
||||
key=task.key,
|
||||
folder=task.folder
|
||||
)
|
||||
|
||||
# Log the results
|
||||
total_missing = sum(len(eps) for eps in missing_episodes.values())
|
||||
if total_missing > 0:
|
||||
logger.info(
|
||||
f"Found {total_missing} missing episodes across "
|
||||
f"{len(missing_episodes)} seasons for {task.key}"
|
||||
)
|
||||
|
||||
# Notify anime_service to sync episodes to database
|
||||
if self.anime_service:
|
||||
logger.debug(f"Calling anime_service.sync_episodes_to_db for {task.key}")
|
||||
await self.anime_service.sync_episodes_to_db(task.key)
|
||||
else:
|
||||
logger.warning(f"anime_service not available, episodes will not be synced to DB for {task.key}")
|
||||
else:
|
||||
logger.info(f"No missing episodes found for {task.key}")
|
||||
|
||||
# Update series status in database
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
series_db = await AnimeSeriesService.get_by_key(db, task.key)
|
||||
if series_db:
|
||||
series_db.episodes_loaded = True
|
||||
series_db.loading_status = "loading_episodes"
|
||||
await db.commit()
|
||||
|
||||
# Mark progress as complete
|
||||
task.progress["episodes"] = True
|
||||
task.progress["episodes"] = True
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Failed to scan missing episodes for {task.key}: {e}")
|
||||
task.progress["episodes"] = False
|
||||
|
||||
async def _broadcast_status(
|
||||
self,
|
||||
@@ -567,7 +633,9 @@ class BackgroundLoaderService:
|
||||
payload = {
|
||||
"type": "series_loading_update",
|
||||
"key": task.key,
|
||||
"series_key": task.key, # For frontend compatibility
|
||||
"folder": task.folder,
|
||||
"status": task.status.value, # For frontend compatibility
|
||||
"loading_status": task.status.value,
|
||||
"progress": task.progress,
|
||||
"message": message,
|
||||
|
||||
Reference in New Issue
Block a user