feat: add loading page with real-time initialization progress

- Create loading.html template with WebSocket-based progress updates
- Update initialization_service to emit progress events via ProgressService
- Modify setup endpoint to run initialization in background and redirect to loading page
- Add /loading route in page_controller
- Show real-time progress for series sync, NFO scan, and media scan steps
- Display completion message with button to continue to app
- Handle errors with visual feedback
This commit is contained in:
2026-01-23 14:54:56 +01:00
parent 77ffdac84b
commit 48a2fd0f2a
5 changed files with 714 additions and 21 deletions

View File

@@ -1,4 +1,6 @@
"""Centralized initialization service for application startup and setup."""
from typing import Optional
import structlog
from src.config.settings import settings
@@ -7,7 +9,7 @@ from src.server.services.anime_service import sync_series_from_data_files
logger = structlog.get_logger(__name__)
async def perform_initial_setup():
async def perform_initial_setup(progress_service=None):
"""Perform initial setup including series sync and scan completion marking.
This function is called both during application lifespan startup
@@ -18,12 +20,27 @@ async def perform_initial_setup():
4. NFO scan is performed if configured
5. Media scan is performed
Args:
progress_service: Optional ProgressService instance for emitting updates
Returns:
bool: True if initialization was performed, False if skipped
"""
from src.server.database.connection import get_db_session
from src.server.database.system_settings_service import SystemSettingsService
# Send initial progress update
if progress_service:
progress_service.emit_progress(
progress_id="series_sync",
progress_type="system",
status="started",
title="Syncing Series Database",
message="Checking initialization status...",
percent=0,
metadata={"step_id": "series_sync"}
)
# Check if initial setup has been completed
try:
async with get_db_session() as db:
@@ -35,6 +52,16 @@ async def perform_initial_setup():
logger.info(
"Initial scan already completed, skipping data file sync"
)
if progress_service:
progress_service.emit_progress(
progress_id="series_sync",
progress_type="system",
status="completed",
title="Syncing Series Database",
message="Already completed",
percent=100,
metadata={"step_id": "series_sync"}
)
return False
else:
logger.info(
@@ -58,11 +85,32 @@ async def perform_initial_setup():
logger.info(
"Initialization skipped - anime directory not configured"
)
if progress_service:
progress_service.emit_progress(
progress_id="series_sync",
progress_type="system",
status="completed",
title="Syncing Series Database",
message="No anime directory configured",
percent=100,
metadata={"step_id": "series_sync"}
)
return False
# Only sync from data files on first run
if not is_initial_scan_done:
logger.info("Performing initial anime folder scan...")
if progress_service:
progress_service.emit_progress(
progress_id="series_sync",
progress_type="system",
status="in_progress",
title="Syncing Series Database",
message="Scanning anime folders...",
percent=25,
metadata={"step_id": "series_sync"}
)
sync_count = await sync_series_from_data_files(
settings.anime_directory
)
@@ -70,6 +118,17 @@ async def perform_initial_setup():
"Data file sync complete. Added %d series.", sync_count
)
if progress_service:
progress_service.emit_progress(
progress_id="series_sync",
progress_type="system",
status="in_progress",
title="Syncing Series Database",
message=f"Synced {sync_count} series from data files",
percent=75,
metadata={"step_id": "series_sync"}
)
# Mark initial scan as completed
try:
async with get_db_session() as db:
@@ -94,6 +153,17 @@ async def perform_initial_setup():
await anime_service._load_series_from_db()
logger.info("Series loaded from database into memory")
if progress_service:
progress_service.emit_progress(
progress_id="series_sync",
progress_type="system",
status="completed",
title="Syncing Series Database",
message="Series loaded into memory",
percent=100,
metadata={"step_id": "series_sync"}
)
return True
except (OSError, RuntimeError, ValueError) as e:
@@ -101,11 +171,26 @@ async def perform_initial_setup():
return False
async def perform_nfo_scan_if_needed():
"""Perform initial NFO scan if not yet completed and configured."""
async def perform_nfo_scan_if_needed(progress_service=None):
"""Perform initial NFO scan if not yet completed and configured.
Args:
progress_service: Optional ProgressService instance for emitting updates
"""
from src.server.database.connection import get_db_session
from src.server.database.system_settings_service import SystemSettingsService
if progress_service:
progress_service.emit_progress(
progress_id="nfo_scan",
progress_type="system",
status="started",
title="Processing NFO Metadata",
message="Checking NFO scan status...",
percent=0,
metadata={"step_id": "nfo_scan"}
)
# Check if initial NFO scan has been completed
try:
async with get_db_session() as db:
@@ -126,16 +211,51 @@ async def perform_nfo_scan_if_needed():
):
if not is_nfo_scan_done:
logger.info("Performing initial NFO scan...")
if progress_service:
progress_service.emit_progress(
progress_id="nfo_scan",
progress_type="system",
status="in_progress",
title="Processing NFO Metadata",
message="Scanning series for NFO files...",
percent=25,
metadata={"step_id": "nfo_scan"}
)
try:
from src.core.services.series_manager_service import (
SeriesManagerService,
)
manager = SeriesManagerService.from_settings()
if progress_service:
progress_service.emit_progress(
progress_id="nfo_scan",
progress_type="system",
status="in_progress",
title="Processing NFO Metadata",
message="Processing NFO files with TMDB data...",
percent=50,
metadata={"step_id": "nfo_scan"}
)
await manager.scan_and_process_nfo()
await manager.close()
logger.info("Initial NFO scan completed")
if progress_service:
progress_service.emit_progress(
progress_id="nfo_scan",
progress_type="system",
status="completed",
title="Processing NFO Metadata",
message="NFO scan completed successfully",
percent=100,
metadata={"step_id": "nfo_scan"}
)
# Mark NFO scan as completed
try:
async with get_db_session() as db:
@@ -155,15 +275,49 @@ async def perform_nfo_scan_if_needed():
e,
exc_info=True
)
if progress_service:
progress_service.emit_progress(
progress_id="nfo_scan",
progress_type="system",
status="failed",
title="Processing NFO Metadata",
message=f"NFO scan failed: {str(e)}",
percent=0,
metadata={"step_id": "nfo_scan"}
)
else:
logger.info(
"Skipping NFO scan - already completed on previous run"
)
if progress_service:
progress_service.emit_progress(
progress_id="nfo_scan",
progress_type="system",
status="completed",
title="Processing NFO Metadata",
message="Already completed",
percent=100,
metadata={"step_id": "nfo_scan"}
)
else:
if not settings.tmdb_api_key:
logger.info(
"NFO scan skipped - TMDB API key not configured"
)
message = "Skipped - TMDB API key not configured"
else:
message = "Skipped - NFO features disabled"
if progress_service:
progress_service.emit_progress(
progress_id="nfo_scan",
progress_type="system",
status="completed",
title="Processing NFO Metadata",
message=message,
percent=100,
metadata={"step_id": "nfo_scan"}
)
else:
logger.info(
"NFO scan skipped - auto_create and update_on_scan "