diff --git a/backend/endpoints/sockets/scan.py b/backend/endpoints/sockets/scan.py index 62fa1fa14..daff142eb 100644 --- a/backend/endpoints/sockets/scan.py +++ b/backend/endpoints/sockets/scan.py @@ -270,10 +270,6 @@ async def _identify_rom( ) ) - # Silly checks to make the type checker happy - if not rom: - return - # Build rom files object before scanning should_update_files = _should_get_rom_files( scan_type=scan_type, rom=rom, newly_added=newly_added, roms_ids=roms_ids diff --git a/backend/handler/filesystem/roms_handler.py b/backend/handler/filesystem/roms_handler.py index ae5c7621d..fdfca012c 100644 --- a/backend/handler/filesystem/roms_handler.py +++ b/backend/handler/filesystem/roms_handler.py @@ -259,7 +259,7 @@ class FSRomsHandler(FSHandler): return [f for f in roms if f not in filtered_files] def _build_rom_file( - self, rom_path: Path, file_name: str, file_hash: FileHash + self, rom: Rom, rom_path: Path, file_name: str, file_hash: FileHash ) -> RomFile: # Absolute path to roms abs_file_path = Path(self.base_path, rom_path, file_name) @@ -275,6 +275,8 @@ class FSRomsHandler(FSHandler): ) return RomFile( + rom=rom, + rom_id=rom.id, file_name=file_name, file_path=str(rom_path), file_size_bytes=os.stat(abs_file_path).st_size, @@ -380,9 +382,10 @@ class FSRomsHandler(FSHandler): rom_files.append( self._build_rom_file( - f_path.relative_to(self.base_path), - file_name, - file_hash, + rom=rom, + rom_path=f_path.relative_to(self.base_path), + file_name=file_name, + file_hash=file_hash, ) ) elif hashable_platform: @@ -417,7 +420,12 @@ class FSRomsHandler(FSHandler): ), ) rom_files.append( - self._build_rom_file(Path(rel_roms_path), rom.fs_name, file_hash) + self._build_rom_file( + rom=rom, + rom_path=Path(rel_roms_path), + file_name=rom.fs_name, + file_hash=file_hash, + ) ) else: file_hash = FileHash( @@ -426,7 +434,12 @@ class FSRomsHandler(FSHandler): sha1_hash="", ) rom_files.append( - self._build_rom_file(Path(rel_roms_path), rom.fs_name, file_hash) + self._build_rom_file( + rom=rom, + rom_path=Path(rel_roms_path), + file_name=rom.fs_name, + file_hash=file_hash, + ) ) return ( diff --git a/backend/handler/metadata/hasheous_handler.py b/backend/handler/metadata/hasheous_handler.py index dd0476484..ba1fd0153 100644 --- a/backend/handler/metadata/hasheous_handler.py +++ b/backend/handler/metadata/hasheous_handler.py @@ -53,7 +53,7 @@ class HasheousRom(BaseRom): hasheous_metadata: NotRequired[HasheousMetadata] -ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG = {"dc": ["cue"]} +ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG = {UPS.DC: ["cue", "bin"]} def extract_metadata_from_igdb_rom(rom: dict[str, Any]) -> IGDBMetadata: @@ -238,13 +238,12 @@ class HasheousHandler(MetadataHandler): filtered_files = [ file for file in files - if file.file_size_bytes is not None - and file.file_size_bytes > 0 + if file.file_size_bytes > 0 and file.is_top_level and ( file.file_extension - in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG[platform_slug] - if platform_slug in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG + in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG[UPS(platform_slug)] + if UPS(platform_slug) in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG else True ) ] diff --git a/backend/handler/metadata/playmatch_handler.py b/backend/handler/metadata/playmatch_handler.py index ffd1ca23a..f4b2f6d05 100644 --- a/backend/handler/metadata/playmatch_handler.py +++ b/backend/handler/metadata/playmatch_handler.py @@ -121,11 +121,7 @@ class PlaymatchHandler(MetadataHandler): return PlaymatchRomMatch(igdb_id=None) first_file = next( - ( - file - for file in files - if file.file_size_bytes is not None and file.file_size_bytes > 0 - ), + (file for file in files if file.file_size_bytes > 0), None, ) if first_file is None: diff --git a/backend/handler/metadata/ss_handler.py b/backend/handler/metadata/ss_handler.py index 005eb5705..99749e07a 100644 --- a/backend/handler/metadata/ss_handler.py +++ b/backend/handler/metadata/ss_handler.py @@ -14,7 +14,7 @@ from config.config_manager import MetadataMediaType from config.config_manager import config_manager as cm from handler.filesystem import fs_resource_handler from logger.logger import log -from models.rom import Rom +from models.rom import Rom, RomFile from .base_handler import ( PS2_OPL_REGEX, @@ -128,6 +128,12 @@ ARCADE_SS_IDS: Final = [ # Regex to detect ScreenScraper ID tags in filenames like (ssfr-12345) SS_TAG_REGEX = re.compile(r"\(ssfr-(\d+)\)", re.IGNORECASE) +ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG = { + UPS.DC: ["cue", "chd", "gdi", "cdi"], + UPS.SEGACD: ["cue", "chd", "bin"], + UPS.NGC: ["rvz", "iso", "gcz"], +} + class SSPlatform(TypedDict): slug: str @@ -516,17 +522,36 @@ class SSHandler(MetadataHandler): name=platform["name"], ) - async def lookup_rom(self, rom: Rom, rom_attrs: dict, platform_ss_id: int) -> SSRom: + async def lookup_rom( + self, rom: Rom, platform_ss_id: int, files: list[RomFile] + ) -> SSRom: if not self.is_enabled(): return SSRom(ss_id=None) if not platform_ss_id: return SSRom(ss_id=None) - md5_hash = rom_attrs["md5_hash"] - sha1_hash = rom_attrs["sha1_hash"] - crc_hash = rom_attrs["crc_hash"] - fs_size_bytes = rom_attrs["fs_size_bytes"] + filtered_files = [ + file + for file in files + if file.file_size_bytes > 0 + and file.is_top_level + and ( + file.file_extension + in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG[UPS(rom.platform_slug)] + if UPS(rom.platform_slug) in ACCEPTABLE_FILE_EXTENSIONS_BY_PLATFORM_SLUG + else True + ) + ] + + first_file = max(filtered_files, key=lambda f: f.file_size_bytes, default=None) + if first_file is None: + return SSRom(ss_id=None) + + md5_hash = first_file.md5_hash + sha1_hash = first_file.sha1_hash + crc_hash = first_file.crc_hash + fs_size_bytes = first_file.file_size_bytes if not (md5_hash or sha1_hash or crc_hash): log.info( diff --git a/backend/handler/scan_handler.py b/backend/handler/scan_handler.py index 22b302d9b..278f0a332 100644 --- a/backend/handler/scan_handler.py +++ b/backend/handler/scan_handler.py @@ -556,18 +556,21 @@ async def scan_rom( ) ) ): - hash_lookup = await meta_ss_handler.lookup_rom( - rom, rom_attrs, platform.ss_id - ) - if hash_lookup.get("ss_id"): - return hash_lookup - + # Use the ID to refetch metadata if scan_type == ScanType.UPDATE and rom.ss_id: return await meta_ss_handler.get_rom_by_id(rom, rom.ss_id) - else: - return await meta_ss_handler.get_rom( - rom, rom_attrs["fs_name"], platform_ss_id=platform.ss_id - ) + + # Use the file hashes for lookup + game_by_hash = await meta_ss_handler.lookup_rom( + rom, platform.ss_id, fs_rom["files"] + ) + if game_by_hash.get("ss_id"): + return game_by_hash + + # Fallback to the filename + return await meta_ss_handler.get_rom( + rom, rom_attrs["fs_name"], platform_ss_id=platform.ss_id + ) return SSRom(ss_id=None) diff --git a/backend/models/rom.py b/backend/models/rom.py index ab092ebab..52fd10262 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -117,6 +117,9 @@ class RomFile(BaseModel): f"{self.rom.full_path}/", ".hidden/" if hidden_folder else "" ) + def __repr__(self) -> str: + return f"{self.file_name} ({self.id} -> {self.rom_id})" + class RomMetadata(BaseModel): __tablename__ = "roms_metadata" @@ -407,7 +410,7 @@ class Rom(BaseModel): self._is_identifying = value def __repr__(self) -> str: - return self.fs_name + return f"{self.fs_name} ({self.id})" class RomUserStatus(enum.StrEnum):