"""Persistenter Zustand: wann lief welche Gruppe zuletzt?""" from __future__ import annotations import json import logging import os from datetime import datetime log = logging.getLogger("pvesnap.state") _TIME_FORMAT = "%Y-%m-%dT%H:%M:%S" class State: def __init__(self, path): self.path = path self.data = {"version": 1, "groups": {}} self._dirty = False # -- Laden / Speichern ------------------------------------------------ def load(self): if not os.path.exists(self.path): return self try: with open(self.path, "r", encoding="utf-8") as handle: data = json.load(handle) if isinstance(data, dict) and isinstance(data.get("groups"), dict): self.data = data self.data.setdefault("version", 1) except (OSError, ValueError) as exc: log.warning("Zustandsdatei %s nicht lesbar (%s) - starte mit leerem Zustand", self.path, exc) return self def save(self, force=False): if not self._dirty and not force: return directory = os.path.dirname(os.path.abspath(self.path)) try: os.makedirs(directory, exist_ok=True) tmp = self.path + ".tmp" with open(tmp, "w", encoding="utf-8") as handle: json.dump(self.data, handle, indent=2, ensure_ascii=False) handle.write("\n") handle.flush() os.fsync(handle.fileno()) os.replace(tmp, self.path) self._dirty = False except OSError as exc: log.error("Zustand konnte nicht gespeichert werden (%s): %s", self.path, exc) # -- Zugriff ---------------------------------------------------------- def _entry(self, group_name): return self.data["groups"].setdefault(group_name, {}) def last_run(self, group_name): raw = self._entry(group_name).get("last_run") if not raw: return None try: return datetime.strptime(raw, _TIME_FORMAT) except ValueError: return None def record_run(self, group_name, moment, created=0, deleted=0, errors=None): entry = self._entry(group_name) entry["last_run"] = moment.strftime(_TIME_FORMAT) entry["created"] = created entry["deleted"] = deleted entry["errors"] = list(errors or []) entry["status"] = "error" if errors else "ok" entry["total_runs"] = int(entry.get("total_runs") or 0) + 1 self._dirty = True def info(self, group_name): return dict(self._entry(group_name)) def forget(self, group_name): if group_name in self.data["groups"]: del self.data["groups"][group_name] self._dirty = True def prune_unknown(self, known_names): known = set(known_names) for name in [n for n in self.data["groups"] if n not in known]: del self.data["groups"][name] self._dirty = True