Skip to content

Fix custom library structure PR merge issues - #4196

Open
ruvelro wants to merge 7 commits into
rommapp:masterfrom
ruvelro:fix/pr-3571-custom-library-structure
Open

Fix custom library structure PR merge issues#4196
ruvelro wants to merge 7 commits into
rommapp:masterfrom
ruvelro:fix/pr-3571-custom-library-structure

Conversation

@ruvelro

@ruvelro ruvelro commented Aug 14, 2026

Copy link
Copy Markdown

Summary

  • Revives PR feat: Per-platform custom library structure with non-destructive identity #3571 on top of current master and resolves the merge conflicts.
  • Keeps custom library structure identity based on full ROM paths (fs_path/fs_name) during scanning and missing-ROM reconciliation.
  • Updates the ROM uniqueness constraint to use (platform_id, fs_path, fs_name) so identical file names can coexist in different subfolders.
  • Sorts the touched locale files with the repository i18n checker.

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.py
  • python3 frontend/src/locales/check_i18n_sorted.py
  • git diff --check
  • rg -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 typecheck
  • npm test (693 passed)
  • npm run build

Notes

  • Direct push to rommapp/romm was denied for the authenticated account, so this PR is opened from the ruvelro/romm fork.
  • npm run build emits existing warnings about Browserslist data, :deep, chunk size, and direct eval in vue3-pdf-app, but completes successfully.

claude and others added 7 commits June 21, 2026 20:46
…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
ruvelro marked this pull request as ready for review August 14, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants