From 3a3bffe6991d4d4ad040fb68fd079ff5a7fbe7f0 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 23 Sep 2025 10:23:57 -0400 Subject: [PATCH] progressive scan steps for each game --- backend/endpoints/responses/rom.py | 1 + backend/endpoints/sockets/scan.py | 34 +-- backend/handler/scan_handler.py | 18 +- backend/models/rom.py | 8 + .../__generated__/models/DetailedRomSchema.ts | 1 + .../__generated__/models/SimpleRomSchema.ts | 1 + .../components/common/Navigation/ScanBtn.vue | 21 +- frontend/src/views/Scan.vue | 206 +++++++++--------- 8 files changed, 173 insertions(+), 117 deletions(-) diff --git a/backend/endpoints/responses/rom.py b/backend/endpoints/responses/rom.py index 0898550c6..e0b6eb48c 100644 --- a/backend/endpoints/responses/rom.py +++ b/backend/endpoints/responses/rom.py @@ -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 diff --git a/backend/endpoints/sockets/scan.py b/backend/endpoints/sockets/scan.py index 4b12c7027..da39b3866 100644 --- a/backend/endpoints/sockets/scan.py +++ b/backend/endpoints/sockets/scan.py @@ -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 diff --git a/backend/handler/scan_handler.py b/backend/handler/scan_handler.py index c871809d8..15d0b4fab 100644 --- a/backend/handler/scan_handler.py +++ b/backend/handler/scan_handler.py @@ -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, diff --git a/backend/models/rom.py b/backend/models/rom.py index 7f1954ac1..4ff59af9e 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -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 diff --git a/frontend/src/__generated__/models/DetailedRomSchema.ts b/frontend/src/__generated__/models/DetailedRomSchema.ts index 302b792ce..1365734dc 100644 --- a/frontend/src/__generated__/models/DetailedRomSchema.ts +++ b/frontend/src/__generated__/models/DetailedRomSchema.ts @@ -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); diff --git a/frontend/src/__generated__/models/SimpleRomSchema.ts b/frontend/src/__generated__/models/SimpleRomSchema.ts index 2da97adcb..d41e89c58 100644 --- a/frontend/src/__generated__/models/SimpleRomSchema.ts +++ b/frontend/src/__generated__/models/SimpleRomSchema.ts @@ -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); diff --git a/frontend/src/components/common/Navigation/ScanBtn.vue b/frontend/src/components/common/Navigation/ScanBtn.vue index 47de62173..209f6b664 100644 --- a/frontend/src/components/common/Navigation/ScanBtn.vue +++ b/frontend/src/components/common/Navigation/ScanBtn.vue @@ -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", () => { diff --git a/frontend/src/views/Scan.vue b/frontend/src/views/Scan.vue index 26bc96d4a..66ff63e48 100644 --- a/frontend/src/views/Scan.vue +++ b/frontend/src/views/Scan.vue @@ -365,105 +365,113 @@ async function stopScan() { with-filename >