Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 78 additions & 15 deletions backend/handler/scan_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,22 +791,83 @@ async def fetch_hasheous_rom(hasheous_rom: HasheousRom) -> HasheousRom:
fetch_libretro_rom(),
)

metadata_handlers = {
MetadataSource.IGDB: igdb_handler_rom,
MetadataSource.MOBY: moby_handler_rom,
MetadataSource.SS: ss_handler_rom,
MetadataSource.RA: ra_handler_rom,
MetadataSource.LAUNCHBOX: launchbox_handler_rom,
MetadataSource.HASHEOUS: hasheous_handler_rom,
MetadataSource.FLASHPOINT: flashpoint_handler_rom,
MetadataSource.HLTB: hltb_handler_rom,
MetadataSource.GAMELIST: gamelist_handler_rom,
MetadataSource.LIBRETRO: libretro_handler_rom,
metadata_handlers: dict[MetadataSource, dict] = {
MetadataSource.IGDB: {
"handler": igdb_handler_rom,
"id_field": "igdb_id",
"metadata_field": "igdb_metadata",
},
MetadataSource.MOBY: {
"handler": moby_handler_rom,
"id_field": "moby_id",
"metadata_field": "moby_metadata",
},
MetadataSource.SS: {
"handler": ss_handler_rom,
"id_field": "ss_id",
"metadata_field": "ss_metadata",
},
MetadataSource.RA: {
"handler": ra_handler_rom,
"id_field": "ra_id",
"metadata_field": "ra_metadata",
},
MetadataSource.LAUNCHBOX: {
"handler": launchbox_handler_rom,
"id_field": "launchbox_id",
"metadata_field": "launchbox_metadata",
},
MetadataSource.HASHEOUS: {
"handler": hasheous_handler_rom,
"id_field": "hasheous_id",
"metadata_field": "hasheous_metadata",
},
MetadataSource.FLASHPOINT: {
"handler": flashpoint_handler_rom,
"id_field": "flashpoint_id",
"metadata_field": "flashpoint_metadata",
},
MetadataSource.HLTB: {
"handler": hltb_handler_rom,
"id_field": "hltb_id",
"metadata_field": "hltb_metadata",
},
MetadataSource.GAMELIST: {
"handler": gamelist_handler_rom,
"id_field": "gamelist_id",
"metadata_field": "gamelist_metadata",
},
MetadataSource.LIBRETRO: {
"handler": libretro_handler_rom,
"id_field": "libretro_id",
"metadata_field": None,
},
MetadataSource.SGDB: {
"handler": {},
"id_field": "sgdb_id",
"metadata_field": None,
},
MetadataSource.TGDB: {
"handler": {},
"id_field": "tgdb_id",
"metadata_field": None,
},
}

# For COMPLETE rescans, explicitly clear metadata IDs and metadata for unselected sources
# This ensures that when a source is no longer selected, its data is removed from the ROM
if not newly_added and scan_type == ScanType.COMPLETE:
for source, fields in metadata_handlers.items():
if source not in metadata_sources:
rom_attrs[fields["id_field"]] = None
if fields["metadata_field"]:
rom_attrs[fields["metadata_field"]] = {}
Comment thread
gantoine marked this conversation as resolved.
Comment thread
gantoine marked this conversation as resolved.

# Determine which metadata sources are available
available_sources = [
name for name, handler in metadata_handlers.items() if handler.get(f"{name}_id")
name
for name, fields in metadata_handlers.items()
if fields["handler"].get(fields["id_field"])
]

# Apply metadata priority order
Expand All @@ -815,7 +876,7 @@ async def fetch_hasheous_rom(hasheous_rom: HasheousRom) -> HasheousRom:
)
# Reverse priority order to apply highest priority last
for source_name in reversed(priority_ordered):
handler_data = metadata_handlers[source_name]
handler_data = metadata_handlers[source_name]["handler"]
# Only update fields that have valid values
for key, field_value in handler_data.items():
if field_value:
Expand All @@ -827,7 +888,7 @@ async def fetch_hasheous_rom(hasheous_rom: HasheousRom) -> HasheousRom:
)
# Reverse priority order to apply highest priority last
for source_name in reversed(priority_ordered_artwork):
handler_data = metadata_handlers[source_name]
handler_data = metadata_handlers[source_name]["handler"]
for field in ["url_cover", "url_screenshots", "url_manual"]:
# Only update fields that have valid values
field_value = handler_data.get(field)
Expand Down Expand Up @@ -931,7 +992,9 @@ async def fetch_sgdb_details(playmatch_rom: PlaymatchRomMatch) -> SGDBRom:
)
if sgdb_cover and not manual_cover_preserved:
cover_sources = [
name for name, h in metadata_handlers.items() if h.get("url_cover")
name
for name, fields in metadata_handlers.items()
if fields["handler"].get("url_cover")
]
ranked = get_priority_ordered_metadata_sources(
cover_sources + [MetadataSource.SGDB], "artwork"
Expand Down
83 changes: 82 additions & 1 deletion backend/tests/handler/test_fastapi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from unittest.mock import AsyncMock, patch

import pytest

from handler.database import db_platform_handler
from handler.database import db_platform_handler, db_rom_handler
from handler.metadata import meta_hasheous_handler, meta_playmatch_handler
from handler.metadata.hasheous_handler import HasheousRom
from handler.scan_handler import MetadataSource, ScanType, scan_platform, scan_rom
from models.platform import Platform
from models.rom import Rom, RomFile
Expand Down Expand Up @@ -93,3 +97,80 @@ async def test_scan_rom():
# assert rom.hasheous_id == 4872
# assert rom.fs_size_bytes == 23175094
# assert rom.tags == []


@patch.object(meta_playmatch_handler, "is_enabled", return_value=False)
@patch.object(meta_hasheous_handler, "get_ra_game", new_callable=AsyncMock)
@patch.object(meta_hasheous_handler, "get_igdb_game", new_callable=AsyncMock)
@patch.object(meta_hasheous_handler, "lookup_rom", new_callable=AsyncMock)
async def test_scan_rom_complete_clears_unselected_metadata(
mock_lookup, mock_get_igdb, mock_get_ra, mock_playmatch_enabled
):
"""COMPLETE rescan with newly_added=False must clear id and *_metadata
fields for sources that are no longer in metadata_sources."""
hasheous_result = HasheousRom(
hasheous_id=999,
igdb_id=None,
tgdb_id=None,
ra_id=None,
name="Mock Hasheous Game",
)
mock_lookup.return_value = hasheous_result
mock_get_igdb.return_value = hasheous_result
mock_get_ra.return_value = hasheous_result

platform = Platform(
id=1,
slug="n64",
fs_slug="n64",
name="Nintendo 64",
igdb_id=4,
ra_id=2,
hasheous_id=64,
)
platform = db_platform_handler.add_platform(platform)

rom = Rom(
platform_id=platform.id,
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,
igdb_metadata={"summary": "stale IGDB metadata"},
ra_id=1234,
ra_metadata={"name": "stale RA metadata"},
hasheous_id=4872,
fs_size_bytes=1024,
tags=[],
)
rom = db_rom_handler.add_rom(rom)

async with initialize_context():
result = await scan_rom(
platform=platform,
scan_type=ScanType.COMPLETE,
rom=rom,
fs_rom={
"fs_name": "Paper Mario (USA).z64",
"flat": True,
"nested": False,
"files": [],
"crc_hash": "",
"md5_hash": "",
"sha1_hash": "",
"ra_hash": "",
},
metadata_sources=[MetadataSource.HASHEOUS],
newly_added=False,
)

# IGDB and RA were unselected — their id and metadata must be cleared.
assert result.igdb_id is None
assert result.igdb_metadata == {}
assert result.ra_id is None
assert result.ra_metadata == {}
# Hasheous is still selected and should remain populated.
assert result.hasheous_id == 999
Loading