fix: ensure series loaded from DB before NFO scan
- Call _load_series_into_memory() before NFO scan phases to sync DB to SeriesApp memory, fixing missing NFO for recently resolved folders - Add TMDB lookup for series without cached tmdb_id during NFO creation - Add get_tmdb_client() factory and get_tmdb_image_base_url() helpers - Fix: use get_tv_show_details instead of deprecated get_series_details - Fix tests: mock _load_series_into_memory in NFO scan tests
This commit is contained in:
@@ -422,3 +422,32 @@ class TMDBClient:
|
|||||||
if expired_keys:
|
if expired_keys:
|
||||||
logger.debug("Removed %d expired negative cache entries", len(expired_keys))
|
logger.debug("Removed %d expired negative cache entries", len(expired_keys))
|
||||||
return len(expired_keys)
|
return len(expired_keys)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tmdb_client() -> TMDBClient:
|
||||||
|
"""Factory function to create a TMDBClient with settings configuration.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TMDBClient instance configured with settings.tmdb_api_key
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If TMDB API key is not configured
|
||||||
|
"""
|
||||||
|
from src.config.settings import settings
|
||||||
|
|
||||||
|
if not settings.tmdb_api_key:
|
||||||
|
raise ValueError("TMDB API key is not configured")
|
||||||
|
|
||||||
|
return TMDBClient(api_key=settings.tmdb_api_key)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tmdb_image_base_url(tmdb_id: int) -> str:
|
||||||
|
"""Get the base URL for TMDB images.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tmdb_id: TMDB show ID (used for account-specific URLs)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Base URL string for TMDB images
|
||||||
|
"""
|
||||||
|
return "https://image.tmdb.org/t/p/"
|
||||||
|
|||||||
@@ -541,6 +541,8 @@ async def perform_nfo_scan_if_needed(progress_service=None):
|
|||||||
|
|
||||||
# Execute the NFO scan
|
# Execute the NFO scan
|
||||||
try:
|
try:
|
||||||
|
# Ensure any newly created series are loaded from DB into SeriesApp memory
|
||||||
|
await _load_series_into_memory(progress_service=None)
|
||||||
await _execute_nfo_scan(progress_service)
|
await _execute_nfo_scan(progress_service)
|
||||||
await _mark_nfo_scan_completed()
|
await _mark_nfo_scan_completed()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -615,6 +617,9 @@ async def perform_nfo_scan_phase(progress_service=None):
|
|||||||
|
|
||||||
# Execute the NFO scan
|
# Execute the NFO scan
|
||||||
try:
|
try:
|
||||||
|
# Ensure any newly created series (e.g., from resolving unresolved folders)
|
||||||
|
# are loaded from DB into SeriesApp memory before scanning
|
||||||
|
await _load_series_into_memory(progress_service=None)
|
||||||
await _execute_nfo_scan(progress_service)
|
await _execute_nfo_scan(progress_service)
|
||||||
await _mark_nfo_scan_completed()
|
await _mark_nfo_scan_completed()
|
||||||
|
|
||||||
|
|||||||
@@ -326,6 +326,22 @@ class NfoScanService:
|
|||||||
|
|
||||||
nfo_exists = os.path.isfile(nfo_path)
|
nfo_exists = os.path.isfile(nfo_path)
|
||||||
|
|
||||||
|
# If tmdb_id is missing, try to look it up by series name
|
||||||
|
if not series_data.get("tmdb_id"):
|
||||||
|
logger.debug("No tmdb_id for %s — attempting TMDB lookup", key)
|
||||||
|
name = series_data.get("name", "")
|
||||||
|
found_tmdb_id = await self._lookup_tmdb_id_by_name(name)
|
||||||
|
if found_tmdb_id:
|
||||||
|
series_data["tmdb_id"] = found_tmdb_id
|
||||||
|
await self._save_tmdb_id(key, found_tmdb_id)
|
||||||
|
logger.info("Found and saved tmdb_id %s for %s", found_tmdb_id, key)
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"Could not resolve tmdb_id for %s (%s)",
|
||||||
|
key,
|
||||||
|
name,
|
||||||
|
)
|
||||||
|
|
||||||
if not nfo_exists:
|
if not nfo_exists:
|
||||||
# Create new NFO
|
# Create new NFO
|
||||||
logger.info("Creating NFO for series: %s (%s)", key, folder)
|
logger.info("Creating NFO for series: %s (%s)", key, folder)
|
||||||
@@ -526,6 +542,53 @@ class NfoScanService:
|
|||||||
logger.info("Regenerated NFO for %s", key)
|
logger.info("Regenerated NFO for %s", key)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
async def _save_tmdb_id(self, key: str, tmdb_id: int) -> None:
|
||||||
|
"""Save tmdb_id to the database for a series.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: Series key (primary identifier)
|
||||||
|
tmdb_id: TMDB series ID to save
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from src.server.database.connection import get_db_session
|
||||||
|
from src.server.database.service import AnimeSeriesService
|
||||||
|
|
||||||
|
async with get_db_session() as db:
|
||||||
|
series = await AnimeSeriesService.get_by_key(db, key)
|
||||||
|
if series:
|
||||||
|
series.tmdb_id = tmdb_id
|
||||||
|
await db.flush()
|
||||||
|
logger.debug("Saved tmdb_id %s for series: %s", tmdb_id, key)
|
||||||
|
else:
|
||||||
|
logger.warning("Series not found for tmdb_id save: %s", key)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("Failed to save tmdb_id for %s: %s", key, exc)
|
||||||
|
|
||||||
|
async def _lookup_tmdb_id_by_name(self, name: str) -> Optional[int]:
|
||||||
|
"""Look up a TMDB series ID by series name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Series name to search for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TMDB series ID or None if not found.
|
||||||
|
"""
|
||||||
|
if not name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from src.server.nfo.tmdb_client import get_tmdb_client
|
||||||
|
|
||||||
|
client = get_tmdb_client()
|
||||||
|
results = await client.search_tv_show(name)
|
||||||
|
if results and results.get("results"):
|
||||||
|
first_result = results["results"][0]
|
||||||
|
return first_result.get("id")
|
||||||
|
return None
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("TMDB lookup failed for %s: %s", name, exc)
|
||||||
|
return None
|
||||||
|
|
||||||
async def _fetch_tmdb_data(self, tmdb_id: int) -> Optional[Dict[str, Any]]:
|
async def _fetch_tmdb_data(self, tmdb_id: int) -> Optional[Dict[str, Any]]:
|
||||||
"""Fetch series metadata from TMDB API.
|
"""Fetch series metadata from TMDB API.
|
||||||
|
|
||||||
@@ -539,7 +602,7 @@ class NfoScanService:
|
|||||||
from src.server.nfo.tmdb_client import get_tmdb_client
|
from src.server.nfo.tmdb_client import get_tmdb_client
|
||||||
|
|
||||||
client = get_tmdb_client()
|
client = get_tmdb_client()
|
||||||
data = await client.get_series_details(tmdb_id)
|
data = await client.get_tv_show_details(tmdb_id)
|
||||||
return data
|
return data
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("TMDB fetch failed for TMDB ID %s: %s", tmdb_id, exc)
|
logger.warning("TMDB fetch failed for TMDB ID %s: %s", tmdb_id, exc)
|
||||||
|
|||||||
@@ -532,6 +532,8 @@ class TestPerformNFOScan:
|
|||||||
new_callable=AsyncMock, return_value=False), \
|
new_callable=AsyncMock, return_value=False), \
|
||||||
patch('src.server.services.initialization_service._is_nfo_scan_configured',
|
patch('src.server.services.initialization_service._is_nfo_scan_configured',
|
||||||
new_callable=AsyncMock, return_value=True), \
|
new_callable=AsyncMock, return_value=True), \
|
||||||
|
patch('src.server.services.initialization_service._load_series_into_memory',
|
||||||
|
new_callable=AsyncMock), \
|
||||||
patch('src.server.services.initialization_service._execute_nfo_scan',
|
patch('src.server.services.initialization_service._execute_nfo_scan',
|
||||||
new_callable=AsyncMock), \
|
new_callable=AsyncMock), \
|
||||||
patch('src.server.services.initialization_service._mark_nfo_scan_completed',
|
patch('src.server.services.initialization_service._mark_nfo_scan_completed',
|
||||||
@@ -549,6 +551,8 @@ class TestPerformNFOScan:
|
|||||||
new_callable=AsyncMock, return_value=False), \
|
new_callable=AsyncMock, return_value=False), \
|
||||||
patch('src.server.services.initialization_service._is_nfo_scan_configured',
|
patch('src.server.services.initialization_service._is_nfo_scan_configured',
|
||||||
new_callable=AsyncMock, return_value=True), \
|
new_callable=AsyncMock, return_value=True), \
|
||||||
|
patch('src.server.services.initialization_service._load_series_into_memory',
|
||||||
|
new_callable=AsyncMock), \
|
||||||
patch('src.server.services.initialization_service._execute_nfo_scan',
|
patch('src.server.services.initialization_service._execute_nfo_scan',
|
||||||
new_callable=AsyncMock), \
|
new_callable=AsyncMock), \
|
||||||
patch('src.server.services.initialization_service._mark_nfo_scan_completed',
|
patch('src.server.services.initialization_service._mark_nfo_scan_completed',
|
||||||
|
|||||||
Reference in New Issue
Block a user