refactor: remove database access from core layer
- Remove db_session parameter from SeriesApp, SerieList, SerieScanner - Move all database operations to AnimeService (service layer) - Add add_series_to_db, contains_in_db methods to AnimeService - Update sync_series_from_data_files to use inline DB operations - Remove obsolete test classes for removed DB methods - Fix pylint issues: add broad-except comments, fix line lengths - Core layer (src/core/) now has zero database imports 722 unit tests pass
This commit is contained in:
@@ -142,7 +142,7 @@ class AnimeService:
|
||||
),
|
||||
loop
|
||||
)
|
||||
except Exception as exc:
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
logger.error(
|
||||
"Error handling download status event",
|
||||
error=str(exc)
|
||||
@@ -221,7 +221,7 @@ class AnimeService:
|
||||
),
|
||||
loop
|
||||
)
|
||||
except Exception as exc:
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
logger.error("Error handling scan status event: %s", exc)
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
@@ -288,6 +288,8 @@ class AnimeService:
|
||||
The SeriesApp handles progress tracking via events which are
|
||||
forwarded to the ProgressService through event handlers.
|
||||
|
||||
After scanning, results are persisted to the database.
|
||||
|
||||
All series are identified by their 'key' (provider identifier),
|
||||
with 'folder' stored as metadata.
|
||||
"""
|
||||
@@ -295,19 +297,268 @@ class AnimeService:
|
||||
# Store event loop for event handlers
|
||||
self._event_loop = asyncio.get_running_loop()
|
||||
|
||||
# SeriesApp.rescan is now async and handles events internally
|
||||
await self._app.rescan()
|
||||
# SeriesApp.rescan returns scanned series list
|
||||
scanned_series = await self._app.rescan()
|
||||
|
||||
# Persist scan results to database
|
||||
if scanned_series:
|
||||
await self._save_scan_results_to_db(scanned_series)
|
||||
|
||||
# Reload series from database to ensure consistency
|
||||
await self._load_series_from_db()
|
||||
|
||||
# invalidate cache
|
||||
try:
|
||||
self._cached_list_missing.cache_clear()
|
||||
except Exception:
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
except Exception as exc:
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
logger.exception("rescan failed")
|
||||
raise AnimeServiceError("Rescan failed") from exc
|
||||
|
||||
async def _save_scan_results_to_db(self, series_list: list) -> int:
|
||||
"""
|
||||
Save scan results to the database.
|
||||
|
||||
Creates or updates series records in the database based on
|
||||
scan results.
|
||||
|
||||
Args:
|
||||
series_list: List of Serie objects from scan
|
||||
|
||||
Returns:
|
||||
Number of series saved/updated
|
||||
"""
|
||||
from src.server.database.connection import get_db_session
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
|
||||
saved_count = 0
|
||||
|
||||
async with get_db_session() as db:
|
||||
for serie in series_list:
|
||||
try:
|
||||
# Check if series already exists
|
||||
existing = await AnimeSeriesService.get_by_key(
|
||||
db, serie.key
|
||||
)
|
||||
|
||||
if existing:
|
||||
# Update existing series
|
||||
await self._update_series_in_db(
|
||||
serie, existing, db
|
||||
)
|
||||
else:
|
||||
# Create new series
|
||||
await self._create_series_in_db(serie, db)
|
||||
|
||||
saved_count += 1
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.warning(
|
||||
"Failed to save series to database: %s (key=%s) - %s",
|
||||
serie.name,
|
||||
serie.key,
|
||||
str(e)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Saved %d series to database from scan results",
|
||||
saved_count
|
||||
)
|
||||
return saved_count
|
||||
|
||||
async def _create_series_in_db(self, serie, db) -> None:
|
||||
"""Create a new series in the database."""
|
||||
from src.server.database.service import (
|
||||
AnimeSeriesService, EpisodeService
|
||||
)
|
||||
|
||||
anime_series = await AnimeSeriesService.create(
|
||||
db=db,
|
||||
key=serie.key,
|
||||
name=serie.name,
|
||||
site=serie.site,
|
||||
folder=serie.folder,
|
||||
)
|
||||
|
||||
# Create Episode records
|
||||
if serie.episodeDict:
|
||||
for season, episode_numbers in serie.episodeDict.items():
|
||||
for ep_num in episode_numbers:
|
||||
await EpisodeService.create(
|
||||
db=db,
|
||||
series_id=anime_series.id,
|
||||
season=season,
|
||||
episode_number=ep_num,
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"Created series in database: %s (key=%s)",
|
||||
serie.name,
|
||||
serie.key
|
||||
)
|
||||
|
||||
async def _update_series_in_db(self, serie, existing, db) -> None:
|
||||
"""Update an existing series in the database."""
|
||||
from src.server.database.service import (
|
||||
AnimeSeriesService, EpisodeService
|
||||
)
|
||||
|
||||
# Get existing episodes
|
||||
existing_episodes = await EpisodeService.get_by_series(db, existing.id)
|
||||
existing_dict: dict[int, list[int]] = {}
|
||||
for ep in existing_episodes:
|
||||
if ep.season not in existing_dict:
|
||||
existing_dict[ep.season] = []
|
||||
existing_dict[ep.season].append(ep.episode_number)
|
||||
for season in existing_dict:
|
||||
existing_dict[season].sort()
|
||||
|
||||
# Update episodes if changed
|
||||
if existing_dict != serie.episodeDict:
|
||||
new_dict = serie.episodeDict or {}
|
||||
for season, episode_numbers in new_dict.items():
|
||||
existing_eps = set(existing_dict.get(season, []))
|
||||
for ep_num in episode_numbers:
|
||||
if ep_num not in existing_eps:
|
||||
await EpisodeService.create(
|
||||
db=db,
|
||||
series_id=existing.id,
|
||||
season=season,
|
||||
episode_number=ep_num,
|
||||
)
|
||||
|
||||
# Update folder if changed
|
||||
if existing.folder != serie.folder:
|
||||
await AnimeSeriesService.update(
|
||||
db,
|
||||
existing.id,
|
||||
folder=serie.folder
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"Updated series in database: %s (key=%s)",
|
||||
serie.name,
|
||||
serie.key
|
||||
)
|
||||
|
||||
async def _load_series_from_db(self) -> None:
|
||||
"""
|
||||
Load series from the database into SeriesApp.
|
||||
|
||||
This method is called during initialization and after rescans
|
||||
to ensure the in-memory series list is in sync with the database.
|
||||
"""
|
||||
from src.core.entities.series import Serie
|
||||
from src.server.database.connection import get_db_session
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
|
||||
async with get_db_session() as db:
|
||||
anime_series_list = await AnimeSeriesService.get_all(
|
||||
db, with_episodes=True
|
||||
)
|
||||
|
||||
# Convert to Serie objects
|
||||
series_list = []
|
||||
for anime_series in anime_series_list:
|
||||
# Build episode_dict from episodes relationship
|
||||
episode_dict: dict[int, list[int]] = {}
|
||||
if anime_series.episodes:
|
||||
for episode in anime_series.episodes:
|
||||
season = episode.season
|
||||
if season not in episode_dict:
|
||||
episode_dict[season] = []
|
||||
episode_dict[season].append(episode.episode_number)
|
||||
# Sort episode numbers
|
||||
for season in episode_dict:
|
||||
episode_dict[season].sort()
|
||||
|
||||
serie = Serie(
|
||||
key=anime_series.key,
|
||||
name=anime_series.name,
|
||||
site=anime_series.site,
|
||||
folder=anime_series.folder,
|
||||
episodeDict=episode_dict
|
||||
)
|
||||
series_list.append(serie)
|
||||
|
||||
# Load into SeriesApp
|
||||
self._app.load_series_from_list(series_list)
|
||||
|
||||
async def add_series_to_db(
|
||||
self,
|
||||
serie,
|
||||
db
|
||||
):
|
||||
"""
|
||||
Add a series to the database if it doesn't already exist.
|
||||
|
||||
Uses serie.key for identification. Creates a new AnimeSeries
|
||||
record in the database if it doesn't already exist.
|
||||
|
||||
Args:
|
||||
serie: The Serie instance to add
|
||||
db: Database session for async operations
|
||||
|
||||
Returns:
|
||||
Created AnimeSeries instance, or None if already exists
|
||||
"""
|
||||
from src.server.database.service import AnimeSeriesService, EpisodeService
|
||||
|
||||
# Check if series already exists in DB
|
||||
existing = await AnimeSeriesService.get_by_key(db, serie.key)
|
||||
if existing:
|
||||
logger.debug(
|
||||
"Series already exists in database: %s (key=%s)",
|
||||
serie.name,
|
||||
serie.key
|
||||
)
|
||||
return None
|
||||
|
||||
# Create new series in database
|
||||
anime_series = await AnimeSeriesService.create(
|
||||
db=db,
|
||||
key=serie.key,
|
||||
name=serie.name,
|
||||
site=serie.site,
|
||||
folder=serie.folder,
|
||||
)
|
||||
|
||||
# Create Episode records for each episode in episodeDict
|
||||
if serie.episodeDict:
|
||||
for season, episode_numbers in serie.episodeDict.items():
|
||||
for episode_number in episode_numbers:
|
||||
await EpisodeService.create(
|
||||
db=db,
|
||||
series_id=anime_series.id,
|
||||
season=season,
|
||||
episode_number=episode_number,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Added series to database: %s (key=%s)",
|
||||
serie.name,
|
||||
serie.key
|
||||
)
|
||||
|
||||
return anime_series
|
||||
|
||||
async def contains_in_db(self, key: str, db) -> bool:
|
||||
"""
|
||||
Check if a series with the given key exists in the database.
|
||||
|
||||
Args:
|
||||
key: The unique provider identifier for the series
|
||||
db: Database session for async operations
|
||||
|
||||
Returns:
|
||||
True if the series exists in the database
|
||||
"""
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
|
||||
existing = await AnimeSeriesService.get_by_key(db, key)
|
||||
return existing is not None
|
||||
|
||||
async def download(
|
||||
self,
|
||||
serie_folder: str,
|
||||
@@ -365,7 +616,7 @@ def get_anime_service(series_app: SeriesApp) -> AnimeService:
|
||||
|
||||
async def sync_series_from_data_files(
|
||||
anime_directory: str,
|
||||
logger=None
|
||||
log_instance=None
|
||||
) -> int:
|
||||
"""
|
||||
Sync series from data files to the database.
|
||||
@@ -379,17 +630,17 @@ async def sync_series_from_data_files(
|
||||
|
||||
Args:
|
||||
anime_directory: Path to the anime directory with data files
|
||||
logger: Optional logger instance for logging operations.
|
||||
log_instance: Optional logger instance for logging operations.
|
||||
If not provided, uses structlog.
|
||||
|
||||
Returns:
|
||||
Number of new series added to the database
|
||||
"""
|
||||
log = logger or structlog.get_logger(__name__)
|
||||
log = log_instance or structlog.get_logger(__name__)
|
||||
|
||||
try:
|
||||
from src.core.entities.SerieList import SerieList
|
||||
from src.server.database.connection import get_db_session
|
||||
from src.server.database.service import AnimeSeriesService, EpisodeService
|
||||
|
||||
log.info(
|
||||
"Starting data file to database sync",
|
||||
@@ -412,11 +663,6 @@ async def sync_series_from_data_files(
|
||||
)
|
||||
|
||||
async with get_db_session() as db:
|
||||
serie_list = SerieList(
|
||||
anime_directory,
|
||||
db_session=db,
|
||||
skip_load=True
|
||||
)
|
||||
added_count = 0
|
||||
skipped_count = 0
|
||||
for serie in all_series:
|
||||
@@ -438,15 +684,43 @@ async def sync_series_from_data_files(
|
||||
continue
|
||||
|
||||
try:
|
||||
result = await serie_list.add_to_db(serie, db)
|
||||
if result:
|
||||
added_count += 1
|
||||
# Check if series already exists in DB
|
||||
existing = await AnimeSeriesService.get_by_key(db, serie.key)
|
||||
if existing:
|
||||
log.debug(
|
||||
"Added series to database",
|
||||
"Series already exists in database",
|
||||
name=serie.name,
|
||||
key=serie.key
|
||||
)
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
# Create new series in database
|
||||
anime_series = await AnimeSeriesService.create(
|
||||
db=db,
|
||||
key=serie.key,
|
||||
name=serie.name,
|
||||
site=serie.site,
|
||||
folder=serie.folder,
|
||||
)
|
||||
|
||||
# Create Episode records for each episode in episodeDict
|
||||
if serie.episodeDict:
|
||||
for season, episode_numbers in serie.episodeDict.items():
|
||||
for episode_number in episode_numbers:
|
||||
await EpisodeService.create(
|
||||
db=db,
|
||||
series_id=anime_series.id,
|
||||
season=season,
|
||||
episode_number=episode_number,
|
||||
)
|
||||
|
||||
added_count += 1
|
||||
log.debug(
|
||||
"Added series to database",
|
||||
name=serie.name,
|
||||
key=serie.key
|
||||
)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
log.warning(
|
||||
"Failed to add series to database",
|
||||
key=serie.key,
|
||||
@@ -462,7 +736,7 @@ async def sync_series_from_data_files(
|
||||
)
|
||||
return added_count
|
||||
|
||||
except Exception as e:
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
log.warning(
|
||||
"Failed to sync series to database",
|
||||
error=str(e),
|
||||
|
||||
Reference in New Issue
Block a user