Commit Graph

146 Commits

Author SHA1 Message Date
Georges-Antoine Assi
ae60d14f81 Merge branch 'master' into feat/composite-hashing-archives 2026-05-29 11:50:17 -04:00
nendo
41c91fdd5b SaveSync: push null-slot exclusion into the SQL query
Three sync callsites (endpoints/sync.py, sync_watcher.py, and both
branches of tasks/sync_push_pull_task.py) ran get_saves(...) and then
discarded archival null-slot rows in a Python list comprehension. On
libraries with many archival/web-UI uploads that's a strict waste:
those rows are pulled from MariaDB, hydrated into Save model instances,
and then immediately filtered out.

Add a slot_not_null bool kwarg to DBSavesHandler.get_saves and apply
the filter in the SQL query. Update all four callsites to use it and
drop the Python-side comprehension. Default stays False so unrelated
callers keep the current behavior.
2026-05-29 17:40:18 +09:00
nendo
5bb10dacd1 SaveSync: paginate recompute task scan by primary key
get_all_saves() materialized every Save row across all users into a
single .all() list. On instances with very large libraries that's a
real RAM ceiling and pins every row for the lifetime of the recompute
run.

Replace it with get_saves_after_id(after_id, limit) and have the
recompute task drive keyset pagination in PAGE_SIZE-row chunks. SQLAlchemy
streaming via .execution_options(yield_per=...) is incompatible with the
per-call session lifetime that @begin_session enforces (the session
exits before the consumer iterates), so keyset paging from the caller is
the cleanest fit.

Behavior is unchanged: same row coverage, same idempotency, same
counters. Memory usage drops from O(all saves) to O(PAGE_SIZE).
2026-05-29 17:38:49 +09:00
nendo
edb5d15420 Fix save-sync hash drift, archival save leak, and dedupe scoping
Cleanup pass on save-sync addressing three independent failure modes
that interact in production data: content_hash drift between client
and server, null-slot archival saves leaking into sync flows, and
content-hash dedupe collapsing legitimately-distinct slots.

Bug fixes
- compute_content_hash dispatched on zipfile.is_zipfile(relative_path),
  which silently returned False whenever the process's CWD wasn't
  ASSETS_BASE_PATH. Every zip save fell through to the raw-MD5 branch,
  persisting hashes that disagreed with clients computing the intended
  per-entry zip-hash. Resolve to a full path before the dispatch.
- _build_negotiate_plan, sync_push_pull_task, and sync_watcher all
  treated null-slot saves as sync-eligible. Null-slot saves represent
  web-UI / archival uploads; including them in negotiate plans matched
  them against device pushes by filename and overwrote archival data.
  Filter null-slot saves at all three call sites.
- get_save_by_content_hash matched on (rom_id, user_id, content_hash)
  only, so identical bytes uploaded to different slots collapsed into
  one record. Scope the lookup by slot when provided so clone-save-
  to-new-slot creates a distinct row per slot.
- get_save_by_filename matched on (rom_id, user_id, file_name) only.
  When two uploads to different slots happened in the same wall-clock
  second (the datetime tag is per-second), the second upload UPDATED
  the first record's slot instead of creating a distinct row. Scope
  the filename lookup by slot too.

One-shot recovery
- New recompute_save_content_hashes manual task walks every Save row,
  recomputes via the fixed dispatch, and updates rows whose values
  differ. Idempotent; safe to re-run.
- Backend startup runs a COUNT(content_hash IS NULL) query and, if
  any rows exist, enqueues the recompute task on the low-priority
  RQ queue. The API process moves on; the worker handles the
  recompute out-of-band. Subsequent restarts find zero NULL hashes
  and skip. Admins can also trigger the task manually.

Test infrastructure
- Added tests/_zipfile_shim.reload_zipfile() mirroring the pattern
  from utils/zip_cache.py for the same zipfile-inflate64 + CPython
  3.13.5 incompatibility. Test fixtures that build ZIPs call it
  immediately before opening the archive.
2026-05-29 17:00:01 +09:00
Georges-Antoine Assi
a1194dc5e0 changes from bot review 2026-05-28 09:02:26 -04:00
Georges-Antoine Assi
bb2b99099b fix(sync): isolate SSH handler init errors to per-device failure
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:12:10 -04:00
Claude
26bdc11e13 refactor(filesystem): lazy-init launchbox + sync handlers, drop tolerate_missing_base
Apply the same lazy-factory pattern to FSLaunchboxHandler and FSSyncHandler
that ssh_sync_handler now uses. With both opt-in features deferred to
first-use, the tolerate_missing_base escape hatch on FSHandler is no longer
needed — every handler now fails loudly on mkdir failure, which is the
right behavior for the always-on core paths (assets, library, resources).

Touched call sites:
  - resources_handler._resolve_local_file_uri (launchbox)
  - sync_watcher.py, endpoints/device.py, tasks/manual/sync_folder_scan.py
    (fs_sync)

Net effect:
  - Default installs never poke /romm/launchbox or /romm/sync at startup.
  - Misconfigured opt-in users get a clear, actionable PermissionError at
    the call site instead of a silent warning followed by mystery failures.
  - tolerate_missing_base, its tests, and one stale log import are gone.
2026-05-24 14:59:03 +00:00
Claude
1890958ff2 refactor(sync): make SSH sync handler lazy-initialized
The module-level SSHSyncHandler() singleton ran filesystem side effects
(mkdir on SYNC_SSH_KEYS_PATH) at import time, which meant even default
installs with push-pull sync disabled would touch /romm/sync — and the
previous tolerate-and-warn fallback could leave users wondering why
sync silently does nothing.

Replace the eager singleton with a functools.cache'd factory. The
handler is now constructed on first use, so:

  - Default-install users (ENABLE_SYNC_PUSH_PULL=false, no manual sync
    triggered) never touch /romm/sync.
  - Users opting in get a clear, actionable RuntimeError pointing at
    the unwritable path and the env var to override, at the call site
    rather than buried in a startup stack trace.

Also document in env.template that enabling either sync mode requires
a writable volume at $ROMM_BASE_PATH/sync.
2026-05-24 14:49:53 +00:00
Georges-Antoine Assi
715ec658bd changes from bot review 2026-05-23 07:37:23 -04:00
Georges-Antoine Assi
8c3d82784f fix cleanup orphaned resources 2026-05-18 20:30:18 -04:00
Georges-Antoine Assi
832447bbb1 rnu fmt 2026-04-29 14:30:48 -04:00
copilot-swe-agent[bot]
80b5fdf21f fix: add periodic janitor to clean up orphaned upload tmp directories
Agent-Logs-Url: https://github.com/rommapp/romm/sessions/2e55dd7f-d99d-48a2-ae15-0b8c4817cc6e

Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-04-29 16:42:12 +00:00
Georges-Antoine Assi
4928041593 manual cleanu 2026-04-12 11:04:12 -04:00
Georges-Antoine Assi
f107dc2752 changes from bot rview 2026-03-22 17:17:14 -04:00
Georges-Antoine Assi
4c97eddfc3 fix trunk check 2026-03-22 16:30:14 -04:00
nendo
4edb1710a5 fix: address review feedback on session handler and counter
- Restore NoResultFound behavior on update_session, complete_session,
  fail_session when row is missing (scalar returns None, old .one()
  raised -- silent None is a semantic regression)
- Remove redundant get_session call from _increment_session_counter;
  the atomic SQL increment is already a no-op on missing rows
- Log warning when passed session_id is not found in _sync_device
  instead of silently creating an orphan session
2026-03-16 10:59:49 +09:00
nendo
55638d15dc fix: address bugs, security issues, and convention violations in save-sync
- Fix broken path construction in FSSyncHandler: build_* methods now
  return relative paths; sync_watcher uses paths relative to sync base
  instead of CWD (was completely non-functional in production)
- Fix SSH connection leak in push-pull task: conn.close() now in finally
- Add log.warning for disabled SSH host key verification
- Fix race condition in session operation counter: use atomic SQL
  increment instead of read-then-write
- Extract _increment_session_counter helper, add exc_info to warnings
- Replace legacy session.query() with select() in sync_sessions_handler
- Fix orphaned session: trigger_push_pull now passes session_id to job
- Fix wasteful SSH download when no matched_save exists
- Fix BaseModel import collision in sync.py (pydantic -> project base)
- Fix ORM mutation in UserSchema.from_orm_with_request: set field on
  schema instance instead of mutating live ORM object
- Mask ssh_password and ssh_key_path in DeviceSchema API response
- Fix migration PostgreSQL compatibility: condition ON UPDATE clause
  on MySQL, drop enum in downgrade
- Rename copy-paste artifact rom_user_status_enum
2026-03-16 10:56:43 +09:00
Georges-Antoine Assi
f13f929d7d tweaks based on self review 2026-03-14 22:26:53 -04:00
Georges-Antoine Assi
e6ddc5da11 bot attempt at save sync 2026-03-14 22:13:38 -04:00
Georges-Antoine Assi
7146081d75 dont update user set statutuses 2026-03-12 20:26:30 -04:00
Georges-Antoine Assi
72e884a83c run fmt 2026-03-12 19:02:24 -04:00
copilot-swe-agent[bot]
4234ca3953 fix: always update rom_user status from RA award on each sync run
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-03-12 22:59:47 +00:00
copilot-swe-agent[bot]
da30e4daf1 feat: auto-update game status from RetroAchievements award kind
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-03-11 02:27:38 +00:00
Georges-Antoine Assi
8f8ef1f700 some changes from review 2026-03-10 16:13:21 -04:00
copilot-swe-agent[bot]
f938e627b4 Move cleanup-all missing ROMs to background task to prevent browser hang
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-03-10 02:25:58 +00:00
Georges-Antoine Assi
0c1ea31856 retrun still 2026-03-09 22:06:29 -04:00
copilot-swe-agent[bot]
5e1a4bf35e Fix: automatic library scan blocked when no metadata sources configured
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-03-10 01:56:47 +00:00
Georges-Antoine Assi
eda88b70d1 get claude to refactor launchbox_handler 2026-03-07 16:02:39 -05:00
Georges-Antoine Assi
395b857592 changes from self review 2026-03-07 14:44:02 -05:00
Georges-Antoine Assi
6de46eb067 Merge branch 'master' into launchbox-handler 2026-03-07 11:31:03 -05:00
Georges-Antoine Assi
ee8b55e6ef last set of changes 2026-03-07 09:56:17 -05:00
Georges-Antoine Assi
7c2c964c32 fix check issues 2026-01-28 16:05:19 -05:00
Georges-Antoine Assi
2a1583e504 Merge branch 'master' into launchbox-handler 2026-01-28 15:01:01 -05:00
Gravel Freeman
732cc2a530 add support for importing metadata from a local launchbox installation 2026-01-19 13:12:27 -05:00
Georges-Antoine Assi
3d7a9c2d1d Merge pull request #2885 from rommapp/hotfix-multi-task-schedule
[HOTFIX] Dont schedule tasks if already queued
2026-01-10 09:40:35 -05:00
Georges-Antoine Assi
743bddcb23 add valkey to pytest ci 2026-01-10 09:22:46 -05:00
Georges-Antoine Assi
a1fafdc081 only run netplay cleanup every 30 minutes 2026-01-09 18:10:04 -05:00
Georges-Antoine Assi
38ce9897d4 [HOTFIX] Dont schedule tasks if already queued 2026-01-09 17:55:16 -05:00
zurdi
ab637203ac Merge branch 'master' into feat/rom-filter-multivalue 2025-12-24 12:39:45 +00:00
Victor R. Santos
e131df3c74 Allow Launchbox case-insensitive rom search 2025-12-22 15:42:23 -03:00
zurdi
33b2f643ee Change scan type to QUICK for scheduled library scans 2025-12-17 15:01:04 +00:00
Georges-Antoine Assi
1812b1a3f9 changes from bot reivew 2025-12-07 12:19:13 -05:00
Georges-Antoine Assi
09dbb2e244 use redis to store netplay data 2025-12-07 12:02:35 -05:00
zurdi
c1d43f67f8 Refactor ROM retrieval to support multi-value platform filtering across various handlers and endpoints 2025-11-27 22:52:33 +00:00
Georges-Antoine Assi
b2dea510c4 [ROMM-2628] Fix desirialize job func_name 2025-11-10 17:57:28 -05:00
Georges-Antoine Assi
525bffe9d0 clarify orphan cleanup task 2025-10-30 10:51:42 -04:00
Georges-Antoine Assi
f30f0bfd75 Smarter detection of whether to scan roms 2025-10-26 13:21:56 -04:00
Georges-Antoine Assi
9fa15d20f0 totally refactor scan types 2025-10-23 16:57:40 -04:00
Georges-Antoine Assi
9a8899f678 use explicit kwargs on scan 2025-10-19 12:43:03 -04:00
Georges-Antoine Assi
b689e3bde1 [ROMM-2531] Allow scanning without metadata providers 2025-10-19 12:37:18 -04:00