"""File locking and atomic JSON helpers.""" import fcntl import json import os from contextlib import contextmanager from pathlib import Path @contextmanager def locked_file(path: Path): path.parent.mkdir(parents=True, exist_ok=True) with open(path, "a+", encoding="utf-8") as f: fcntl.flock(f.fileno(), fcntl.LOCK_EX) f.seek(0) yield f f.flush() os.fsync(f.fileno()) fcntl.flock(f.fileno(), fcntl.LOCK_UN) def atomic_write_json(path: Path, data: dict): path.parent.mkdir(parents=True, exist_ok=True) tmp = path.with_suffix(path.suffix + ".tmp") tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") os.replace(tmp, path) def load_json_locked(path: Path, lock_path: Path, default): with locked_file(lock_path): if not path.exists(): return default try: return json.loads(path.read_text(encoding="utf-8")) except Exception: return default def save_json_locked(path: Path, lock_path: Path, data: dict): with locked_file(lock_path): atomic_write_json(path, data)