fix: redirect to /setup/unresolved after series scan completes

- Add 'system_progress' event type to loading page redirect condition
- Add checkUnresolvedAndRedirect() for phase=initial to handle race condition
  where backend initialization completes before WebSocket connects
- Backend now emits series_sync progress events during initial setup
- Loading page checks /api/setup/unresolved immediately on load for phase=initial
- Fixes users getting stuck on loading page after setup
This commit is contained in:
2026-07-15 22:06:40 +02:00
parent 47bd393a57
commit d99636e9c7
5 changed files with 104 additions and 41 deletions

View File

@@ -271,7 +271,7 @@ async def _load_series_into_memory(progress_service=None) -> None:
)
async def _scan_folders_to_database(progress_service=None) -> int:
async def _scan_folders_to_database(progress_service=None) -> tuple[int, int]:
"""Scan anime folders and create AnimeSeries DB records.
This function runs during initial setup only. It delegates to
@@ -285,7 +285,7 @@ async def _scan_folders_to_database(progress_service=None) -> int:
progress_service: Optional ProgressService for progress updates
Returns:
int: Number of new series created
Tuple of (number of new series created, number of unresolved folders)
"""
logger.info("Scanning anime folders for new series...")
@@ -293,16 +293,17 @@ async def _scan_folders_to_database(progress_service=None) -> int:
logger.info(
"Anime directory not configured or does not exist, skipping folder scan"
)
return 0
return 0, 0
# Use SetupService to handle the scanning and creation
created_count = await SetupService.run()
created_count, unresolved_count = await SetupService.run()
logger.info(
"Folder scan complete",
created=created_count
created=created_count,
unresolved=unresolved_count
)
return created_count
return created_count, unresolved_count
async def _validate_anime_directory(progress_service=None) -> bool:
@@ -380,7 +381,7 @@ async def perform_initial_setup(progress_service=None):
# Perform the actual initialization
try:
# Scan folders and create AnimeSeries records first
folder_scan_count = await _scan_folders_to_database(progress_service)
folder_scan_count, unresolved_count = await _scan_folders_to_database(progress_service)
if folder_scan_count > 0:
logger.info("Created %d series from anime folders", folder_scan_count)
@@ -398,11 +399,20 @@ async def perform_initial_setup(progress_service=None):
await _mark_initial_scan_completed()
# Mark loading as complete in config (used by middleware to allow redirect to /login)
# Only mark complete if there are no unresolved folders - otherwise user needs
# to resolve them first via /setup/unresolved
try:
from src.server.services.config_service import get_config_service
config_svc = get_config_service()
init_config = config_svc.load_config()
init_config.other['loading_complete'] = True
if unresolved_count == 0:
init_config.other['loading_complete'] = True
logger.info("No unresolved folders - marking loading complete")
else:
logger.info(
"Unresolved folders exist (%d) - deferring loading_complete",
unresolved_count
)
config_svc.save_config(init_config, create_backup=False)
except Exception as e:
logger.warning("Failed to save loading_complete flag: %s", e)

View File

@@ -267,7 +267,7 @@ class SetupService:
)
@classmethod
async def run(cls) -> int:
async def run(cls) -> tuple[int, int]:
"""Run the setup service.
Scans anime folders, creates AnimeSeries records, and resolves
@@ -275,11 +275,11 @@ class SetupService:
that initial scan hasn't been completed yet (via _check_initial_scan_status).
Returns:
Number of new series created
Tuple of (number of new series created, number of unresolved folders)
"""
if not settings.anime_directory:
logger.info("Anime directory not configured, skipping setup")
return 0
return 0, 0
anime_dir = Path(settings.anime_directory)
if not anime_dir.is_dir():
@@ -287,7 +287,7 @@ class SetupService:
"Anime directory does not exist, skipping setup: %s",
anime_dir
)
return 0
return 0, 0
logger.info("Running setup service...")
@@ -385,6 +385,7 @@ class SetupService:
"Could not resolve series key for folder, tracking as unresolved: %s",
folder_name
)
unresolved_count += 1
continue
# Also check if a series with this key already exists (different folder, same anime)
@@ -445,6 +446,6 @@ class SetupService:
error=str(e),
exc_info=True
)
return created_count
return created_count, unresolved_count
return created_count
return created_count, unresolved_count

View File

@@ -451,8 +451,9 @@
updateStep(stepId, status, msg, percent, current, total);
// Check for completion of series_sync
// stepId is used because type is 'system_progress' for SYSTEM progress events
if (metadata?.initialization_complete || (stepId === 'series_sync' && status === 'completed')) {
// For scan_completed messages: stepId='scan_completed', no status field, no metadata
// system_progress events are emitted by progress_service during initial setup (ProgressType.SYSTEM)
if (metadata?.initialization_complete || type === 'scan_completed' || type === 'system_progress' || (stepId === 'series_sync' && status === 'completed')) {
// For initial phase, series_sync completion leads to /setup/unresolved
handleSeriesSyncComplete();
}
@@ -720,10 +721,42 @@
createStep('nfo_scan', stepTitles['nfo_scan']);
// Trigger NFO scan phase via API
triggerNfoScanPhase();
connectWebSocket();
} else {
// For initial phase, initialization already completed before this page loaded
// Check for unresolved folders immediately and redirect
checkUnresolvedAndRedirect();
}
connectWebSocket();
});
// For initial phase, check if there are unresolved folders and redirect accordingly
// This is needed because the backend initialization completes before this page loads,
// so WebSocket events are missed
async function checkUnresolvedAndRedirect() {
try {
const response = await fetch('/api/setup/unresolved');
if (response.ok) {
const folders = await response.json();
if (folders.length > 0) {
// Unresolved folders exist - redirect to unresolved page
clearSetupPhase();
window.location.href = '/setup/unresolved';
} else {
// No unresolved folders - redirect to login
clearSetupPhase();
window.location.href = '/login';
}
} else {
// Error - stay on page and wait for potential WebSocket events
console.error('Failed to check unresolved folders:', response.status);
connectWebSocket();
}
} catch (error) {
console.error('Error checking unresolved folders:', error);
// Stay on page and wait for WebSocket events
connectWebSocket();
}
}
</script>
</body>