feat(cloud-sync): support config/thumbnails/system as opaque per-user blobs - #3931
Conversation
… blobs RetroArch's Cloud Sync also offers a config/, thumbnails/, and system/ category alongside saves/states, none of which belong to a ROM. The previous behavior rejected them with 409 so RetroArch would keep retrying locally instead of losing data. This stores them as plain per-user files under CLOUD_SYNC_BLOB_BASE_PATH (FSCloudSyncBlobHandler) instead, namespaced by user so two RetroArch installs syncing to the same RomM instance under different accounts don't clobber each other's files, and includes them in the manifest alongside saves/states.
|
Psp suppont not added yet. Waiting for review. |
RetroArch's cloud-sync core-name segment uses its own directory casing (e.g. "Snes9x"), while RomM's emulator field convention is the lowercase libretro core id (e.g. "snes9x") -- the same convention its own web player matches saves against exactly. Storing the raw RetroArch casing made a synced save invisible in RomM's web player, and could hand RetroArch back a folder name its local install never uses. Adds a small translation table (ported from the community romm-retroarch-sync project) plus a safe universal normalization for the write direction. Cores outside the table round-trip unchanged rather than guessing at an unverified casing.
|
Added a second commit: normalizes the emulator/core-name casing between RetroArch's cloud-sync path segment (its own directory casing, e.g. Without this, a save synced in over cloud-sync would store
10 new tests (table lookups both directions + a manifest round-trip test), 60/60 passing, no regressions in the broader asset/save/state suite. |
Summary
Builds on #3904. RetroArch's Cloud Sync also offers a
config/,thumbnails/, andsystem/category alongside saves/states (Settings → Saving → Cloud Sync → Sync Configuration/Thumbnails/System Files). None of these belong to a ROM, so the current PR rejects them with 409 rather than answering a fake success — correct, but it means those three toggles simply can't be used.This adds them as opaque per-user blobs instead of going through the asset/ROM matching #3904 introduces:
CLOUD_SYNC_BLOB_BASE_PATH(new config path, alongside the existing library/resources/assets paths underROMM_BASE_PATH).FSCloudSyncBlobHandler(backend/handler/filesystem/cloud_sync_blob_handler.py) — plain-file storage built on the existingFSHandlerbase (path validation, atomic writes, locking all come from there for free).cloud_sync_handler.py:parse_cloud_sync_blob_path,blob_md5(same Redis-cache-by-path+size+mtime approach asasset_md5),build_blob_manifest_entries.cloud_sync.py: GET/PUT/DELETE/MOVE now branch to blob handling before falling through to the asset logic.Blobs are namespaced per RomM user (
fs_asset_handler.user_folder_path), so two RetroArch installs syncing to the same RomM instance under different accounts never see each other's config/thumbnails/system files — matching how saves/states are already scoped.Unlike asset hashes, blob hashes are always real MD5s of the file on disk (no
content_hash-is-null problem to work around), still cached the same way since these files are typically tiny but numerous (thumbnail packs especially).test_rejects_unsupported_sync_rootnow asserts against a genuinely unsupported root (deleted/saves/...) instead ofconfig/, sinceconfig/is no longer rejected. A newTestCloudSyncBlobPathParsing+TestCloudSyncBlobsclass covers path parsing, upload/download/delete, nested thumbnail paths, and manifest inclusion.Test plan
uv run pytest backend/tests/endpoints/test_cloud_sync.py— 49 passed (34 existing + 15 new/updated)uv run pytest backend/tests/filtered to asset/save/state/filesystem/handler suites — 1119 passed, no regressionsruff check/ruff format --checkclean on all changed filesAI assistance disclosure
Written with Claude Code, building directly on the design and conventions established in #3904.