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:
|
## TODO List:
|
||||||
|
|
||||||
✅ **COMPLETED:** anime not showing issue - Fixed by loading series from database on every startup
|
✅ **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 asyncio
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime, timezone
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -733,6 +734,98 @@ class AnimeService:
|
|||||||
# Load into SeriesApp
|
# Load into SeriesApp
|
||||||
self._app.load_series_from_list(series_list)
|
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(
|
async def add_series_to_db(
|
||||||
self,
|
self,
|
||||||
serie,
|
serie,
|
||||||
|
|||||||
@@ -289,12 +289,6 @@ class BackgroundLoaderService:
|
|||||||
db
|
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
|
# Load NFO and images if missing
|
||||||
if missing["nfo"] or missing["logo"] or missing["images"]:
|
if missing["nfo"] or missing["logo"] or missing["images"]:
|
||||||
await self._load_nfo_and_images(task, db)
|
await self._load_nfo_and_images(task, db)
|
||||||
@@ -303,6 +297,11 @@ class BackgroundLoaderService:
|
|||||||
task.progress["logo"] = True
|
task.progress["logo"] = True
|
||||||
task.progress["images"] = 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
|
# Mark as completed
|
||||||
task.status = LoadingStatus.COMPLETED
|
task.status = LoadingStatus.COMPLETED
|
||||||
task.completed_at = datetime.now(timezone.utc)
|
task.completed_at = datetime.now(timezone.utc)
|
||||||
@@ -451,12 +450,15 @@ class BackgroundLoaderService:
|
|||||||
logger.exception(f"Failed to load episodes for {task.key}: {e}")
|
logger.exception(f"Failed to load episodes for {task.key}: {e}")
|
||||||
raise
|
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.
|
"""Load NFO file and images for a series by reusing NFOService.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
task: The loading task
|
task: The loading task
|
||||||
db: Database session
|
db: Database session
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if NFO was created, False if it already existed or failed
|
||||||
"""
|
"""
|
||||||
task.status = LoadingStatus.LOADING_NFO
|
task.status = LoadingStatus.LOADING_NFO
|
||||||
await self._broadcast_status(task, "Checking NFO file...")
|
await self._broadcast_status(task, "Checking NFO file...")
|
||||||
@@ -470,7 +472,7 @@ class BackgroundLoaderService:
|
|||||||
task.progress["nfo"] = False
|
task.progress["nfo"] = False
|
||||||
task.progress["logo"] = False
|
task.progress["logo"] = False
|
||||||
task.progress["images"] = False
|
task.progress["images"] = False
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Check if NFO already exists
|
# Check if NFO already exists
|
||||||
if self.series_app.nfo_service.has_nfo(task.folder):
|
if self.series_app.nfo_service.has_nfo(task.folder):
|
||||||
@@ -497,7 +499,7 @@ class BackgroundLoaderService:
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
logger.info(f"Existing NFO found and database updated for series: {task.key}")
|
logger.info(f"Existing NFO found and database updated for series: {task.key}")
|
||||||
return
|
return False
|
||||||
|
|
||||||
# NFO doesn't exist, create it
|
# NFO doesn't exist, create it
|
||||||
await self._broadcast_status(task, "Generating NFO file...")
|
await self._broadcast_status(task, "Generating NFO file...")
|
||||||
@@ -531,6 +533,7 @@ class BackgroundLoaderService:
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
logger.info(f"NFO and images created and loaded for series: {task.key}")
|
logger.info(f"NFO and images created and loaded for series: {task.key}")
|
||||||
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Failed to load NFO/images for {task.key}: {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["nfo"] = False
|
||||||
task.progress["logo"] = False
|
task.progress["logo"] = False
|
||||||
task.progress["images"] = 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(
|
async def _broadcast_status(
|
||||||
self,
|
self,
|
||||||
@@ -567,7 +633,9 @@ class BackgroundLoaderService:
|
|||||||
payload = {
|
payload = {
|
||||||
"type": "series_loading_update",
|
"type": "series_loading_update",
|
||||||
"key": task.key,
|
"key": task.key,
|
||||||
|
"series_key": task.key, # For frontend compatibility
|
||||||
"folder": task.folder,
|
"folder": task.folder,
|
||||||
|
"status": task.status.value, # For frontend compatibility
|
||||||
"loading_status": task.status.value,
|
"loading_status": task.status.value,
|
||||||
"progress": task.progress,
|
"progress": task.progress,
|
||||||
"message": message,
|
"message": message,
|
||||||
|
|||||||
Reference in New Issue
Block a user