Skip to content

feat: add state backup and auto-recovery#218

Open
christopherblaisdell wants to merge 2 commits intodanielcopper:mainfrom
christopherblaisdell:feat/state-backup-recovery
Open

feat: add state backup and auto-recovery#218
christopherblaisdell wants to merge 2 commits intodanielcopper:mainfrom
christopherblaisdell:feat/state-backup-recovery

Conversation

@christopherblaisdell
Copy link
Copy Markdown

Summary

Adds a rolling \state.json.prev\ backup to prevent total state loss from mid-sync crashes or corruption. If state.json loses its shortcut registry (e.g., due to a crash during write), the previous state is automatically recovered on next load.

Motivation

The plugin's state.json holds the shortcut registry — the mapping of RomM ROM IDs to Steam shortcut app IDs. If this file is corrupted or emptied during a crash (e.g., CEF crash during sync, power loss, filesystem error), all shortcut mappings are lost and the user has to re-sync from scratch. With hundreds of shortcuts, this is a significant data loss.

What changed

persistence.py — save_state()

  • Before each write, rotates the current \state.json\ to \state.json.prev\ using \os.replace()\ (atomic)
  • Rotation happens under the existing file lock, before the new data is written
  • Suppresses OSError if rotation fails (e.g., no prior state file)

persistence.py — load_state()

  • After loading state.json, checks if \shortcut_registry\ is empty
  • If empty, attempts to load \state.json.prev\ and checks its registry
  • If .prev has entries, auto-recovers by using the previous state
  • Logs a warning when recovery occurs so the user knows it happened
  • Handles missing/corrupt .prev gracefully (no crash, no recovery attempt)

test_persistence.py — 8 new test cases

  • \ est_save_state_creates_prev_file\ — .prev created on second save
  • \ est_save_state_rotates_prev_on_each_write\ — .prev tracks previous state
  • \ est_load_state_recovers_from_empty_registry\ — auto-recovery works
  • \ est_load_state_no_recovery_when_registry_has_entries\ — normal case unaffected
  • \ est_load_state_no_recovery_when_no_prev_file\ — no .prev = no recovery
  • \ est_load_state_no_recovery_when_prev_also_empty\ — both empty = no recovery
  • \ est_load_state_no_recovery_when_prev_is_corrupt\ — corrupt .prev handled

Risk

Very low — filesystem operations with defensive error handling. Normal save/load path unchanged except for the additional .prev rotation step.

PR size

121 insertions, 1 deletion across 2 files.

Christopher Blaisdell added 2 commits April 5, 2026 16:38
Rolling .prev backup for state.json prevents total state loss from
mid-sync crashes or corruption.

save_state():
- Rotates current state.json to state.json.prev before each write
- Uses os.replace() for atomic rotation under the existing file lock

load_state():
- Detects empty shortcut_registry in state.json
- If state.json.prev has a non-empty registry, auto-recovers from it
- Logs a warning when recovery occurs so the user knows it happened
- Handles missing/corrupt .prev gracefully (no crash, no recovery)

Tests: 8 new test cases covering rotation, recovery, and edge cases
(empty .prev, corrupt .prev, no .prev file, normal operation)
@danielcopper
Copy link
Copy Markdown
Owner

No response since 2026-04-08 to the questions in #215 re: commit f86bf0d (which removes the game detail patch and would disable that whole feature).

Deferring this PR until the v1 release. Happy to resume review then if:

If neither happens by v1 release time, I'll likely close this and the sibling PRs (#215, #216, #218, #219). Ping me anytime to revive.

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.

2 participants