Handle scan failure correctly

This commit is contained in:
grimsi
2025-07-18 23:46:08 +02:00
parent f759b0c947
commit 8078d9a4cc
2 changed files with 156 additions and 134 deletions
@@ -13,7 +13,7 @@ import {useSnapshot} from "valtio/react";
import {scanState} from "Frontend/state/ScanState"; import {scanState} from "Frontend/state/ScanState";
import LibraryScanProgress from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanProgress"; import LibraryScanProgress from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanProgress";
import {libraryState} from "Frontend/state/LibraryState"; 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 {timeBetween, timeUntil, toTitleCase} from "Frontend/util/utils";
import LibraryScanStatus from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanStatus"; import LibraryScanStatus from "Frontend/generated/org/gameyfin/app/libraries/dto/LibraryScanStatus";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
@@ -77,21 +77,23 @@ export default function ScanProgressPopover() {
</p> </p>
} }
</div> </div>
{scan.status === LibraryScanStatus.IN_PROGRESS ? {scan.status === LibraryScanStatus.IN_PROGRESS &&
scan.currentStep.current && scan.currentStep.total ? (scan.currentStep.current && scan.currentStep.total ?
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<p className="text-default-500"> <p className="text-default-500">
{`${scan.currentStep.description} (${scan.currentStep.current}/${scan.currentStep.total})`} {`${scan.currentStep.description} (${scan.currentStep.current}/${scan.currentStep.total})`}
</p> </p>
<Progress <Progress
value={scan.currentStep.current / scan.currentStep.total * 100} value={scan.currentStep.current / scan.currentStep.total * 100}
size="sm"/> size="sm"/>
</div> : </div> :
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<p className="text-default-500">{scan.currentStep.description}</p> <p className="text-default-500">{scan.currentStep.description}</p>
<Progress isIndeterminate size="sm"/> <Progress isIndeterminate size="sm"/>
</div> </div>
: )
}
{scan.status === LibraryScanStatus.COMPLETED &&
<p> <p>
{scan.result?.new} new /&nbsp; {scan.result?.new} new /&nbsp;
{(scan as any).result?.updated != null && `${(scan as any).result.updated} updated / `} {(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!)}) (in {timeBetween(scan.startedAt, scan.finishedAt!)})
</p> </p>
} }
{scan.status === LibraryScanStatus.FAILED &&
<p className="text-danger flex flex-row gap-1"><Warning weight="fill"/>
Scan failed (check logs for details)
</p>
}
{scans.length > 1 && index < (scans.length - 1) && <Divider className="my-2"/>} {scans.length > 1 && index < (scans.length - 1) && <Divider className="my-2"/>}
</div> </div>
)} )}
@@ -117,70 +117,77 @@ class LibraryScanService(
) )
emit(progress) emit(progress)
val scanResult = filesystemService.scanLibraryForGamefiles(library) try {
val newPaths = scanResult.newPaths val scanResult = filesystemService.scanLibraryForGamefiles(library)
val removedGamePaths = scanResult.removedGamePaths.map { it.toString() } val newPaths = scanResult.newPaths
val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() } val removedGamePaths = scanResult.removedGamePaths.map { it.toString() }
val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() }
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Matching new games", description = "Matching new games",
current = 0, current = 0,
total = newPaths.size total = newPaths.size
) )
emit(progress) emit(progress)
// 1. Match new games // 1. Match new games
val (newUnmatchedPaths, matchedGames) = matchNewGames(library, newPaths, progress) val (newUnmatchedPaths, matchedGames) = matchNewGames(library, newPaths, progress)
val (removedGames) = updateLibrary( val (removedGames) = updateLibrary(
library, library,
removedUnmatchedPaths, removedUnmatchedPaths,
newUnmatchedPaths, newUnmatchedPaths,
removedGamePaths removedGamePaths
) )
// 2. Download all images // 2. Download all images
val totalImages = matchedGames.count { it.coverImage != null } + val totalImages = matchedGames.count { it.coverImage != null } +
matchedGames.count { it.headerImage !== null } + matchedGames.count { it.headerImage !== null } +
matchedGames.sumOf { it.images.size } matchedGames.sumOf { it.images.size }
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Downloading images", description = "Downloading images",
current = 0, current = 0,
total = totalImages total = totalImages
) )
emit(progress) emit(progress)
val (gamesWithImages) = downloadImages(matchedGames, progress) val (gamesWithImages) = downloadImages(matchedGames, progress)
// 3. Calculate game file sizes // 3. Calculate game file sizes
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Calculating file sizes", description = "Calculating file sizes",
current = 0, current = 0,
total = gamesWithImages.size total = gamesWithImages.size
) )
emit(progress) emit(progress)
val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress) val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress)
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Finishing up", description = "Finishing up",
current = 0, current = 0,
total = gamesWithFileSizes.size total = gamesWithFileSizes.size
) )
emit(progress) emit(progress)
val (persistedGames) = finishScan(gamesWithFileSizes, library, progress) val (persistedGames) = finishScan(gamesWithFileSizes, library, progress)
progress.currentStep = LibraryScanStep(description = "Finished") progress.currentStep = LibraryScanStep(description = "Finished")
progress.finishedAt = Instant.now() progress.finishedAt = Instant.now()
progress.status = LibraryScanStatus.COMPLETED progress.status = LibraryScanStatus.COMPLETED
progress.result = QuickScanResult( progress.result = QuickScanResult(
new = persistedGames.size, new = persistedGames.size,
removed = removedGames.size, removed = removedGames.size,
unmatched = newUnmatchedPaths.size unmatched = newUnmatchedPaths.size
) )
emit(progress) 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) { private fun fullScan(library: Library) {
@@ -193,86 +200,94 @@ class LibraryScanService(
) )
emit(progress) emit(progress)
val scanResult = filesystemService.scanLibraryForGamefiles(library) try {
val newPaths = scanResult.newPaths val scanResult = filesystemService.scanLibraryForGamefiles(library)
val removedGamePaths = scanResult.removedGamePaths.map { it.toString() } val newPaths = scanResult.newPaths
val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() } val removedGamePaths = scanResult.removedGamePaths.map { it.toString() }
val removedUnmatchedPaths = scanResult.removedUnmatchedPaths.map { it.toString() }
// 1. Update existing games // 1. Update existing games
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Updating existing games", description = "Updating existing games",
current = 0, current = 0,
total = library.games.size total = library.games.size
) )
emit(progress) emit(progress)
val (updatedGames) = updateExistingGames(library.games, progress) val (updatedGames) = updateExistingGames(library.games, progress)
// 2. Match new games // 2. Match new games
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Matching new games", description = "Matching new games",
current = 0, current = 0,
total = newPaths.size total = newPaths.size
) )
emit(progress) emit(progress)
val (newUnmatchedPaths, newMatchedGames) = matchNewGames(library, newPaths, progress) val (newUnmatchedPaths, newMatchedGames) = matchNewGames(library, newPaths, progress)
val (removedGames) = updateLibrary( val (removedGames) = updateLibrary(
library, library,
removedUnmatchedPaths, removedUnmatchedPaths,
newUnmatchedPaths, newUnmatchedPaths,
removedGamePaths removedGamePaths
) )
// 3. Download all images // 3. Download all images
val newAndUpdatedGames = newMatchedGames + updatedGames val newAndUpdatedGames = newMatchedGames + updatedGames
val totalImages = newAndUpdatedGames.count { it.coverImage != null } + val totalImages = newAndUpdatedGames.count { it.coverImage != null } +
newAndUpdatedGames.count { it.headerImage !== null } + newAndUpdatedGames.count { it.headerImage !== null } +
newAndUpdatedGames.sumOf { it.images.size } newAndUpdatedGames.sumOf { it.images.size }
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Downloading images", description = "Downloading images",
current = 0, current = 0,
total = totalImages total = totalImages
) )
emit(progress) emit(progress)
val (gamesWithImages) = downloadImages(newAndUpdatedGames, progress) val (gamesWithImages) = downloadImages(newAndUpdatedGames, progress)
// 4. Calculate game file sizes // 4. Calculate game file sizes
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Calculating file sizes", description = "Calculating file sizes",
current = 0, current = 0,
total = gamesWithImages.size total = gamesWithImages.size
) )
emit(progress) emit(progress)
val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress) val (gamesWithFileSizes) = calculateFileSizes(gamesWithImages, progress)
// 5. Finish scan // 5. Finish scan
progress.currentStep = LibraryScanStep( progress.currentStep = LibraryScanStep(
description = "Finishing up", description = "Finishing up",
current = 0, current = 0,
total = gamesWithFileSizes.size total = gamesWithFileSizes.size
) )
emit(progress) emit(progress)
val (persistedGames) = finishScan(gamesWithFileSizes, library, progress) val (persistedGames) = finishScan(gamesWithFileSizes, library, progress)
// 6. Send final progress update // 6. Send final progress update
progress.currentStep = LibraryScanStep(description = "Finished") progress.currentStep = LibraryScanStep(description = "Finished")
progress.finishedAt = Instant.now() progress.finishedAt = Instant.now()
progress.status = LibraryScanStatus.COMPLETED progress.status = LibraryScanStatus.COMPLETED
progress.result = FullScanResult( progress.result = FullScanResult(
new = persistedGames.size, new = persistedGames.size,
removed = removedGames.size, removed = removedGames.size,
unmatched = newUnmatchedPaths.size, unmatched = newUnmatchedPaths.size,
updated = updatedGames.size updated = updatedGames.size
) )
emit(progress) 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( private fun matchNewGames(