diff --git a/src/server/api/auth.py b/src/server/api/auth.py index b9623a5..4071be4 100644 --- a/src/server/api/auth.py +++ b/src/server/api/auth.py @@ -30,7 +30,8 @@ async def setup_auth(req: SetupRequest): """Initial setup endpoint to configure the master password. This endpoint also initializes the configuration with all provided values - and saves them to config.json. + and saves them to config.json. It triggers background initialization + and redirects to a loading page that shows real-time progress. """ if auth_service.is_configured(): raise HTTPException( @@ -117,17 +118,9 @@ async def setup_auth(req: SetupRequest): # Save the config with all updates config_service.save_config(config, create_backup=False) - # Perform initial setup (series sync, NFO scan, media scan) - # This ensures everything is initialized immediately after setup - # without requiring an application restart - from src.config.settings import settings - from src.server.services.initialization_service import ( - perform_initial_setup, - perform_nfo_scan_if_needed, - ) - # Sync config.json values to settings object # (mirroring the logic in fastapi_app.py lifespan) + from src.config.settings import settings other_settings = dict(config.other) if config.other else {} if other_settings.get("anime_directory"): settings.anime_directory = str(other_settings["anime_directory"]) @@ -142,12 +135,53 @@ async def setup_auth(req: SetupRequest): settings.nfo_download_fanart = config.nfo.download_fanart settings.nfo_image_size = config.nfo.image_size - # Perform the initial series sync and mark as completed - await perform_initial_setup() + # Trigger initialization in background task + import asyncio + + from src.server.services.initialization_service import ( + perform_initial_setup, + perform_nfo_scan_if_needed, + ) + from src.server.utils.dependencies import get_progress_service - # Perform NFO scan if configured - await perform_nfo_scan_if_needed() + progress_service = get_progress_service() + async def run_initialization(): + """Run initialization steps with progress updates.""" + try: + # Perform the initial series sync and mark as completed + await perform_initial_setup(progress_service) + + # Perform NFO scan if configured + await perform_nfo_scan_if_needed(progress_service) + + # Send completion event + progress_service.emit_progress( + progress_id="initialization_complete", + progress_type="system", + status="completed", + title="Initialization Complete", + message="All initialization tasks completed successfully", + percent=100, + metadata={"initialization_complete": True} + ) + except Exception as e: + # Send error event + progress_service.emit_progress( + progress_id="initialization_error", + progress_type="error", + status="failed", + title="Initialization Failed", + message=str(e), + percent=0, + metadata={"initialization_complete": True, "error": str(e)} + ) + + # Start initialization in background + asyncio.create_task(run_initialization()) + + # Return redirect to loading page + return {"status": "ok", "redirect": "/loading"} # Note: Media scan is skipped during setup as it requires # background_loader service which is only available during # application lifespan. It will run on first application startup. diff --git a/src/server/controllers/page_controller.py b/src/server/controllers/page_controller.py index 3acaafc..7f14a50 100644 --- a/src/server/controllers/page_controller.py +++ b/src/server/controllers/page_controller.py @@ -49,3 +49,13 @@ async def queue_page(request: Request): request, title="Download Queue - Aniworld" ) + + +@router.get("/loading", response_class=HTMLResponse) +async def loading_page(request: Request): + """Serve the initialization loading page.""" + return render_template( + "loading.html", + request, + title="Initializing - Aniworld" + ) diff --git a/src/server/services/initialization_service.py b/src/server/services/initialization_service.py index 52d44dc..380d617 100644 --- a/src/server/services/initialization_service.py +++ b/src/server/services/initialization_service.py @@ -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 " diff --git a/src/server/web/templates/loading.html b/src/server/web/templates/loading.html new file mode 100644 index 0000000..a08155b --- /dev/null +++ b/src/server/web/templates/loading.html @@ -0,0 +1,487 @@ + + + + + + + AniWorld Manager - Initializing + + + + + + +
+
+
+ +

Initializing AniWorld Manager

+

Please wait while we set up your anime library...

+
+ +
+ +
+ +
+ Connecting... +
+ + + + +
+
+ + + + + diff --git a/src/server/web/templates/setup.html b/src/server/web/templates/setup.html index 88d11a5..21305e0 100644 --- a/src/server/web/templates/setup.html +++ b/src/server/web/templates/setup.html @@ -703,10 +703,18 @@ const data = await response.json(); if (response.ok && data.status === 'ok') { - showMessage('Setup completed successfully! Redirecting to login...', 'success'); - setTimeout(() => { - window.location.href = '/login'; - }, 2000); + // Redirect to loading page if provided, otherwise go to login + if (data.redirect) { + showMessage('Setup saved! Initializing your anime library...', 'success'); + setTimeout(() => { + window.location.href = data.redirect; + }, 500); + } else { + showMessage('Setup completed successfully! Redirecting to login...', 'success'); + setTimeout(() => { + window.location.href = '/login'; + }, 2000); + } } else { const errorMessage = data.detail || data.message || 'Setup failed'; showMessage(errorMessage, 'error');