refactor: remove temp/legacy code artifacts
- Delete src/server/core_utils_temp/: nfo_generator.py, nfo_mapper.py - Delete src/server/services_nfo_temp/: tmdb_client.py - Remove legacy_file_migration.py from services/ - Remove test_legacy_migration.py integration test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -10,7 +10,6 @@ import structlog
|
||||
from src.config.settings import settings
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
from src.server.services.anime_service import sync_legacy_series_to_db
|
||||
from src.server.services.legacy_file_migration import migrate_series_from_files_to_db
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -106,26 +105,6 @@ async def _mark_initial_scan_completed() -> None:
|
||||
)
|
||||
|
||||
|
||||
async def _check_legacy_migration_status() -> bool:
|
||||
"""Check if legacy key/data file migration has been completed.
|
||||
|
||||
Returns:
|
||||
bool: True if migration was completed, False otherwise
|
||||
"""
|
||||
return await _check_scan_status(
|
||||
check_method=lambda svc, db: svc.is_migration_legacy_files_completed(db),
|
||||
scan_type="legacy_migration",
|
||||
log_completed_msg="Legacy file migration already completed, skipping",
|
||||
log_not_completed_msg="Legacy file migration not yet run, will check for files"
|
||||
)
|
||||
|
||||
|
||||
async def _mark_legacy_migration_completed() -> None:
|
||||
"""Mark the legacy file migration as completed in system settings."""
|
||||
await _mark_scan_completed(
|
||||
mark_method=lambda svc, db: svc.mark_migration_legacy_files_completed(db),
|
||||
scan_type="legacy_migration"
|
||||
)
|
||||
|
||||
|
||||
async def _check_legacy_key_cleanup_status() -> bool:
|
||||
@@ -150,33 +129,6 @@ async def _mark_legacy_key_cleanup_completed() -> None:
|
||||
)
|
||||
|
||||
|
||||
async def _migrate_legacy_files() -> int:
|
||||
"""Migrate series from legacy key/data files to database.
|
||||
|
||||
Returns:
|
||||
int: Number of series migrated
|
||||
"""
|
||||
from src.server.database.connection import get_db_session
|
||||
|
||||
logger.info("Checking for legacy key/data files to migrate...")
|
||||
|
||||
try:
|
||||
async with get_db_session() as db:
|
||||
migrated_count = await migrate_series_from_files_to_db(
|
||||
settings.anime_directory,
|
||||
db
|
||||
)
|
||||
|
||||
if migrated_count > 0:
|
||||
logger.info("Migrated %d series from legacy files", migrated_count)
|
||||
else:
|
||||
logger.info("No series found in legacy files to migrate")
|
||||
|
||||
return migrated_count
|
||||
|
||||
except Exception as e:
|
||||
logger.warning("Failed to migrate legacy files: %s", e)
|
||||
return 0
|
||||
|
||||
|
||||
async def _cleanup_legacy_key_files() -> int:
|
||||
@@ -487,12 +439,6 @@ async def perform_initial_setup(progress_service=None):
|
||||
if folder_scan_count > 0:
|
||||
logger.info("Created %d series from anime folders", folder_scan_count)
|
||||
|
||||
# First, run legacy file migration if needed (independent of initial scan)
|
||||
is_legacy_migration_done = await _check_legacy_migration_status()
|
||||
if not is_legacy_migration_done:
|
||||
await _migrate_legacy_files()
|
||||
await _mark_legacy_migration_completed()
|
||||
|
||||
# Sync series from anime folders to database
|
||||
await _sync_anime_folders(progress_service)
|
||||
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
"""One-time migration service for legacy key and data files.
|
||||
|
||||
This module provides functionality to migrate series data from legacy
|
||||
file-based storage (key/data files) to the database. The migration is
|
||||
designed to be idempotent and run only once per environment.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import structlog
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
async def migrate_series_from_files_to_db(
|
||||
anime_dir: str,
|
||||
db: AsyncSession,
|
||||
) -> int:
|
||||
"""Migrate series from legacy key/data files to database.
|
||||
|
||||
Scans for folders containing legacy 'key' or 'data' files and imports
|
||||
any series not already in the database. The DB version wins if a series
|
||||
exists in both places.
|
||||
|
||||
Args:
|
||||
anime_dir: Path to the anime directory
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Number of series imported
|
||||
"""
|
||||
from src.server.database.service import AnimeSeriesService, EpisodeService
|
||||
|
||||
if not anime_dir or not os.path.isdir(anime_dir):
|
||||
logger.warning(
|
||||
"Anime directory does not exist, skipping legacy migration",
|
||||
anime_dir=anime_dir
|
||||
)
|
||||
return 0
|
||||
|
||||
migrated_count = 0
|
||||
scanned_count = 0
|
||||
|
||||
try:
|
||||
for folder_name in os.listdir(anime_dir):
|
||||
folder_path = os.path.join(anime_dir, folder_name)
|
||||
|
||||
if not os.path.isdir(folder_path):
|
||||
continue
|
||||
|
||||
scanned_count += 1
|
||||
|
||||
# Check for 'key' file (single line with series key)
|
||||
key_file = os.path.join(folder_path, "key")
|
||||
# Check for 'data' file (JSON with series metadata)
|
||||
data_file = os.path.join(folder_path, "data")
|
||||
|
||||
series_data: Optional[dict] = None
|
||||
|
||||
# Try to load from 'data' file first (more complete)
|
||||
if os.path.isfile(data_file):
|
||||
series_data = _load_data_file(data_file)
|
||||
elif os.path.isfile(key_file):
|
||||
# Fall back to 'key' file - just the key, need to infer other data
|
||||
series_data = _load_key_file(key_file, folder_name)
|
||||
|
||||
if series_data is None:
|
||||
continue
|
||||
|
||||
key = series_data.get("key")
|
||||
if not key:
|
||||
logger.warning(
|
||||
"Skipping folder with no valid key",
|
||||
folder=folder_name
|
||||
)
|
||||
continue
|
||||
|
||||
# Check if already in DB
|
||||
existing = await AnimeSeriesService.get_by_key(db, key)
|
||||
if existing:
|
||||
logger.debug(
|
||||
"Series already in database, skipping",
|
||||
key=key,
|
||||
folder=folder_name
|
||||
)
|
||||
continue
|
||||
|
||||
# Create the series in DB
|
||||
try:
|
||||
name = series_data.get("name") or folder_name
|
||||
site = series_data.get("site", "https://aniworld.to")
|
||||
folder = series_data.get("folder", folder_name)
|
||||
year = series_data.get("year")
|
||||
|
||||
anime_series = await AnimeSeriesService.create(
|
||||
db=db,
|
||||
key=key,
|
||||
name=name,
|
||||
site=site,
|
||||
folder=folder,
|
||||
year=year,
|
||||
)
|
||||
|
||||
# Create episodes if present
|
||||
episode_dict = series_data.get("episodeDict", {})
|
||||
if episode_dict:
|
||||
for season, episode_numbers in episode_dict.items():
|
||||
for episode_number in episode_numbers:
|
||||
await EpisodeService.create(
|
||||
db=db,
|
||||
series_id=anime_series.id,
|
||||
season=season,
|
||||
episode_number=episode_number,
|
||||
)
|
||||
|
||||
migrated_count += 1
|
||||
logger.info(
|
||||
"Migrated series from legacy file",
|
||||
key=key,
|
||||
name=name,
|
||||
folder=folder_name
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to migrate series from legacy file",
|
||||
key=key,
|
||||
folder=folder_name,
|
||||
error=str(e)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Legacy migration failed",
|
||||
anime_dir=anime_dir,
|
||||
error=str(e),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Legacy file migration complete",
|
||||
scanned_folders=scanned_count,
|
||||
migrated=migrated_count
|
||||
)
|
||||
return migrated_count
|
||||
|
||||
|
||||
def _load_data_file(data_file_path: str) -> Optional[dict]:
|
||||
"""Load and parse a legacy 'data' file (JSON).
|
||||
|
||||
Args:
|
||||
data_file_path: Path to the data file
|
||||
|
||||
Returns:
|
||||
Parsed data dict or None if parsing fails
|
||||
"""
|
||||
try:
|
||||
with open(data_file_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
logger.warning(
|
||||
"Data file is not a dictionary",
|
||||
file=data_file_path
|
||||
)
|
||||
return None
|
||||
|
||||
# Ensure episodeDict has int keys
|
||||
if "episodeDict" in data and isinstance(data["episodeDict"], dict):
|
||||
data["episodeDict"] = {
|
||||
int(k): v for k, v in data["episodeDict"].items()
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(
|
||||
"Failed to parse legacy data file (JSON error)",
|
||||
file=data_file_path,
|
||||
error=str(e)
|
||||
)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to read legacy data file",
|
||||
file=data_file_path,
|
||||
error=str(e)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def _load_key_file(key_file_path: str, folder_name: str) -> Optional[dict]:
|
||||
"""Load a legacy 'key' file (single line with series key).
|
||||
|
||||
Args:
|
||||
key_file_path: Path to the key file
|
||||
folder_name: Folder name to use as fallback name
|
||||
|
||||
Returns:
|
||||
Data dict with key and inferred fields, or None if loading fails
|
||||
"""
|
||||
try:
|
||||
with open(key_file_path, "r", encoding="utf-8") as f:
|
||||
key = f.read().strip()
|
||||
|
||||
if not key:
|
||||
logger.warning(
|
||||
"Key file is empty",
|
||||
file=key_file_path
|
||||
)
|
||||
return None
|
||||
|
||||
# Infer basic data from key file
|
||||
return {
|
||||
"key": key,
|
||||
"name": folder_name,
|
||||
"site": "https://aniworld.to",
|
||||
"folder": folder_name,
|
||||
"episodeDict": {},
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to read legacy key file",
|
||||
file=key_file_path,
|
||||
error=str(e)
|
||||
)
|
||||
return None
|
||||
Reference in New Issue
Block a user