Fix custom library structure PR merge issues - #4196
Open
ruvelro wants to merge 7 commits into
Open
Conversation
…ntity Adds opt-in, per-platform subfolder scanning (closes rommapp#2050). When enabled for a platform, RomM recurses that platform's subfolders and treats each nested file as its own ROM, instead of collapsing a subfolder into one multi-file ROM — so libraries organized into Hacks/, Translations/, Homebrew/, etc. are scanned correctly. Enable per platform in config.yml (default off, existing setups unaffected): scan: subfolders: nes: true # recurse every subfolder snes: ["Hacks", "Translations"] # recurse only these; keep others whole Key difference from a naive path-identity approach: identity is reconciled by content hash on scan. Moving or renaming a ROM between subfolders is detected by its hash against a now-missing ROM and relocated in place, so saves, play history, favorites and collection membership follow it. Falls back to path identity when hashing is unavailable (skip_hash_calculation or a non-hashable platform). A cheap size pre-filter avoids hashing every newly-seen file (e.g. on first enable). Details: - Per-platform opt-in via scan.subfolders (fs_slug -> bool | list[str]); no DB schema change. - Recursion skips hidden (dot-prefixed) folders, and keeps a folder whole as a single multi-file ROM when it holds a disc/playlist descriptor (.m3u/.cue/.gdi/.ccd/.toc) — covers cue+bin / multi-disc games. - Path-based keying for the steady-state lookup (get_roms_by_fs_name and mark_missing_roms key on full path) so identically-named files in different subfolders stay distinct; the (platform_id, fs_name) index is non-unique. - File resolution (download/delete/hash) uses the ROM's stored fs_path. - Scan log flags entries that became "missing" because a folder is now recursed, so stale entries are easy to clean up. - Frontend: the Files tab shows a ROM's on-disk Location (click-to-copy), built as a shared LocationChip mirroring HashChip's RTag-based pattern. New i18n keys added to all locales. Tests: backend unit tests for recursion (collisions, hidden folders, descriptor dirs kept whole, named-list form), full-path keying in get_roms_by_fs_name / mark_missing_roms, and hash-based relocation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sk2dzM3K9qxWBdPAeGb7us
Generalizes the opt-in subfolder scanning into a per-platform custom
library structure template, modeled on Retrom's. Replaces the
`scan.subfolders` (bool | list) flag with `filesystem.structure`
(fs_slug -> template); platforms without a template keep RomM's default
discovery (top-level files and folders), so existing setups are unaffected.
A template is relative to the platform's ROM folder (RomM already resolves
the library root, roms_folder and platform directory) and is a sequence of
`/`-separated path sections:
- a braced section is a macro, a bare section is a literal folder matched
exactly;
- the last section must be the terminal {gameFile} (each file is a game) or
{gameDir} (each folder is a single multi-file game);
- any other braced section ({region}, {category}, ...) is a wildcard
directory level that matches any folder (organizational only).
Examples:
filesystem:
structure:
nes: "{category}/{gameFile}" # roms/nes/Hacks/foo.nes
ps3: "{category}/{gameDir}" # roms/ps3/PSN/Game/
snes: "{region}/{gameFile}" # roms/snes/USA/foo.sfc
Declaring {gameFile} vs {gameDir} removes the previous disc-descriptor
guessing entirely — the user states file-vs-folder. Hidden (dot-prefixed)
folders are never descended into. Hash-based non-destructive identity
(relocate a moved/renamed game in place, preserving saves/history/favorites/
collections) is retained and now triggers for any platform with a custom
structure.
- config: parse + validate templates at load (parse_library_structure,
LibraryStructure); reject {platform}/{library} (RomM resolves those) and
malformed templates.
- fs handler: _discover_structured_roms walks literal/wildcard levels to the
terminal; _discover_default_roms preserves the default behavior.
- Tests: template parser (valid/invalid), per-platform config loading, and
structured discovery (wildcard/literal levels, file/dir terminals, depth>1,
hidden-folder skip, cross-folder name collisions). Subfolder-flag tests
replaced.
- Docs: config.example.yml documents filesystem.structure; scan.subfolders
removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sk2dzM3K9qxWBdPAeGb7us
A single fixed-depth template can't describe a platform that holds loose
games in its root AND organizes others into grouping subfolders — `{gameFile}`
drops the grouped games, `{category}/{gameFile}` drops the loose ones. Let a
platform's `filesystem.structure` value be a list of templates; discovery is
their union, deduplicated by full path:
structure:
nes:
- "{gameFile}" # loose top-level games
- "{category}/{gameFile}" # games inside grouping subfolders
- config: accept str | list[str]; platform_structure() returns a tuple of
parsed structures; parse_platform_structures() + validation handle both forms.
- fs handler: _collect_fs_roms unions each structure's discovery, dedup by
(fs_path, fs_name).
- tests: list parsing, per-platform list config loading, and the mixed
loose+grouped discovery case.
- docs: config.example.yml leads with the mixed-layout list example.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sk2dzM3K9qxWBdPAeGb7us
…-0b77y7 # Conflicts: # frontend/src/locales/bg_BG/rom.json # frontend/src/locales/cs_CZ/rom.json # frontend/src/locales/de_DE/rom.json # frontend/src/locales/en_GB/rom.json # frontend/src/locales/en_US/rom.json # frontend/src/locales/es_ES/rom.json # frontend/src/locales/fr_FR/rom.json # frontend/src/locales/hu_HU/rom.json # frontend/src/locales/it_IT/rom.json # frontend/src/locales/ja_JP/rom.json # frontend/src/locales/ko_KR/rom.json # frontend/src/locales/pl_PL/rom.json # frontend/src/locales/pt_BR/rom.json # frontend/src/locales/ro_RO/rom.json # frontend/src/locales/ru_RU/rom.json # frontend/src/locales/zh_CN/rom.json # frontend/src/locales/zh_TW/rom.json
ruvelro
marked this pull request as ready for review
August 14, 2026 07:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
masterand resolves the merge conflicts.fs_path/fs_name) during scanning and missing-ROM reconciliation.(platform_id, fs_path, fs_name)so identical file names can coexist in different subfolders.Validation
python3 -m py_compile backend/config/config_manager.py backend/endpoints/sockets/scan.py backend/handler/filesystem/roms_handler.py backend/handler/database/roms_handler.py backend/models/rom.py backend/alembic/versions/0091_unique_platform_fs_name.pypython3 frontend/src/locales/check_i18n_sorted.pygit diff --checkrg -n "^(<<<<<<<|=======|>>>>>>>)" .../.venv/bin/python -m pytest tests/config/test_config_loader.py tests/handler/filesystem/test_roms_handler.py tests/handler/test_db_handler.py tests/endpoints/sockets/test_scan.py tests/handler/test_fastapi.py../.venv/bin/python -m pytest(2842 passed, 2 skipped)npm run typechecknpm test(693 passed)npm run buildNotes
rommapp/rommwas denied for the authenticated account, so this PR is opened from theruvelro/rommfork.npm run buildemits existing warnings about Browserslist data,:deep, chunk size, and directevalinvue3-pdf-app, but completes successfully.