progressive scan steps for each game

This commit is contained in:
Georges-Antoine Assi
2025-09-23 10:23:57 -04:00
parent 654499f2da
commit 3a3bffe699
8 changed files with 173 additions and 117 deletions

View File

@@ -242,6 +242,7 @@ class RomSchema(BaseModel):
path_manual: str | None
url_manual: str | None
is_identifying: bool = False
is_unidentified: bool
is_identified: bool

View File

@@ -234,6 +234,7 @@ async def _identify_rom(
fs_rom=fs_rom,
metadata_sources=metadata_sources,
newly_added=newly_added,
socket_manager=socket_manager,
)
scan_stats.scanned_roms += 1
@@ -242,6 +243,14 @@ async def _identify_rom(
_added_rom = db_rom_handler.add_rom(scanned_rom)
if _added_rom.is_identified:
await socket_manager.emit(
"scan:scanning_rom",
SimpleRomSchema.from_orm_with_factory(_added_rom).model_dump(
exclude={"created_at", "updated_at", "rom_user"}
),
)
# Delete the existing rom files in the DB
db_rom_handler.purge_rom_files(_added_rom.id)
@@ -316,14 +325,9 @@ async def _identify_rom(
await socket_manager.emit(
"scan:scanning_rom",
{
"platform_name": platform.name,
"platform_slug": platform.slug,
"platform_fs_slug": platform.fs_slug,
**SimpleRomSchema.from_orm_with_factory(_added_rom).model_dump(
exclude={"created_at", "updated_at", "rom_user"}
),
},
SimpleRomSchema.from_orm_with_factory(_added_rom).model_dump(
exclude={"created_at", "updated_at", "rom_user"}
),
)
await socket_manager.emit("", None)
@@ -460,25 +464,25 @@ async def scan_platforms(
if not roms_ids:
roms_ids = []
sm = _get_socket_manager()
socket_manager = _get_socket_manager()
if not metadata_sources:
log.error("No metadata sources provided")
await sm.emit("scan:done_ko", "No metadata sources provided")
await socket_manager.emit("scan:done_ko", "No metadata sources provided")
return None
try:
fs_platforms: list[str] = await fs_platform_handler.get_platforms()
except FolderStructureNotMatchException as e:
log.error(e)
await sm.emit("scan:done_ko", e.message)
await socket_manager.emit("scan:done_ko", e.message)
return None
scan_stats = ScanStats()
async def stop_scan():
log.info(f"{emoji.EMOJI_STOP_SIGN} Scan stopped manually")
await sm.emit("scan:done", scan_stats.__dict__)
await socket_manager.emit("scan:done", scan_stats.__dict__)
redis_client.delete(STOP_SCAN_FLAG)
try:
@@ -506,7 +510,7 @@ async def scan_platforms(
fs_platforms=fs_platforms,
roms_ids=roms_ids,
metadata_sources=metadata_sources,
socket_manager=sm,
socket_manager=socket_manager,
)
missed_platforms = db_platform_handler.mark_missing_platforms(fs_platforms)
@@ -516,13 +520,13 @@ async def scan_platforms(
log.warning(f" - {p.slug} ({p.fs_slug})")
log.info(f"{emoji.EMOJI_CHECK_MARK} Scan completed")
await sm.emit("scan:done", scan_stats.__dict__)
await socket_manager.emit("scan:done", scan_stats.__dict__)
except ScanStoppedException:
await stop_scan()
except Exception as e:
log.error(f"Error in scan_platform: {e}")
# Catch all exceptions and emit error to the client
await sm.emit("scan:done_ko", str(e))
await socket_manager.emit("scan:done_ko", str(e))
# Re-raise the exception to be caught by the error handler
raise e

View File

@@ -2,8 +2,11 @@ import asyncio
import enum
from typing import Any
import socketio # type: ignore
from config.config_manager import config_manager as cm
from handler.database import db_platform_handler
from endpoints.responses.rom import SimpleRomSchema
from handler.database import db_platform_handler, db_rom_handler
from handler.filesystem import fs_asset_handler, fs_firmware_handler
from handler.filesystem.roms_handler import FSRom
from handler.metadata import (
@@ -281,6 +284,7 @@ async def scan_rom(
fs_rom: FSRom,
metadata_sources: list[str],
newly_added: bool,
socket_manager: socketio.AsyncRedisManager,
) -> Rom:
if not metadata_sources:
log.error("No metadata sources provided")
@@ -384,6 +388,18 @@ async def scan_rom(
return HasheousRom(hasheous_id=None, igdb_id=None, tgdb_id=None, ra_id=None)
_added_rom = db_rom_handler.add_rom(Rom(**rom_attrs))
_added_rom.is_identifying = True
await socket_manager.emit(
"scan:scanning_rom",
{
**SimpleRomSchema.from_orm_with_factory(_added_rom).model_dump(
exclude={"created_at", "updated_at", "rom_user"}
),
},
)
# Run hash fetches concurrently
(
playmatch_hash_match,

View File

@@ -369,6 +369,14 @@ class Rom(BaseModel):
)
return self.ra_metadata
@property
def is_identifying(self) -> bool:
return self._is_identifying or False
@is_identifying.setter
def is_identifying(self, value: bool) -> None:
self._is_identifying = value
def __repr__(self) -> str:
return self.fs_name

View File

@@ -62,6 +62,7 @@ export type DetailedRomSchema = {
has_manual: boolean;
path_manual: (string | null);
url_manual: (string | null);
is_identifying?: boolean;
is_unidentified: boolean;
is_identified: boolean;
revision: (string | null);

View File

@@ -56,6 +56,7 @@ export type SimpleRomSchema = {
has_manual: boolean;
path_manual: (string | null);
url_manual: (string | null);
is_identifying?: boolean;
is_unidentified: boolean;
is_identified: boolean;
revision: (string | null);

View File

@@ -57,9 +57,18 @@ socket.on(
socket.on("scan:scanning_rom", (rom: SimpleRom) => {
scanningStore.set(true);
// Remove the ROM from the recent list and add it back to the top
romsStore.removeFromRecent(rom);
romsStore.addToRecent(rom);
if (romsStore.currentPlatform?.id === rom.platform_id) {
romsStore.add([rom]);
const existingRom = romsStore.allRoms.find((r) => r.id === rom.id);
if (existingRom) {
romsStore.update(rom);
} else {
romsStore.add([rom]);
}
}
let scannedPlatform = scanningPlatforms.value.find(
@@ -78,7 +87,15 @@ socket.on("scan:scanning_rom", (rom: SimpleRom) => {
scannedPlatform = scanningPlatforms.value[0];
}
scannedPlatform?.roms.push(rom);
// Check if ROM already exists in the store
const existingRom = scannedPlatform?.roms.find((r) => r.id === rom.id);
if (existingRom) {
scannedPlatform.roms = scannedPlatform.roms.map((r) =>
r.id === rom.id ? rom : r,
);
} else {
scannedPlatform?.roms.push(rom);
}
});
socket.on("scan:done", () => {

View File

@@ -365,105 +365,113 @@ async function stopScan() {
with-filename
>
<template #append>
<v-chip
v-if="rom.is_unidentified"
color="red"
size="x-small"
label
>
Not identified
<v-icon class="ml-1"> mdi-close </v-icon>
</v-chip>
<v-chip
v-if="rom.hasheous_id"
title="Verified with Hasheous"
class="text-white pa-0 mr-1"
size="small"
>
<v-avatar class="bg-romm-green" size="26" rounded="0">
<v-icon>mdi-check-decagram-outline</v-icon>
</v-avatar>
</v-chip>
<v-chip
v-if="rom.igdb_id"
class="pa-0 mr-1"
size="small"
title="IGDB match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/igdb.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.ss_id"
class="pa-0 mr-1"
size="small"
title="ScreenScraper match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/ss.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.moby_id"
class="pa-0 mr-1"
size="small"
title="MobyGames match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/moby.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.launchbox_id"
class="pa-0 mr-1"
size="small"
title="LaunchBox match"
>
<v-avatar size="26" style="background: #185a7c">
<v-img src="/assets/scrappers/launchbox.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.ra_id"
class="pa-0 mr-1"
size="small"
title="RetroAchievements match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/ra.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.hasheous_id"
class="pa-1 mr-1 bg-surface"
size="small"
title="Hasheous match"
>
<v-avatar size="18" rounded>
<v-img src="/assets/scrappers/hasheous.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.flashpoint_id"
class="pa-1 mr-1 bg-surface"
size="small"
title="Flashpoint match"
>
<v-avatar size="18" rounded>
<v-img src="/assets/scrappers/flashpoint.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.hltb_id"
class="pa-1 mr-1 bg-surface"
size="small"
title="HowLongToBeat match"
>
<v-avatar size="18" rounded>
<v-img src="/assets/scrappers/hltb.png" />
</v-avatar>
</v-chip>
<template v-if="rom.is_identifying">
<v-chip color="orange" size="x-small" label>
<v-icon class="mr-1"> mdi-search-web </v-icon>
Identifying
</v-chip>
</template>
<template v-else>
<v-chip
v-if="rom.is_unidentified"
color="red"
size="x-small"
label
>
<v-icon class="mr-1"> mdi-close </v-icon>
Not identified
</v-chip>
<v-chip
v-if="rom.hasheous_id"
title="Verified with Hasheous"
class="text-white pa-0 mr-1"
size="small"
>
<v-avatar class="bg-romm-green" size="26" rounded="0">
<v-icon>mdi-check-decagram-outline</v-icon>
</v-avatar>
</v-chip>
<v-chip
v-if="rom.igdb_id"
class="pa-0 mr-1"
size="small"
title="IGDB match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/igdb.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.ss_id"
class="pa-0 mr-1"
size="small"
title="ScreenScraper match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/ss.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.moby_id"
class="pa-0 mr-1"
size="small"
title="MobyGames match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/moby.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.launchbox_id"
class="pa-0 mr-1"
size="small"
title="LaunchBox match"
>
<v-avatar size="26" style="background: #185a7c">
<v-img src="/assets/scrappers/launchbox.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.ra_id"
class="pa-0 mr-1"
size="small"
title="RetroAchievements match"
>
<v-avatar size="26" rounded>
<v-img src="/assets/scrappers/ra.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.hasheous_id"
class="pa-1 mr-1 bg-surface"
size="small"
title="Hasheous match"
>
<v-avatar size="18" rounded>
<v-img src="/assets/scrappers/hasheous.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.flashpoint_id"
class="pa-1 mr-1 bg-surface"
size="small"
title="Flashpoint match"
>
<v-avatar size="18" rounded>
<v-img src="/assets/scrappers/flashpoint.png" />
</v-avatar>
</v-chip>
<v-chip
v-if="rom.hltb_id"
class="pa-1 mr-1 bg-surface"
size="small"
title="HowLongToBeat match"
>
<v-avatar size="18" rounded>
<v-img src="/assets/scrappers/hltb.png" />
</v-avatar>
</v-chip>
</template>
</template>
</RomListItem>
<v-list-item