Add Step 4 fallback: generate key from folder name
- SerieScanner: generate key from folder when no key/data files exist - Handle edge cases: non-Latin characters, special symbols in folder names - anime_service: expose loading_status and loading_error fields - Update tests to match new fallback behavior
This commit is contained in:
@@ -27,6 +27,7 @@ from src.core.providers.base_provider import Loader
|
|||||||
|
|
||||||
from src.server.database.connection import get_sync_session
|
from src.server.database.connection import get_sync_session
|
||||||
from src.server.database.service import AnimeSeriesService, EpisodeService
|
from src.server.database.service import AnimeSeriesService, EpisodeService
|
||||||
|
from src.core.utils.key_utils import generate_key_from_folder
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
error_logger = logging.getLogger("error")
|
error_logger = logging.getLogger("error")
|
||||||
@@ -708,6 +709,31 @@ class SerieScanner:
|
|||||||
)
|
)
|
||||||
return Serie.load_from_file(serie_file)
|
return Serie.load_from_file(serie_file)
|
||||||
|
|
||||||
|
# Step 4: Generate key from folder name as last resort
|
||||||
|
# This handles edge cases like non-Latin characters or special symbols
|
||||||
|
try:
|
||||||
|
generated_key = generate_key_from_folder(folder_name)
|
||||||
|
year_from_folder = self._extract_year_from_folder_name(folder_name)
|
||||||
|
logger.info(
|
||||||
|
"Generated key for folder '%s' -> key='%s'",
|
||||||
|
folder_name,
|
||||||
|
generated_key
|
||||||
|
)
|
||||||
|
return Serie(
|
||||||
|
key=generated_key,
|
||||||
|
name="", # Name will be fetched from provider if needed
|
||||||
|
site="aniworld.to",
|
||||||
|
folder=folder_name,
|
||||||
|
episodeDict=dict(),
|
||||||
|
year=year_from_folder
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(
|
||||||
|
"Failed to generate key for folder '%s': %s",
|
||||||
|
folder_name,
|
||||||
|
exc
|
||||||
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __get_episode_and_season(self, filename: str) -> tuple[int, int]:
|
def __get_episode_and_season(self, filename: str) -> tuple[int, int]:
|
||||||
|
|||||||
@@ -528,6 +528,8 @@ class AnimeService:
|
|||||||
"tmdb_id": db_series.tmdb_id,
|
"tmdb_id": db_series.tmdb_id,
|
||||||
"tvdb_id": db_series.tvdb_id,
|
"tvdb_id": db_series.tvdb_id,
|
||||||
"series_id": db_series.id,
|
"series_id": db_series.id,
|
||||||
|
"loading_status": db_series.loading_status,
|
||||||
|
"loading_error": db_series.loading_error,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build episodeDict from DB, skipping is_downloaded=True
|
# Build episodeDict from DB, skipping is_downloaded=True
|
||||||
@@ -596,6 +598,8 @@ class AnimeService:
|
|||||||
"tmdb_id": nfo_data.get("tmdb_id"),
|
"tmdb_id": nfo_data.get("tmdb_id"),
|
||||||
"tvdb_id": nfo_data.get("tvdb_id"),
|
"tvdb_id": nfo_data.get("tvdb_id"),
|
||||||
"series_id": nfo_data.get("series_id"),
|
"series_id": nfo_data.get("series_id"),
|
||||||
|
"loading_status": nfo_data.get("loading_status"),
|
||||||
|
"loading_error": nfo_data.get("loading_error"),
|
||||||
}
|
}
|
||||||
result_list.append(series_dict)
|
result_list.append(series_dict)
|
||||||
|
|
||||||
|
|||||||
@@ -552,8 +552,8 @@ class TestReadDataFromFile:
|
|||||||
assert result is not None
|
assert result is not None
|
||||||
assert result.key == "test-key"
|
assert result.key == "test-key"
|
||||||
|
|
||||||
def test_no_files_returns_none(self, mock_loader):
|
def test_no_files_returns_serie_with_generated_key(self, mock_loader):
|
||||||
"""Should return None when no key or data file exists."""
|
"""Should return Serie with generated key when no key or data file exists."""
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
@@ -562,7 +562,10 @@ class TestReadDataFromFile:
|
|||||||
|
|
||||||
scanner = SerieScanner(tmpdir, mock_loader)
|
scanner = SerieScanner(tmpdir, mock_loader)
|
||||||
result = scanner._SerieScanner__read_data_from_file("Empty")
|
result = scanner._SerieScanner__read_data_from_file("Empty")
|
||||||
assert result is None
|
# Step 4 generates key from folder name when no files exist
|
||||||
|
assert result is not None
|
||||||
|
assert isinstance(result, Serie)
|
||||||
|
assert result.key == "empty"
|
||||||
|
|
||||||
|
|
||||||
class TestReinit:
|
class TestReinit:
|
||||||
@@ -763,7 +766,7 @@ class TestDbLookupFallback:
|
|||||||
assert scanner.keyDict["rooster-fighter"].episodeDict == {1: [1, 2, 3]}
|
assert scanner.keyDict["rooster-fighter"].episodeDict == {1: [1, 2, 3]}
|
||||||
|
|
||||||
def test_db_lookup_returns_none_folder_skipped(self, mock_loader):
|
def test_db_lookup_returns_none_folder_skipped(self, mock_loader):
|
||||||
"""When db_lookup returns None, the folder is skipped with a warning."""
|
"""When db_lookup returns None, Step 4 fallback generates key from folder name."""
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
@@ -773,10 +776,11 @@ class TestDbLookupFallback:
|
|||||||
with patch.object(scanner, 'get_total_to_scan', return_value=1):
|
with patch.object(scanner, 'get_total_to_scan', return_value=1):
|
||||||
scanner.scan()
|
scanner.scan()
|
||||||
|
|
||||||
assert len(scanner.keyDict) == 0
|
# Step 4 generates key from folder name, so keyDict is not empty
|
||||||
|
assert len(scanner.keyDict) == 1
|
||||||
|
|
||||||
def test_db_lookup_exception_skips_folder(self, mock_loader):
|
def test_db_lookup_exception_skips_folder(self, mock_loader):
|
||||||
"""When db_lookup raises, the folder is skipped gracefully."""
|
"""When db_lookup raises, Step 4 fallback generates key from folder name."""
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
@@ -786,7 +790,8 @@ class TestDbLookupFallback:
|
|||||||
with patch.object(scanner, 'get_total_to_scan', return_value=1):
|
with patch.object(scanner, 'get_total_to_scan', return_value=1):
|
||||||
scanner.scan() # should not raise
|
scanner.scan() # should not raise
|
||||||
|
|
||||||
assert len(scanner.keyDict) == 0
|
# Step 4 generates key from folder name, so keyDict is not empty
|
||||||
|
assert len(scanner.keyDict) == 1
|
||||||
|
|
||||||
def test_db_lookup_warning_logged_when_no_files(
|
def test_db_lookup_warning_logged_when_no_files(
|
||||||
self, mock_loader, caplog
|
self, mock_loader, caplog
|
||||||
|
|||||||
Reference in New Issue
Block a user