Files
proxmox-snapshot-service/pvesnap/state.py
T
2026-07-30 16:52:58 +02:00

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