3539 Commits

Author SHA1 Message Date
zurdi
d7a896b5da
fix: re-parse tags from filename when renaming a rom
Renaming a rom via PUT /roms/{id} only updated fs_name and its
derivatives, leaving regions/languages/tags/revision/version stale
against the new filename. Re-parse them whenever fs_name changes so
edits like "patapon (Fr En)" -> "Patapon (Fr, En)" are reflected in
the database.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 22:23:33 +00:00
Georges-Antoine Assi
49fd1280af
Merge pull request #3273 from rommapp/copilot/fix-custom-boxart-download
fix: include `cus` region in default ScreenScraper fallback regions
2026-04-16 21:37:32 -04:00
Georges-Antoine Assi
7b20101d50
Merge pull request #3270 from Namaneo/home-boxarts
A bunch of minor bug fixes
2026-04-16 11:16:16 -04:00
Georges-Antoine Assi
b18edb1528
fix trunk issues 2026-04-16 11:15:58 -04:00
copilot-swe-agent[bot]
bf246f842f
fix: add cus region to default ScreenScraper fallback regions
Games where only custom (cus) region artwork exists were not fetched
unless users explicitly added 'cus' to their region priority config.
Adding 'cus' to the default fallback list ensures custom artwork is
always considered as a fallback when no other regional artwork exists.

Agent-Logs-Url: https://github.com/rommapp/romm/sessions/75c3e455-f7dd-4bd6-bec7-0460987e40a0

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-16 14:28:51 +00:00
Georges-Antoine Assi
e1969652bf
run fmt 2026-04-16 08:14:48 -04:00
Julien Loir
9a311a889b Add missing skip_hash_calculation from config write 2026-04-16 12:57:30 +02:00
Georges-Antoine Assi
f5474ee588
fix(tests): bump pytest-asyncio for pytest 9 compatibility
pytest-asyncio 0.23 is incompatible with pytest 9 (it calls the removed
Package.obj attribute, causing an INTERNALERROR during collection).
Bump to ~= 1.1 and set asyncio_default_fixture_loop_scope to silence the
1.x deprecation warning.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 19:43:53 -04:00
Georges-Antoine Assi
341709674a
tiny fix 2026-04-15 18:39:17 -04:00
Georges-Antoine Assi
833cf28be9
Merge pull request #3264 from Namaneo/fix-download
Fix download failing when hash has not been computed
2026-04-15 18:37:30 -04:00
Julien Loir
97e5bbe1cc Fix download failing when hash has not been computed 2026-04-15 13:50:48 +02:00
Spinnich
15b485b118 fix(collections): address reviewer feedback on types and concurrency
- Use CollectionSchema instead of ReturnType<typeof collectionsStore.getCollection>
  in AddRoms.vue and RemoveRoms.vue (simpler, per gantoine review)
- Wrap bulk INSERT in a savepoint so a concurrent duplicate-key violation
  is caught via IntegrityError and ignored rather than aborting the transaction
- Only bump Collection.updated_at in remove_roms_from_collection when rows
  were actually deleted (rowcount > 0), matching add_roms_to_collection behavior

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 06:46:12 -04:00
Spinnich
14342b5726 fix(tests): use client.request(\"DELETE\") to send JSON body in tests
Starlette's TestClient (httpx-based) does not expose body kwargs on the
delete() convenience method; client.request(\"DELETE\", ..., json=...) is
the correct approach. Also switch datetime.utcnow() to
datetime.now(timezone.utc) to silence Python 3.13 deprecation warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:02:26 -04:00
Spinnich
9aab88ad8b fix(tests): fix remaining DELETE body and timestamp comparison issues
- Switch content= to data= for DELETE requests (Starlette TestClient is
  requests-based and does not accept content= keyword argument)
- Fix test_bumps_updated_at to record time before the API call and use >=
  comparison, avoiding false failures when MariaDB truncates DATETIME to
  whole seconds and creation/update land in the same second

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 20:53:57 -04:00
Spinnich
fae6d869f1 fix(tests): correct test failures in collection endpoint tests
- Replace client.delete(json=...) with content=json.dumps()+Content-Type header
  (Starlette TestClient does not forward json= on DELETE requests)
- Adjust duplicate-name test to expect HTTP 500 matching CollectionAlreadyExistsException
- Add description="" to collections created without it to satisfy Pydantic schema
- Strip tzinfo before comparing updated_at to avoid offset-naive/aware TypeError

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 20:43:12 -04:00
Spinnich
1c9f010918 Fix isort import ordering in test_collection.py 2026-04-14 20:33:45 -04:00
Spinnich
4cc0c868b7 Fix linting and formatting issues from trunk CI 2026-04-14 20:27:45 -04:00
Spinnich
2ecefa3d3f Fix race condition in collection and favorite rom membership updates
Replace full rom_ids list replacement with atomic POST/DELETE endpoints
that add or remove individual ROMs from a collection. This prevents
concurrent rapid clicks from overwriting each other (last-write-wins).

Also fix missing session.flush() in add_rom_user() and add collection
endpoint tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 15:08:53 -04:00
copilot-swe-agent[bot]
ce5624b470
Add index on rom_files(rom_id) to fix PostgreSQL performance issue
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/7bb628cb-282d-44c3-b912-9c81212611a9

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-14 02:35:19 +00:00
Georges-Antoine Assi
154165fadf
final fixes 2026-04-12 19:26:34 -04:00
Georges-Antoine Assi
105ed84e43
feat: show TheGamesDB link on platform drawer when tgdb_id is set
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 19:04:51 -04:00
Georges-Antoine Assi
e8a6e9f01d
final fixes 2026-04-12 18:43:24 -04:00
Georges-Antoine Assi
d45afb5dde
more fixes 2026-04-12 18:32:15 -04:00
Georges-Antoine Assi
f2df03361e
fix: cast spread-dict back to RAUserGameProgression to preserve typing
Pylance infers dict[str, Unknown] from the {**old, "highest_award_kind": ...}
spread, which then fails to assign to list[RAUserGameProgression]. Wrap in
typing.cast so the TypedDict type survives the reassignment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:08:18 -04:00
Georges-Antoine Assi
628d8d8bae
refactor: pass RAGamesPlatform dict into calculate_hash, normalize extension
Callers now pass the full platform dict and rom.fs_extension; the service
normalizes the extension (optional leading dot, case-insensitive) before
checking the compressed-archive skip set, so ROMs stored with bare
extensions like "zip" correctly hit the skip path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 18:05:44 -04:00
Georges-Antoine Assi
8f1b8f41d7
perf: skip RAHasher subprocess for archived disc-platform ROMs
RAHasher was being spawned for every hashable ROM regardless of file
type. When the source file is a zip/7z/tar and the RA platform needs
an on-disk disc image (PSX, PS2, PSP, Saturn, Dreamcast, Sega CD,
3DO, PC-FX, Neo Geo CD, TurboGrafx CD, Atari Jaguar CD, Wii), the
subprocess fails with "Unsupported console for buffer hash: {id}"
after paying full process-spawn overhead per ROM — a serious slowdown
when indexing large zipped collections (e.g. myrient PS2/PSP sets).

calculate_hash now short-circuits those combinations with a debug log
and no subprocess. Raw disc images (.iso, .chd, .cue/.bin) and
archives on cartridge platforms still go through RAHasher as before.

Also centralize COMPRESSED_FILE_EXTENSIONS in utils/filesystem.py so
roms_handler (is_compressed_file / hashing), rahasher (skip logic),
and feeds (PKGi passthrough) share one source of truth. The shared
set adds .rar, which is_compressed_file now recognizes too.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 17:18:14 -04:00
Georges-Antoine Assi
3ce0873931
fix 2026-04-12 16:09:36 -04:00
Georges-Antoine Assi
1b5943bd93
feat: fetch libretro Named_Snaps/Titles/Logos gated by SCAN_MEDIA
get_rom now also fetches Named_Snaps, Named_Titles, and Named_Logos
when the matching MetadataMediaType (SCREENSHOT, TITLE_SCREEN, LOGO)
is in SCAN_MEDIA. Box art is still fetched unconditionally — it drives
url_cover and libretro_id. Matching extras are appended to
url_screenshots so the scan_handler artwork priority loop picks them
up without further changes. All enabled listings are fetched
concurrently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:01:37 -04:00
Georges-Antoine Assi
62e540d60e
changes from bot review 2026-04-12 15:43:14 -04:00
Georges-Antoine Assi
f94c1491e5
fix: respect SCAN_ARTWORK_PRIORITY for SGDB and align libretro search with SGDB pattern
SGDB's url_cover used to clobber whatever the artwork priority loop
picked, which meant user-set priorities (e.g. libretro > sgdb) and
manually uploaded covers were silently overridden. SGDB now only
replaces the current url_cover when it outranks every other source
that produced one under SCAN_ARTWORK_PRIORITY, and never over a
manual cover preserved on UPDATE/UNMATCHED scans. Default artwork
priority gains sgdb at the top so existing "SGDB wins" behavior is
preserved for default configs.

On the /search/roms endpoint, libretro is now an enrichment source
alongside SGDB instead of a primary match source: it decorates
entries resolved by IGDB/Moby/SS/Flashpoint/Launchbox with
libretro_id and libretro_url_cover, mirroring how SGDB works.
get_matched_roms_by_name is removed from the libretro handler since
nothing else calls it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 11:52:44 -04:00
Georges-Antoine Assi
4928041593
manual cleanu 2026-04-12 11:04:12 -04:00
Georges-Antoine Assi
b3c79ab6ff
fix migration order 2026-04-12 10:15:53 -04:00
Georges-Antoine Assi
6df31e2cc0
Merge branch 'master' into libretro-handler 2026-04-12 10:14:26 -04:00
Georges-Antoine Assi
f9f3dfd927
changes from bot review 2026-04-12 09:50:54 -04:00
Georges-Antoine Assi
abc69c790f
fix scanning 2026-04-12 09:35:34 -04:00
Georges-Antoine Assi
85f9444b57
fix: restore get_roms_by_fs_name after stripped @with_details
Commit 3991e1b6e removed `@with_details` from `get_roms_by_fs_name` but
left the body using the `query` parameter that decorator was supposed
to inject, so every scan hit `'NoneType' object has no attribute
'filter'` and crashed the platform identification task.

Make the function self-contained: build `select(Rom)` directly and
eager-load only `Rom.platform`, the one relationship the scan loop
actually needs (via `rom.platform_slug` / `rom.platform.fs_slug`).
Keeps the prior commit's intent of avoiding the heavy `with_details`
eager-load on every batch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:10:27 -04:00
Georges-Antoine Assi
de9efb3da8
refactor: simplify mark_missing_roms with a single flips dict
Collapse the two parallel id lists and their mirrored chunked-update
loops into a `flips: dict[bool, list[int]]` keyed by desired state, and
drop unused rom assignments in the related tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:13:33 -04:00
Georges-Antoine Assi
522df9d31a
feat: add libretro thumbnails as an artwork source
Adds the libretro thumbnail repository as a first-class artwork source so
region-correct box art (PAL/Europe, Japan, etc.) can be matched directly
to ROM filenames, addressing rommapp/romm#3239.

Implementation follows the SGDB handler pattern (artwork-only, no game
metadata): MetadataSource enum entry, scan-time fetch wired into the
SCAN_ARTWORK_PRIORITY loop, /search/roms integration, MatchRom dialog
chip + cover selection, and a heartbeat flag.

Matching is exact case-insensitive against the directory listing first
(so a ROM named "(Europe)" lands on the (Europe) artwork), with a
JaroWinkler fuzzy fallback at 0.8 that strips parenthetical tags from
both sides. Listings are cached in Redis with a 24h TTL.

`libretro_id` is persisted on the Rom model as the SHA1 hex of the
matched libretro filename — stable across scans, distinct per region,
indexed for lookup. Migration 0077 adds the column.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:57:20 -04:00
Georges-Antoine Assi
3991e1b6ed
fix: scan stalls on platforms with 10k+ already-scanned ROMs
The scan was spending excessive time on large platforms even when all ROMs
were already scanned. Root causes: per-ROM UPDATE queries for skipped ROMs
(10k individual writes), missing composite index on (platform_id, fs_name)
causing full table scans, NOT IN clauses with 10k+ values in
mark_missing_roms(), and redundant filesystem reads.

Changes:
- Add bulk_mark_present() for batch-updating skipped ROMs in one query
- Move skip detection from _identify_rom to the batch loop so skipped ROMs
  never enter the async scan pipeline, and report progress for them
- Add composite index idx_roms_platform_id_fs_name via migration 0077
- Rewrite mark_missing_roms() with flip-based approach: mark all missing,
  then un-mark present ones in chunks of 1000
- Cache filesystem reads in scan_platforms() to avoid double directory
  traversal (precounting + scanning)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 19:20:40 -04:00
Georges-Antoine Assi
686c609b3a
Merge pull request #3155 from tmgast/feature/play-session-ingest
Add play session ingest for game time tracking
2026-04-11 11:23:02 -04:00
Georges-Antoine Assi
1eba6da6af
fix: gate MariaDB-specific FK lookup behind dialect check in 0068 downgrade
The information_schema query using DATABASE() only works on MariaDB/MySQL.
Move it inside the is_postgresql guard so PostgreSQL downgrades don't error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 11:09:33 -04:00
Georges-Antoine Assi
9646d4c239
fix: resolve MariaDB errors in migration downgrades (0068, 0072)
- 0072: remove redundant index drops before drop_table in downgrade
- 0068: temporarily drop FK constraint before dropping composite index
  that MariaDB uses to back the FK on saves.rom_id
- 0068: remove redundant index drops for tables being dropped
- 0068: guard DROP TYPE behind is_postgresql check (MariaDB has no types)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 10:45:33 -04:00
Georges-Antoine Assi
af2d1f3471
changes from bot review 2026-04-11 09:58:32 -04:00
copilot-swe-agent[bot]
d6036cb5ef
fix: respect LOGLEVEL env var for all log output sources
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/0b5ef21c-89e1-4f08-b402-03f3276aab08

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-11 02:13:20 +00:00
Georges-Antoine Assi
b0a88ff54a
fix typo 2026-04-09 23:13:50 -04:00
Georges-Antoine Assi
00a10f32f2
fix 2026-04-09 23:06:25 -04:00
Georges-Antoine Assi
9a574e076a
Add if_not_exists/if_exists guards to all alembic create_index/drop_index ops
Prevents errors when migrations are re-run against a database that
already has (or has already dropped) the target indexes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 23:04:08 -04:00
Georges-Antoine Assi
ccf5f656b7
Add if_not_exists/if_exists guards to all alembic add_column/drop_column ops 2026-04-09 22:50:07 -04:00
Georges-Antoine Assi
6317740466
Add if_not_exists/if_exists guards to all alembic table create/drop ops
Prevents errors when migrations are re-run against a database that
already has (or has already dropped) the target tables.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:46:41 -04:00
Georges-Antoine Assi
6db9d45928
actually fix 2026-04-07 22:53:44 -04:00