Grotti 3.0 Phase 1: Migrationen, Pool, Queue- und Abzug-Fixes.

Nummerierte SQL-Migrationen, psycopg-Pool, waitress, hängende Queue-Jobs,
sichere History-Abzüge, Chargen-Locks, Upload-/Host-Validierung, ProxyFix.
This commit is contained in:
Dominic Gundel committed 2026-10-18 00:47:23 +02:00
1 parent 9e22cebb81
commit 0236278929
19 files changed
+986 -81

No files matched your search

+36 -21
View File
@@ -43,14 +43,7 @@ def _targets_from_ace(conn, host, ace):
targets = []
for item in ace:
slot = int(item["ams_index"]) + 1
row = conn.execute(
"""
SELECT * FROM spools
WHERE printer_host = %s AND ace_slot = %s
ORDER BY id LIMIT 1
""",
(host, slot),
).fetchone()
row = store.spool_at_slot(conn, host, slot)
if row:
targets.append(row)
return targets
@@ -62,12 +55,18 @@ def unix_to_dt(value):
return datetime.fromtimestamp(float(value), timezone.utc)
def choose_spool(conn, host, material, hinted_id):
def choose_spool(conn, host, material, hinted_id, ended_at=None):
"""Spule für History-Abzug. Ohne Hint nur Rollen, die vor Druckende existierten."""
if hinted_id:
row = store.get_spool_row(conn, hinted_id)
if row:
if row and _spool_valid_for_end(row, ended_at):
return row
rows = store.spools_for_printer(conn, host)
if row and not _spool_valid_for_end(row, ended_at):
return None
rows = [
row for row in store.spools_for_printer(conn, host)
if _spool_valid_for_end(row, ended_at)
]
if len(rows) == 1:
return rows[0]
if material:
@@ -77,6 +76,24 @@ def choose_spool(conn, host, material, hinted_id):
return None
def _spool_valid_for_end(spool, ended_at):
"""Verhindert Abzug alter Moonraker-Jobs auf neu eingelegte Spulen."""
if ended_at is None or not spool:
return True
created = spool.get("created_at")
if created is None:
return True
end = ended_at
if getattr(created, "tzinfo", None) is None and getattr(end, "tzinfo", None) is not None:
created = created.replace(tzinfo=timezone.utc)
if getattr(end, "tzinfo", None) is None and getattr(created, "tzinfo", None) is not None:
end = end.replace(tzinfo=timezone.utc)
try:
return created <= end
except TypeError:
return True
def file_material_types(file_row):
types = []
for item in (file_row or {}).get("filaments") or []:
@@ -93,14 +110,7 @@ def resolve_spools_at_host(conn, host, ace_map=None):
if mapping:
for item in mapping:
slot = int(item["ams_index"]) + 1
row = conn.execute(
"""
SELECT * FROM spools
WHERE printer_host = %s AND ace_slot = %s
ORDER BY id LIMIT 1
""",
(host, slot),
).fetchone()
row = store.spool_at_slot(conn, host, slot)
if row:
found.append(row)
return found
@@ -329,7 +339,12 @@ def sync_history(conn, cfg):
file_id = (sent or {}).get("file_id") or (known or {}).get("id")
project_id = (sent or {}).get("project_id") or (known or {}).get("project_id")
hinted = (sent or {}).get("spool_id") or (known or {}).get("spool_id")
spool = choose_spool(conn, printer["host"], material, hinted)
ended_at = unix_to_dt(job.get("end_time"))
# Ohne Send-Eintrag keine Rate — sonst zieht eine neue Spule alle alten Jobs ab.
if not sent and not hinted:
spool = None
else:
spool = choose_spool(conn, printer["host"], material, hinted, ended_at=ended_at)
terminal = state in TERMINAL
live = store.find_live_run(conn, printer["host"], filename)
progress = float((live or {}).get("progress_last") or 0)
@@ -503,7 +518,7 @@ def send_file(
progress("pruefen", 15, "Drucker prüfen…")
try:
live = moonraker.query(host)
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, KeyError) as exc:
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, KeyError, OSError, ValueError) as exc:
raise GrottiError("Der Drucker ist nicht erreichbar.", "offline") from exc
if live.get("state") in ("printing", "paused"):
raise GrottiError("Der Drucker druckt schon.", "belegt", 409)