For ROMs tagged with multiple regions (e.g. "(Japan, USA)"), filename order
previously decided which region's name and box art won. Now reorder the rom's
filename-tagged regions by SCAN_REGION_PRIORITY before prepending, so the
user's configured preference wins among the regions the file is actually
tagged as. Untagged priority regions still cannot outrank a filename-tagged
region.
Also tweak the Total Rescan → Complete Rescan label in en_GB/en_US scan
locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix several issues in ScreenScraper API request/response handling:
- Correctly handle SS-specific HTTP error codes (KO responses, 429, 431,
and the SS-quirk of returning 401 when server CPU >60%).
- Construct requests with proper parameter encoding so jeuInfos lookups
and search queries return the expected results.
- Store media URLs returned by SS as-is, preserving the dev credential
query parameters required for media playback. Removing them broke
downstream media fetches.
To keep dev credentials out of log output, add a redacting formatter in
the logger pipeline that scrubs ssid/sspassword/devid/devpassword query
parameters from any URL it sees.
Test coverage added for the new HTTP error paths and the as-is URL
storage behaviour.
Importer (gamelist/launchbox file:// flows) and exporters (gamelist.xml,
metadata.pegasus.txt local exports) now hardlink media assets when source
and destination share a filesystem, falling back transparently to a copy
on EXDEV / EPERM / EOPNOTSUPP / EMLINK / EACCES (cross-device, FAT32,
exFAT, network mounts, etc.). Saves disk space and is effectively
instantaneous on large files (videos, manuals, miximages).
Covers keep a real copy (allow_link=False) because _store_cover resizes
the small cover in place via PIL.Image.save, which would truncate the
shared inode and corrupt the user's source image.
Also makes FSSyncHandler tolerate a missing/unwritable /romm/sync at
startup: an OSError from mkdir now logs a warning instead of crashing
the whole app at module-import time. Sync calls still fail at use time
if the mount remains broken — the right place to surface the error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI runs pytest without root, so the new /var/lib/romm/sync default
fails when FSSyncHandler tries to mkdir its base path at import time.
The other handlers stay writable in tests because they derive from
ROMM_BASE_PATH=romm_test (relative); pin SYNC_BASE_PATH the same way.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The shell fallback was assigned locally but never exported, so
sync_watcher.py and the Python config layer never saw the resolved
value. They happened to land on the same /var/lib/romm/sync default by
coincidence; export it so the shell and Python defaults stay linked
through a single source of truth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The prod Dockerfile creates /var/lib/romm/sync at build time, but if a
user overrides SYNC_BASE_PATH to a path that doesn't exist (or runs the
dev entrypoint, which never created the default), watchfiles fails to
start because its target directory is missing. Have both entrypoints
mkdir -p the resolved path before handing it to watchfiles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the sync staging folder out of ROMM_BASE_PATH so it lives on a
dedicated writable mount. This lets the container run with a read-only
root filesystem without losing in-flight save uploads, and keeps
app-owned state separate from the user-curated library volume.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CHD files now follow the same hash logic as all other file types — CRC32,
MD5, and SHA1 are computed from raw container bytes. This allows
ScreenScraper to log KO entries for unrecognised CHD files, which it
could not do when only the disc-data SHA1 was being computed.
The CHD header SHA1 (disc-data SHA1) is separately extracted and stored
in a new chd_sha1_hash field on RomFile, with a migration adding the
column to rom_files. Hasheous receives only this disc-data SHA1 (no
CRC/MD5) since it indexes disc-based games by disc-data SHA1, not raw
file hashes.
The RAHasher multi-file path now passes the largest CHD directly instead
of a /* wildcard, which RAHasher cannot expand. Hash computations are
wrapped in asyncio.to_thread to avoid blocking the event loop during
large reads.
Hash-lookup metadata handlers (ScreenScraper, Hasheous, Playmatch) now
fall back to rom.files (stored DB hashes) when fs_rom files are not
rehashed, fixing hash-based matching for UNMATCHED and UPDATE scan types.
The Disc SHA-1 is displayed in the ROM detail view for both single-file
(FileInfo.vue) and multi-file (FileSelectItem.vue) CHD games.