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)
|
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):
|
async def send_backup_jobs_status(msg: Message):
|
||||||
services = [
|
services = [
|
||||||
("backup-auto", "backup-auto.timer"),
|
("backup-auto", "backup-auto.timer"),
|
||||||
@@ -78,7 +89,11 @@ async def cmd_repo_stats(msg: Message):
|
|||||||
await msg.answer(raw1, reply_markup=backup_kb)
|
await msg.answer(raw1, reply_markup=backup_kb)
|
||||||
return
|
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 ---
|
# --- raw-data stats ---
|
||||||
rc2, raw2 = await run_cmd(
|
rc2, raw2 = await run_cmd(
|
||||||
@@ -90,7 +105,11 @@ async def cmd_repo_stats(msg: Message):
|
|||||||
await msg.answer(raw2, reply_markup=backup_kb)
|
await msg.answer(raw2, reply_markup=backup_kb)
|
||||||
return
|
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 ---
|
# --- snapshots count ---
|
||||||
rc3, raw_snaps = await run_cmd(
|
rc3, raw_snaps = await run_cmd(
|
||||||
@@ -98,7 +117,14 @@ async def cmd_repo_stats(msg: Message):
|
|||||||
use_restic_env=True,
|
use_restic_env=True,
|
||||||
timeout=20
|
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 = (
|
msg_text = (
|
||||||
"📦 **Repository stats**\n\n"
|
"📦 **Repository stats**\n\n"
|
||||||
@@ -124,7 +150,10 @@ async def cmd_backup_status(msg: Message):
|
|||||||
await msg.answer(raw, reply_markup=backup_kb)
|
await msg.answer(raw, reply_markup=backup_kb)
|
||||||
return
|
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:
|
if not snaps:
|
||||||
await msg.answer("📦 Snapshots: none", reply_markup=backup_kb)
|
await msg.answer("📦 Snapshots: none", reply_markup=backup_kb)
|
||||||
return
|
return
|
||||||
@@ -193,7 +222,10 @@ async def cmd_last_snapshot(msg: Message):
|
|||||||
await msg.answer(raw, reply_markup=backup_kb)
|
await msg.answer(raw, reply_markup=backup_kb)
|
||||||
return
|
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:
|
if not snaps:
|
||||||
await msg.answer("📦 Snapshots: none", reply_markup=backup_kb)
|
await msg.answer("📦 Snapshots: none", reply_markup=backup_kb)
|
||||||
return
|
return
|
||||||
@@ -212,7 +244,10 @@ async def cmd_last_snapshot(msg: Message):
|
|||||||
await msg.answer(raw2, reply_markup=backup_kb)
|
await msg.answer(raw2, reply_markup=backup_kb)
|
||||||
return
|
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 = (
|
msg_text = (
|
||||||
"📦 **Last snapshot**\n\n"
|
"📦 **Last snapshot**\n\n"
|
||||||
|
|||||||
@@ -41,30 +41,30 @@ async def worker():
|
|||||||
|
|
||||||
def format_status() -> str:
|
def format_status() -> str:
|
||||||
pending = list(_pending)
|
pending = list(_pending)
|
||||||
lines = ["?? Queue"]
|
lines = ["🧾 Queue"]
|
||||||
lines.append(f"?? Running: {_current_label or 'idle'}")
|
lines.append(f"🔄 Running: {_current_label or 'idle'}")
|
||||||
lines.append(f"? Pending: {len(pending)}")
|
lines.append(f"⏳ Pending: {len(pending)}")
|
||||||
if pending:
|
if pending:
|
||||||
preview = ", ".join([p[0] for p in pending[:5]])
|
preview = ", ".join([p[0] for p in pending[:5]])
|
||||||
lines.append(f"?? Next: {preview}")
|
lines.append(f"➡️ Next: {preview}")
|
||||||
return "".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def format_details(limit: int = 10) -> str:
|
def format_details(limit: int = 10) -> str:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
lines = ["?? Queue details"]
|
lines = ["🧾 Queue details"]
|
||||||
if _current_label:
|
if _current_label:
|
||||||
started_at = _current_meta.get("started_at") if _current_meta else None
|
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"
|
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:
|
else:
|
||||||
lines.append("?? Running: idle")
|
lines.append("🔄 Running: idle")
|
||||||
|
|
||||||
pending = list(_pending)
|
pending = list(_pending)
|
||||||
lines.append(f"? Pending: {len(pending)}")
|
lines.append(f"⏳ Pending: {len(pending)}")
|
||||||
if pending:
|
if pending:
|
||||||
lines.append("?? Position | Label | Wait")
|
lines.append("🔢 Position | Label | Wait")
|
||||||
for i, (label, enqueued_at) in enumerate(pending[:limit], start=1):
|
for i, (label, enqueued_at) in enumerate(pending[:limit], start=1):
|
||||||
wait = int(now - enqueued_at)
|
wait = int(now - enqueued_at)
|
||||||
lines.append(f"{i:>3} | {label} | {wait}s")
|
lines.append(f"{i:>3} | {label} | {wait}s")
|
||||||
return "".join(lines)
|
return "\n".join(lines)
|
||||||
|
|||||||
Reference in New Issue
Block a user