91 lines
3.0 KiB
Python
91 lines
3.0 KiB
Python
"""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
|