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)
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.

1 participant