feat(scanner): add DB fallback for series key resolution
When SerieScanner encounters a folder without a local key or data file, it now optionally falls back to a database lookup by folder name. This prevents newly-added series from being silently skipped on rescan when their metadata only lives in the DB. Changes: - SerieScanner accepts an optional db_lookup callable - SeriesApp forwards db_lookup to SerieScanner - AnimeSeriesService adds get_by_folder_sync() helper - dependencies.py wires a sync DB lookup into get_series_app() - Unit tests cover fallback hit, miss, and exception paths
This commit is contained in:
@@ -148,7 +148,27 @@ class AnimeSeriesService:
|
||||
select(AnimeSeries).where(AnimeSeries.key == key)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_by_folder_sync(db: Session, folder: str) -> Optional[AnimeSeries]:
|
||||
"""Look up an anime series by its filesystem folder name (sync).
|
||||
|
||||
Intended as a fallback for ``SerieScanner`` when neither a ``key``
|
||||
file nor a ``data`` file exists on disk for a given folder.
|
||||
|
||||
Args:
|
||||
db: Synchronous database session (from ``get_sync_session``).
|
||||
folder: Filesystem folder name to match (e.g.
|
||||
``"Rooster Fighter (2026)"``).
|
||||
|
||||
Returns:
|
||||
``AnimeSeries`` instance or ``None`` if not found.
|
||||
"""
|
||||
result = db.execute(
|
||||
select(AnimeSeries).where(AnimeSeries.folder == folder)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@staticmethod
|
||||
async def get_all(
|
||||
db: AsyncSession,
|
||||
|
||||
@@ -57,6 +57,44 @@ _rate_limit_lock = Lock()
|
||||
_RATE_LIMIT_WINDOW_SECONDS = 60.0
|
||||
|
||||
|
||||
def _make_db_lookup():
|
||||
"""Build a synchronous ``(folder) -> Serie | None`` callable for SerieScanner.
|
||||
|
||||
The returned function opens a short-lived sync DB session, queries for a
|
||||
series whose ``folder`` column matches the given name, and converts the
|
||||
ORM row to a ``Serie`` domain object. Returns ``None`` when the DB is not
|
||||
yet initialised or no matching row is found.
|
||||
"""
|
||||
from src.core.entities.series import Serie
|
||||
|
||||
def _lookup(folder: str) -> Optional["Serie"]:
|
||||
try:
|
||||
from src.server.database.connection import get_sync_session
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
|
||||
db = get_sync_session()
|
||||
try:
|
||||
row = AnimeSeriesService.get_by_folder_sync(db, folder)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if row is None:
|
||||
return None
|
||||
return Serie(
|
||||
key=row.key,
|
||||
name=row.name or "",
|
||||
site=row.site,
|
||||
folder=row.folder,
|
||||
episodeDict={},
|
||||
year=row.year,
|
||||
)
|
||||
except RuntimeError:
|
||||
# DB not initialised yet (e.g. first boot before init_db())
|
||||
return None
|
||||
|
||||
return _lookup
|
||||
|
||||
|
||||
def get_series_app() -> SeriesApp:
|
||||
"""
|
||||
Dependency to get SeriesApp instance.
|
||||
@@ -134,7 +172,7 @@ def get_series_app() -> SeriesApp:
|
||||
),
|
||||
)
|
||||
|
||||
_series_app = SeriesApp(anime_dir)
|
||||
_series_app = SeriesApp(anime_dir, db_lookup=_make_db_lookup())
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
Reference in New Issue
Block a user