diff --git a/app/src/main/frontend/components/general/ScanProgressPopover.tsx b/app/src/main/frontend/components/general/ScanProgressPopover.tsx index 368b289..7898658 100644 --- a/app/src/main/frontend/components/general/ScanProgressPopover.tsx +++ b/app/src/main/frontend/components/general/ScanProgressPopover.tsx @@ -13,7 +13,7 @@ import {useSnapshot} from "valtio/react"; import {scanState} from "Frontend/state/ScanState"; import LibraryScanProgress from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanProgress"; import {libraryState} from "Frontend/state/LibraryState"; -import {Target} from "@phosphor-icons/react"; +import {Target, Warning} from "@phosphor-icons/react"; import {timeBetween, timeUntil, toTitleCase} from "Frontend/util/utils"; import LibraryScanStatus from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanStatus"; import {useEffect, useState} from "react"; @@ -77,21 +77,23 @@ export default function ScanProgressPopover() {

} - {scan.status === LibraryScanStatus.IN_PROGRESS ? - scan.currentStep.current && scan.currentStep.total ? -
-

- {`${scan.currentStep.description} (${scan.currentStep.current}/${scan.currentStep.total})`} -

- -
: -
-

{scan.currentStep.description}

- -
- : + {scan.status === LibraryScanStatus.IN_PROGRESS && + (scan.currentStep.current && scan.currentStep.total ? +
+

+ {`${scan.currentStep.description} (${scan.currentStep.current}/${scan.currentStep.total})`} +

+ +
: +
+

{scan.currentStep.description}

+ +
+ ) + } + {scan.status === LibraryScanStatus.COMPLETED &&

{scan.result?.new} new /  {(scan as any).result?.updated != null && `${(scan as any).result.updated} updated / `} @@ -100,6 +102,11 @@ export default function ScanProgressPopover() { (in {timeBetween(scan.startedAt, scan.finishedAt!)})

} + {scan.status === LibraryScanStatus.FAILED && +

+ Scan failed (check logs for details) +

+ } {scans.length > 1 && index < (scans.length - 1) && } )} diff --git a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt index 502f6cd..2087619 100644 --- a/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt +++ b/app/src/main/kotlin/org/gameyfin/app/libraries/LibraryScanService.kt @@ -117,70 +117,77 @@ class LibraryScanService( ) emit(progress) - val scanResult = filesystemService.scanLibraryForGamefiles(library) - val newPaths = scanResult.newPaths - val removedGamePaths = scanResult.removedGamePaths.map { it.toString() } - val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() } + try { + val scanResult = filesystemService.scanLibraryForGamefiles(library) + val newPaths = scanResult.newPaths + val removedGamePaths = scanResult.removedGamePaths.map { it.toString() } + val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() } - progress.currentStep = LibraryScanStep( - description = "Matching new games", - current = 0, - total = newPaths.size - ) - emit(progress) + progress.currentStep = LibraryScanStep( + description = "Matching new games", + current = 0, + total = newPaths.size + ) + emit(progress) - // 1. Match new games - val (newUnmatchedPaths, matchedGames) = matchNewGames(library, newPaths, progress) + // 1. Match new games + val (newUnmatchedPaths, matchedGames) = matchNewGames(library, newPaths, progress) - val (removedGames) = updateLibrary( - library, - removedUnmatchedPaths, - newUnmatchedPaths, - removedGamePaths - ) + val (removedGames) = updateLibrary( + library, + removedUnmatchedPaths, + newUnmatchedPaths, + removedGamePaths + ) - // 2. Download all images - val totalImages = matchedGames.count { it.coverImage != null } + - matchedGames.count { it.headerImage !== null } + - matchedGames.sumOf { it.images.size } + // 2. Download all images + val totalImages = matchedGames.count { it.coverImage != null } + + matchedGames.count { it.headerImage !== null } + + matchedGames.sumOf { it.images.size } - progress.currentStep = LibraryScanStep( - description = "Downloading images", - current = 0, - total = totalImages - ) - emit(progress) + progress.currentStep = LibraryScanStep( + description = "Downloading images", + current = 0, + total = totalImages + ) + emit(progress) - val (gamesWithImages) = downloadImages(matchedGames, progress) + val (gamesWithImages) = downloadImages(matchedGames, progress) - // 3. Calculate game file sizes - progress.currentStep = LibraryScanStep( - description = "Calculating file sizes", - current = 0, - total = gamesWithImages.size - ) - emit(progress) + // 3. Calculate game file sizes + progress.currentStep = LibraryScanStep( + description = "Calculating file sizes", + current = 0, + total = gamesWithImages.size + ) + emit(progress) - val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress) + val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress) - progress.currentStep = LibraryScanStep( - description = "Finishing up", - current = 0, - total = gamesWithFileSizes.size - ) - emit(progress) + progress.currentStep = LibraryScanStep( + description = "Finishing up", + current = 0, + total = gamesWithFileSizes.size + ) + emit(progress) - val (persistedGames) = finishScan(gamesWithFileSizes, library, progress) + val (persistedGames) = finishScan(gamesWithFileSizes, library, progress) - progress.currentStep = LibraryScanStep(description = "Finished") - progress.finishedAt = Instant.now() - progress.status = LibraryScanStatus.COMPLETED - progress.result = QuickScanResult( - new = persistedGames.size, - removed = removedGames.size, - unmatched = newUnmatchedPaths.size - ) - emit(progress) + progress.currentStep = LibraryScanStep(description = "Finished") + progress.finishedAt = Instant.now() + progress.status = LibraryScanStatus.COMPLETED + progress.result = QuickScanResult( + new = persistedGames.size, + removed = removedGames.size, + unmatched = newUnmatchedPaths.size + ) + emit(progress) + } catch (e: Exception) { + log.error(e) { "Error during quick scan for library ${library.id}: ${e.message}" } + progress.status = LibraryScanStatus.FAILED + progress.finishedAt = Instant.now() + emit(progress) + } } private fun fullScan(library: Library) { @@ -193,86 +200,94 @@ class LibraryScanService( ) emit(progress) - val scanResult = filesystemService.scanLibraryForGamefiles(library) - val newPaths = scanResult.newPaths - val removedGamePaths = scanResult.removedGamePaths.map { it.toString() } - val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() } + try { + val scanResult = filesystemService.scanLibraryForGamefiles(library) + val newPaths = scanResult.newPaths + val removedGamePaths = scanResult.removedGamePaths.map { it.toString() } + val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() } - // 1. Update existing games - progress.currentStep = LibraryScanStep( - description = "Updating existing games", - current = 0, - total = library.games.size - ) - emit(progress) + // 1. Update existing games + progress.currentStep = LibraryScanStep( + description = "Updating existing games", + current = 0, + total = library.games.size + ) + emit(progress) - val (updatedGames) = updateExistingGames(library.games, progress) + val (updatedGames) = updateExistingGames(library.games, progress) - // 2. Match new games - progress.currentStep = LibraryScanStep( - description = "Matching new games", - current = 0, - total = newPaths.size - ) - emit(progress) + // 2. Match new games + progress.currentStep = LibraryScanStep( + description = "Matching new games", + current = 0, + total = newPaths.size + ) + emit(progress) - val (newUnmatchedPaths, newMatchedGames) = matchNewGames(library, newPaths, progress) + val (newUnmatchedPaths, newMatchedGames) = matchNewGames(library, newPaths, progress) - val (removedGames) = updateLibrary( - library, - removedUnmatchedPaths, - newUnmatchedPaths, - removedGamePaths - ) + val (removedGames) = updateLibrary( + library, + removedUnmatchedPaths, + newUnmatchedPaths, + removedGamePaths + ) - // 3. Download all images - val newAndUpdatedGames = newMatchedGames + updatedGames + // 3. Download all images + val newAndUpdatedGames = newMatchedGames + updatedGames - val totalImages = newAndUpdatedGames.count { it.coverImage != null } + - newAndUpdatedGames.count { it.headerImage !== null } + - newAndUpdatedGames.sumOf { it.images.size } + val totalImages = newAndUpdatedGames.count { it.coverImage != null } + + newAndUpdatedGames.count { it.headerImage !== null } + + newAndUpdatedGames.sumOf { it.images.size } - progress.currentStep = LibraryScanStep( - description = "Downloading images", - current = 0, - total = totalImages - ) - emit(progress) + progress.currentStep = LibraryScanStep( + description = "Downloading images", + current = 0, + total = totalImages + ) + emit(progress) - val (gamesWithImages) = downloadImages(newAndUpdatedGames, progress) + val (gamesWithImages) = downloadImages(newAndUpdatedGames, progress) - // 4. Calculate game file sizes - progress.currentStep = LibraryScanStep( - description = "Calculating file sizes", - current = 0, - total = gamesWithImages.size - ) - emit(progress) + // 4. Calculate game file sizes + progress.currentStep = LibraryScanStep( + description = "Calculating file sizes", + current = 0, + total = gamesWithImages.size + ) + emit(progress) - val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress) + val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress) - // 5. Finish scan - progress.currentStep = LibraryScanStep( - description = "Finishing up", - current = 0, - total = gamesWithFileSizes.size - ) - emit(progress) + // 5. Finish scan + progress.currentStep = LibraryScanStep( + description = "Finishing up", + current = 0, + total = gamesWithFileSizes.size + ) + emit(progress) - val (persistedGames) = finishScan(gamesWithFileSizes, library, progress) + val (persistedGames) = finishScan(gamesWithFileSizes, library, progress) - // 6. Send final progress update - progress.currentStep = LibraryScanStep(description = "Finished") - progress.finishedAt = Instant.now() - progress.status = LibraryScanStatus.COMPLETED - progress.result = FullScanResult( - new = persistedGames.size, - removed = removedGames.size, - unmatched = newUnmatchedPaths.size, - updated = updatedGames.size - ) - emit(progress) + // 6. Send final progress update + progress.currentStep = LibraryScanStep(description = "Finished") + progress.finishedAt = Instant.now() + progress.status = LibraryScanStatus.COMPLETED + progress.result = FullScanResult( + new = persistedGames.size, + removed = removedGames.size, + unmatched = newUnmatchedPaths.size, + updated = updatedGames.size + ) + emit(progress) + } catch (e: Exception) { + log.error(e) { "Error during full scan for library ${library.id}: ${e.message}" } + progress.status = LibraryScanStatus.FAILED + progress.finishedAt = Instant.now() + emit(progress) + return + } } private fun matchNewGames(