- SerieScanner: Remove key file fallback, keep data file fallback - SystemSettings: Add legacy_key_cleanup_completed flag - initialization_service: Add cleanup task to remove key files from folders with DB entries - Tests updated to reflect key file removal from legacy path Key files caused duplicate key errors on folder rename. DB is now sole source of truth.
222 lines
7.4 KiB
Python
222 lines
7.4 KiB
Python
"""System settings service for managing application-level configuration.
|
|
|
|
This module provides services for managing system-wide settings and state,
|
|
including tracking initial setup completion status.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Optional
|
|
|
|
import structlog
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from src.server.database.models import SystemSettings
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
class SystemSettingsService:
|
|
"""Service for managing system settings."""
|
|
|
|
@staticmethod
|
|
async def get_or_create(db: AsyncSession) -> SystemSettings:
|
|
"""Get the system settings record, creating it if it doesn't exist.
|
|
|
|
Only one system settings record should exist in the database.
|
|
|
|
Args:
|
|
db: Database session
|
|
|
|
Returns:
|
|
SystemSettings instance
|
|
"""
|
|
# Try to get existing settings
|
|
stmt = select(SystemSettings).limit(1)
|
|
result = await db.execute(stmt)
|
|
settings = result.scalar_one_or_none()
|
|
|
|
if settings is None:
|
|
# Create new settings with defaults
|
|
settings = SystemSettings(
|
|
initial_scan_completed=False,
|
|
initial_nfo_scan_completed=False,
|
|
initial_media_scan_completed=False,
|
|
)
|
|
db.add(settings)
|
|
await db.commit()
|
|
await db.refresh(settings)
|
|
logger.info("Created new system settings record")
|
|
|
|
return settings
|
|
|
|
@staticmethod
|
|
async def is_initial_scan_completed(db: AsyncSession) -> bool:
|
|
"""Check if the initial anime folder scan has been completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
|
|
Returns:
|
|
True if initial scan is completed, False otherwise
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
return settings.initial_scan_completed
|
|
|
|
@staticmethod
|
|
async def mark_initial_scan_completed(
|
|
db: AsyncSession,
|
|
timestamp: Optional[datetime] = None
|
|
) -> None:
|
|
"""Mark the initial anime folder scan as completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
timestamp: Optional timestamp to set, defaults to current time
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
settings.initial_scan_completed = True
|
|
settings.last_scan_timestamp = timestamp or datetime.now(timezone.utc)
|
|
await db.commit()
|
|
logger.info("Marked initial scan as completed")
|
|
|
|
@staticmethod
|
|
async def is_initial_nfo_scan_completed(db: AsyncSession) -> bool:
|
|
"""Check if the initial NFO scan has been completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
|
|
Returns:
|
|
True if initial NFO scan is completed, False otherwise
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
return settings.initial_nfo_scan_completed
|
|
|
|
@staticmethod
|
|
async def mark_initial_nfo_scan_completed(
|
|
db: AsyncSession,
|
|
timestamp: Optional[datetime] = None
|
|
) -> None:
|
|
"""Mark the initial NFO scan as completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
timestamp: Optional timestamp to set, defaults to current time
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
settings.initial_nfo_scan_completed = True
|
|
if timestamp:
|
|
settings.last_scan_timestamp = timestamp
|
|
await db.commit()
|
|
logger.info("Marked initial NFO scan as completed")
|
|
|
|
@staticmethod
|
|
async def is_initial_media_scan_completed(db: AsyncSession) -> bool:
|
|
"""Check if the initial media scan has been completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
|
|
Returns:
|
|
True if initial media scan is completed, False otherwise
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
return settings.initial_media_scan_completed
|
|
|
|
@staticmethod
|
|
async def is_migration_legacy_files_completed(db: AsyncSession) -> bool:
|
|
"""Check if legacy key/data file migration has been completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
|
|
Returns:
|
|
True if legacy migration is completed, False otherwise
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
return settings.migration_legacy_files_completed
|
|
|
|
@staticmethod
|
|
async def mark_migration_legacy_files_completed(
|
|
db: AsyncSession,
|
|
timestamp: Optional[datetime] = None
|
|
) -> None:
|
|
"""Mark the legacy key/data file migration as completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
timestamp: Optional timestamp to set, defaults to current time
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
settings.migration_legacy_files_completed = True
|
|
settings.last_scan_timestamp = timestamp or datetime.now(timezone.utc)
|
|
await db.commit()
|
|
logger.info("Marked legacy files migration as completed")
|
|
|
|
@staticmethod
|
|
async def is_legacy_key_cleanup_completed(db: AsyncSession) -> bool:
|
|
"""Check if legacy key file cleanup has been completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
|
|
Returns:
|
|
True if cleanup is completed, False otherwise
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
return settings.legacy_key_cleanup_completed
|
|
|
|
@staticmethod
|
|
async def mark_legacy_key_cleanup_completed(
|
|
db: AsyncSession,
|
|
timestamp: Optional[datetime] = None
|
|
) -> None:
|
|
"""Mark the legacy key file cleanup as completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
timestamp: Optional timestamp to set, defaults to current time
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
settings.legacy_key_cleanup_completed = True
|
|
settings.last_scan_timestamp = timestamp or datetime.now(timezone.utc)
|
|
await db.commit()
|
|
logger.info("Marked legacy key file cleanup as completed")
|
|
|
|
@staticmethod
|
|
async def mark_initial_media_scan_completed(
|
|
db: AsyncSession,
|
|
timestamp: Optional[datetime] = None
|
|
) -> None:
|
|
"""Mark the initial media scan as completed.
|
|
|
|
Args:
|
|
db: Database session
|
|
timestamp: Optional timestamp to set, defaults to current time
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
settings.initial_media_scan_completed = True
|
|
if timestamp:
|
|
settings.last_scan_timestamp = timestamp
|
|
await db.commit()
|
|
logger.info("Marked initial media scan as completed")
|
|
|
|
@staticmethod
|
|
async def reset_all_scans(db: AsyncSession) -> None:
|
|
"""Reset all scan completion flags (for testing or re-setup).
|
|
|
|
Args:
|
|
db: Database session
|
|
"""
|
|
settings = await SystemSettingsService.get_or_create(db)
|
|
settings.initial_scan_completed = False
|
|
settings.initial_nfo_scan_completed = False
|
|
settings.initial_media_scan_completed = False
|
|
settings.migration_legacy_files_completed = False
|
|
settings.legacy_key_cleanup_completed = False
|
|
settings.last_scan_timestamp = None
|
|
await db.commit()
|
|
logger.info("Reset all scan completion flags")
|