Harden backup JSON parsing and fix queue display
This commit is contained in:
@@ -30,6 +30,17 @@ async def _unit_status(unit: str, props: list[str]) -> dict[str, str]:
|
||||
return _parse_systemctl_kv(out)
|
||||
|
||||
|
||||
def _load_json(raw: str, label: str) -> tuple[bool, object | None, str]:
|
||||
if not raw or not raw.strip():
|
||||
return False, None, f"? {label} returned empty output"
|
||||
try:
|
||||
return True, json.loads(raw), ""
|
||||
except json.JSONDecodeError:
|
||||
preview = raw.strip().splitlines()
|
||||
head = preview[0] if preview else "invalid output"
|
||||
return False, None, f"? {label} invalid JSON: {head}"
|
||||
|
||||
|
||||
async def send_backup_jobs_status(msg: Message):
|
||||
services = [
|
||||
("backup-auto", "backup-auto.timer"),
|
||||
@@ -78,7 +89,11 @@ async def cmd_repo_stats(msg: Message):
|
||||
await msg.answer(raw1, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
restore = json.loads(raw1)
|
||||
ok, restore, err = _load_json(raw1, "restic stats")
|
||||
if not ok:
|
||||
await msg.answer(err, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
|
||||
# --- raw-data stats ---
|
||||
rc2, raw2 = await run_cmd(
|
||||
@@ -90,7 +105,11 @@ async def cmd_repo_stats(msg: Message):
|
||||
await msg.answer(raw2, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
raw = json.loads(raw2)
|
||||
ok, raw, err = _load_json(raw2, "restic stats raw-data")
|
||||
if not ok:
|
||||
await msg.answer(err, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
|
||||
# --- snapshots count ---
|
||||
rc3, raw_snaps = await run_cmd(
|
||||
@@ -98,7 +117,14 @@ async def cmd_repo_stats(msg: Message):
|
||||
use_restic_env=True,
|
||||
timeout=20
|
||||
)
|
||||
snaps = len(json.loads(raw_snaps)) if rc3 == 0 else "n/a"
|
||||
if rc3 != 0:
|
||||
snaps = "n/a"
|
||||
else:
|
||||
ok, snap_data, err = _load_json(raw_snaps, "restic snapshots")
|
||||
if ok and isinstance(snap_data, list):
|
||||
snaps = len(snap_data)
|
||||
else:
|
||||
snaps = "n/a"
|
||||
|
||||
msg_text = (
|
||||
"📦 **Repository stats**\n\n"
|
||||
@@ -124,7 +150,10 @@ async def cmd_backup_status(msg: Message):
|
||||
await msg.answer(raw, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
snaps = json.loads(raw)
|
||||
ok, snaps, err = _load_json(raw, "restic snapshots")
|
||||
if not ok or not isinstance(snaps, list):
|
||||
await msg.answer(err, reply_markup=backup_kb)
|
||||
return
|
||||
if not snaps:
|
||||
await msg.answer("📦 Snapshots: none", reply_markup=backup_kb)
|
||||
return
|
||||
@@ -193,7 +222,10 @@ async def cmd_last_snapshot(msg: Message):
|
||||
await msg.answer(raw, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
snaps = json.loads(raw)
|
||||
ok, snaps, err = _load_json(raw, "restic snapshots")
|
||||
if not ok or not isinstance(snaps, list):
|
||||
await msg.answer(err, reply_markup=backup_kb)
|
||||
return
|
||||
if not snaps:
|
||||
await msg.answer("📦 Snapshots: none", reply_markup=backup_kb)
|
||||
return
|
||||
@@ -212,7 +244,10 @@ async def cmd_last_snapshot(msg: Message):
|
||||
await msg.answer(raw2, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
stats = json.loads(raw2)
|
||||
ok, stats, err = _load_json(raw2, f"restic stats {short_id}")
|
||||
if not ok or not isinstance(stats, dict):
|
||||
await msg.answer(err, reply_markup=backup_kb)
|
||||
return
|
||||
|
||||
msg_text = (
|
||||
"📦 **Last snapshot**\n\n"
|
||||
|
||||
@@ -41,30 +41,30 @@ async def worker():
|
||||
|
||||
def format_status() -> str:
|
||||
pending = list(_pending)
|
||||
lines = ["?? Queue"]
|
||||
lines.append(f"?? Running: {_current_label or 'idle'}")
|
||||
lines.append(f"? Pending: {len(pending)}")
|
||||
lines = ["🧾 Queue"]
|
||||
lines.append(f"🔄 Running: {_current_label or 'idle'}")
|
||||
lines.append(f"⏳ Pending: {len(pending)}")
|
||||
if pending:
|
||||
preview = ", ".join([p[0] for p in pending[:5]])
|
||||
lines.append(f"?? Next: {preview}")
|
||||
return "".join(lines)
|
||||
lines.append(f"➡️ Next: {preview}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_details(limit: int = 10) -> str:
|
||||
now = time.time()
|
||||
lines = ["?? Queue details"]
|
||||
lines = ["🧾 Queue details"]
|
||||
if _current_label:
|
||||
started_at = _current_meta.get("started_at") if _current_meta else None
|
||||
runtime = f"{int(now - started_at)}s" if started_at else "n/a"
|
||||
lines.append(f"?? Running: {_current_label} ({runtime})")
|
||||
lines.append(f"🔄 Running: {_current_label} ({runtime})")
|
||||
else:
|
||||
lines.append("?? Running: idle")
|
||||
lines.append("🔄 Running: idle")
|
||||
|
||||
pending = list(_pending)
|
||||
lines.append(f"? Pending: {len(pending)}")
|
||||
lines.append(f"⏳ Pending: {len(pending)}")
|
||||
if pending:
|
||||
lines.append("?? Position | Label | Wait")
|
||||
lines.append("🔢 Position | Label | Wait")
|
||||
for i, (label, enqueued_at) in enumerate(pending[:limit], start=1):
|
||||
wait = int(now - enqueued_at)
|
||||
lines.append(f"{i:>3} | {label} | {wait}s")
|
||||
return "".join(lines)
|
||||
return "\n".join(lines)
|
||||
|
||||
Reference in New Issue
Block a user