mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
Merge pull request #2479 from rommapp/scan-progress
Progressive scan steps for each game
This commit is contained in:
2
.github/workflows/pytest.yml
vendored
2
.github/workflows/pytest.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
uv run pytest -vv --maxfail=10 --junitxml=pytest-report.xml --cov --cov-report xml:coverage.xml --cov-config=.coveragerc .
|
||||
|
||||
- name: Publish test results
|
||||
uses: EnricoMi/publish-unit-test-result-action/linux@sha-3a74b29
|
||||
uses: EnricoMi/publish-unit-test-result-action/linux@v2.20.0
|
||||
if: (!cancelled())
|
||||
with:
|
||||
files: |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 | None = None,
|
||||
) -> Rom:
|
||||
if not metadata_sources:
|
||||
log.error("No metadata sources provided")
|
||||
@@ -384,6 +388,19 @@ 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
|
||||
|
||||
if socket_manager:
|
||||
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,
|
||||
|
||||
@@ -247,6 +247,10 @@ class Rom(BaseModel):
|
||||
back_populates="roms",
|
||||
)
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self._is_identifying = False
|
||||
|
||||
@property
|
||||
def platform_slug(self) -> str:
|
||||
return self.platform.slug
|
||||
@@ -369,6 +373,15 @@ class Rom(BaseModel):
|
||||
)
|
||||
return self.ra_metadata
|
||||
|
||||
# Used only during scan process
|
||||
@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
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from handler.database import db_platform_handler
|
||||
from handler.scan_handler import MetadataSource, ScanType, scan_platform, scan_rom
|
||||
from models.platform import Platform
|
||||
from models.rom import Rom, RomFile
|
||||
@@ -28,9 +29,15 @@ async def test_scan_platform():
|
||||
|
||||
@pytest.mark.vcr
|
||||
async def test_scan_rom():
|
||||
platform = Platform(fs_slug="n64", igdb_id=4)
|
||||
platform = Platform(id=1, slug="n64", fs_slug="n64", name="Nintendo 64", igdb_id=4)
|
||||
platform = db_platform_handler.add_platform(platform)
|
||||
|
||||
rom = Rom(
|
||||
fs_name="Paper Mario (USA).z64",
|
||||
fs_name_no_tags="Paper Mario",
|
||||
fs_name_no_ext="Paper Mario",
|
||||
fs_extension="z64",
|
||||
fs_path="n64/Paper Mario (USA)",
|
||||
name="Paper Mario",
|
||||
igdb_id=3340,
|
||||
fs_size_bytes=1024,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user