Skip to content

refactor: extract shared state_store; route usb_power through it#45

Merged
szrudi merged 3 commits into
mainfrom
refactor/shared-state-store
Jun 13, 2026
Merged

refactor: extract shared state_store; route usb_power through it#45
szrudi merged 3 commits into
mainfrom
refactor/shared-state-store

Conversation

@szrudi

@szrudi szrudi commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Infrastructure split out of #39 so that PR stays focused on the feature. No version bump — ships in the single v1.8.0 with #39, which builds on this.

Why

usb_power persisted its (hub, port) cache by serializing just those two keys and replacing the whole state file. That's an atomic write but a full-file overwrite — fine while usb_power is the only writer, but it would clobber any other feature's slice of the same file.

What

server/state_store.py — the single owner of LABELLE_STATE_FILE:

  • Atomic read-modify-write of the whole document under a process-wide lock (so independent features each persist their slice without clobbering).
  • Best-effort: swallows both I/O and JSON-serialization errors (a bad write loses cross-restart memory, never crashes the request).
  • Tolerant of a missing / corrupt / non-UTF8 file (treated as empty).

usb_power now routes its load/save through it — same on-disk shape (hub/port at top level), same path argument, behavior-preserving (its tests still pass). It also now warns on a present-but-invalid hub/port shape.

A smoke test guards the invariant: only state_store.py may reference LABELLE_STATE_FILE, so no feature reintroduces direct (clobbering) access.

Why now

This is the shared-file foundation the upcoming per-printer settings (#39) builds on. Landing it separately keeps #39 to just the feature.

Tests

New test_state_store.py (atomic RMW, key preservation, concurrency, corrupt/non-UTF8/serialization handling); test_usb_power updated for the delegation + invalid-shape warning; smoke ownership guard. Full backend suite green (252).

🤖 Generated with Claude Code

usb_power persisted its (hub, port) cache by serializing just those keys
and replacing the whole file — fine as the sole writer, but it would
clobber any other feature's slice. Extract state_store.py as the single
owner of LABELLE_STATE_FILE: atomic read-modify-write of the whole
document under a process-wide lock, best-effort (swallows I/O and
serialization errors), tolerant of a missing/corrupt/non-UTF8 file.
Route usb_power's load/save through it (same on-disk shape, same path
arg; also warns on a present-but-invalid hub/port shape).

Behavior-preserving for usb_power; sets up safe multi-writer sharing for
the upcoming per-printer settings (#39). A smoke guard asserts
state_store is the only module referencing LABELLE_STATE_FILE.

No version bump — ships in v1.8.0 with #39, which builds on this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a shared, atomic read-modify-write JSON state store (LABELLE_STATE_FILE) so multiple backend features can persist independent slices of state without clobbering each other (e.g., USB power’s (hub, port) cache alongside upcoming per-printer settings).

Changes:

  • Added server/state_store.py as the single owner of LABELLE_STATE_FILE, providing best-effort atomic read/modify/write under a process-wide lock.
  • Refactored usb_power to load/save its hub/port via state_store instead of overwriting the entire state file.
  • Added/updated tests covering state_store behavior (corrupt/non-UTF8/serialization/concurrency) and a smoke guard ensuring only state_store.py references LABELLE_STATE_FILE.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
server/state_store.py New shared state file owner implementing atomic, locked RMW and tolerant reads/writes.
server/usb_power.py Routes hub/port persistence through state_store and refines logging for invalid shapes.
server/tests/test_state_store.py New tests for atomic RMW, key preservation, error swallowing, and concurrency.
server/tests/test_usb_power.py Updates tests for state_store delegation and adds warnings/quietness assertions.
server/tests/test_smoke.py Adds ownership smoke test to prevent direct LABELLE_STATE_FILE usage outside state_store.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread server/state_store.py
Comment thread server/state_store.py
Comment thread server/usb_power.py Outdated
szrudi and others added 2 commits June 13, 2026 11:31
Path.read_text/write_text default to the process locale encoding, which
can vary between environments and make the persisted JSON unreadable
across restarts if the locale changes. Use encoding="utf-8" on both
sides. Also correct the usb_power comment describing what state_store
actually logs. Adds a non-ASCII round-trip test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@szrudi szrudi merged commit ab3700a into main Jun 13, 2026
1 of 2 checks passed
@szrudi szrudi deleted the refactor/shared-state-store branch June 13, 2026 09:33
szrudi added a commit that referenced this pull request Jun 13, 2026
Brings in #41 (serial ids), #42 (virtual ids), #44 (default printer
selection), #45 (shared state_store + UTF-8). Conflict resolution:
- state_store.py / usb_power.py / test_state_store.py: took main's (the
  #45 versions with UTF-8 read/write + corrected comment + non-ASCII
  test) — they supersede #39's older extracted copies.
- test_smoke.py: kept #39's (superset — adds printer_settings module and
  the /api/printers/<id>/settings route on top of #45's ownership guard).
- useLabelStore setAvailablePrinters: combined main's default-printer
  selection with #39's availablePrintersLoaded flag.
- SettingsBar + its tests, useLabelStore tests, ARCHITECTURE.md:
  unioned #44's selector changes with #39's persist/load-gate.

Frontend 81, backend 276, build green.
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