first commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user