Sanadv3/vision/recognition_state.py

69 lines
2.0 KiB
Python

"""Recognition state file — atomic JSON I/O shared by parent + child.
The dashboard (parent process) writes this file on every toggle / face
gallery change; the Gemini child (`gemini/script.py`) polls it at 1 Hz
to flip its in-memory flags without a session restart.
Format (data/.recognition_state.json):
{
"vision_enabled": bool,
"face_rec_enabled": bool,
"gallery_version": int # bumped on every face CRUD
}
"""
from __future__ import annotations
import json
import os
import tempfile
from dataclasses import asdict, dataclass
from pathlib import Path
@dataclass
class RecognitionState:
vision_enabled: bool = False
face_rec_enabled: bool = False
gallery_version: int = 0
def read(path: Path) -> RecognitionState:
"""Return the persisted state, or a default if missing/corrupt."""
try:
raw = json.loads(Path(path).read_text(encoding="utf-8"))
except (FileNotFoundError, json.JSONDecodeError, OSError):
return RecognitionState()
return RecognitionState(
vision_enabled=bool(raw.get("vision_enabled", False)),
face_rec_enabled=bool(raw.get("face_rec_enabled", False)),
gallery_version=int(raw.get("gallery_version", 0)),
)
def write(path: Path, state: RecognitionState) -> None:
"""Write atomically via tempfile + os.replace."""
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(prefix=f".{p.name}.", suffix=".tmp", dir=str(p.parent))
try:
with os.fdopen(fd, "w", encoding="utf-8") as fh:
json.dump(asdict(state), fh, ensure_ascii=False, indent=2)
os.replace(tmp, p)
except Exception:
try:
os.unlink(tmp)
except OSError:
pass
raise
def mutate(path: Path, **changes) -> RecognitionState:
"""Read-modify-write helper. Returns the new state."""
cur = read(path)
for k, v in changes.items():
if hasattr(cur, k):
setattr(cur, k, v)
write(path, cur)
return cur