all
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled

This commit is contained in:
2026-07-17 00:44:22 +02:00
parent 209f81aa3d
commit 9a006f1457
23 changed files with 3712 additions and 484 deletions
+143 -17
View File
@@ -100,6 +100,35 @@ class PrinterService:
async def get_pending(self) -> list[dict]:
return await self.get_queue("pending")
async def get_pending_by_photo_id(self) -> dict[str, list]:
"""Retourne un dict {photo_id_stem: [entries]} pour toutes les demandes actives.
Permet à la galerie admin de savoir quelles photos ont une demande en attente
sans modifier le schéma SQLite — on match par stem du filename.
"""
rows = await self.get_queue("pending")
# Aussi inclure celles "printing" (en cours d'impression)
rows += await self.get_queue("printing")
result: dict[str, list] = {}
for r in rows:
from pathlib import Path
stem = Path(r["filename"]).stem
result.setdefault(stem, []).append(r)
return result
async def cancel_by_photo_id(self, photo_stem: str) -> int:
"""Annule toutes les demandes pending pour un photo_id donné. Retourne le nb annulé."""
pending = await self.get_queue("pending")
from pathlib import Path
cancelled = 0
for r in pending:
if Path(r["filename"]).stem == photo_stem:
ok = await self.cancel(r["id"])
if ok:
cancelled += 1
return cancelled
async def cancel(self, entry_id: str) -> bool:
async with self._lock:
cursor = await self._db.execute(
@@ -186,48 +215,105 @@ class PrinterService:
except Exception as e:
return {"success": False, "error": str(e)}
# ── Helpers ───────────────────────────────────────────────────────────────
def _printer_name(self, p) -> str:
"""Accepte une entrée printers qui soit un str ou un dict."""
return p["name"] if isinstance(p, dict) else str(p)
def _printer_label(self, p) -> str:
return p.get("label", self._printer_name(p)) if isinstance(p, dict) else str(p)
# ── Statut imprimantes CUPS ───────────────────────────────────────────────
async def get_printers_status(self) -> list[dict]:
"""Retourne le statut des imprimantes CUPS configurées."""
"""Retourne le statut détaillé des imprimantes CUPS configurées."""
statuses = []
for p in self._cfg.printers:
status = await asyncio.to_thread(self._get_printer_status, p["name"])
name = self._printer_name(p)
status = await asyncio.to_thread(self._get_printer_status, name)
statuses.append({
"name": p["name"],
"label": p["label"],
"name": name,
"label": self._printer_label(p),
**status,
})
return statuses
def _get_printer_status(self, printer_name: str) -> dict:
"""Statut CUPS complet pour une imprimante (état, jobs, accepting)."""
try:
result = subprocess.run(
# État de l'imprimante
r_state = subprocess.run(
["lpstat", "-p", printer_name],
capture_output=True, text=True, timeout=5
)
output = result.stdout.lower()
if "idle" in output:
out = r_state.stdout.lower()
if r_state.returncode != 0 or "not found" in (r_state.stderr or "").lower():
return {"state": "offline", "accepting": False, "jobs": [], "jobs_count": 0}
if "idle" in out:
state = "idle"
elif "printing" in output or "processing" in output:
elif "printing" in out or "processing" in out:
state = "printing"
elif "disabled" in output:
elif "stopped" in out or "disabled" in out:
state = "disabled"
elif "not found" in output or result.returncode != 0:
state = "offline"
else:
state = "unknown"
# Compte les jobs en attente
jobs_result = subprocess.run(
["lpstat", "-o", printer_name],
# Est-ce que l'imprimante accepte les nouveaux jobs ?
r_accept = subprocess.run(
["lpstat", "-a", printer_name],
capture_output=True, text=True, timeout=5
)
jobs = len([l for l in jobs_result.stdout.strip().splitlines() if l])
accepting = "accepting" in r_accept.stdout.lower()
return {"state": state, "jobs": jobs}
# Liste des jobs CUPS en cours
jobs = self._get_cups_jobs(printer_name)
return {
"state": state,
"accepting": accepting,
"jobs": jobs,
"jobs_count": len(jobs),
}
except Exception as e:
return {"state": "error", "jobs": 0, "error": str(e)}
return {"state": "error", "accepting": False, "jobs": [], "jobs_count": 0, "error": str(e)}
def _get_cups_jobs(self, printer_name: str) -> list[dict]:
"""Retourne la liste des jobs CUPS en cours pour une imprimante."""
try:
r = subprocess.run(
["lpstat", "-o", printer_name, "-l"],
capture_output=True, text=True, timeout=5
)
jobs = []
current = {}
for line in r.stdout.splitlines():
line = line.strip()
if not line:
continue
if line.startswith(printer_name):
if current:
jobs.append(current)
parts = line.split()
current = {
"id": parts[0] if parts else "",
"user": parts[1] if len(parts) > 1 else "",
"size": parts[3] if len(parts) > 3 else "",
"date": " ".join(parts[4:7]) if len(parts) > 6 else "",
"details": [],
}
elif current and ":" in line:
current.setdefault("details", []).append(line)
if current:
jobs.append(current)
return jobs
except Exception:
return []
async def get_cups_jobs(self, printer_name: str) -> list[dict]:
return await asyncio.to_thread(self._get_cups_jobs, printer_name)
async def cancel_cups_jobs(self, printer_name: str) -> bool:
"""Annule tous les jobs CUPS pour une imprimante."""
@@ -240,3 +326,43 @@ class PrinterService:
except Exception as e:
logger.error("Erreur cancel CUPS: %s", e)
return False
async def cancel_cups_job(self, job_id: str) -> bool:
"""Annule un job CUPS précis."""
try:
result = subprocess.run(
["cancel", job_id],
capture_output=True, text=True, timeout=10
)
return result.returncode == 0
except Exception as e:
logger.error("Erreur cancel CUPS job %s: %s", job_id, e)
return False
async def enable_printer(self, printer_name: str) -> bool:
"""Active l'imprimante CUPS (cupsenable) et accepte les nouveaux jobs."""
try:
r1 = subprocess.run(["cupsenable", printer_name], capture_output=True, timeout=10)
r2 = subprocess.run(["cupsaccept", printer_name], capture_output=True, timeout=10)
return r1.returncode == 0 and r2.returncode == 0
except Exception as e:
logger.error("Erreur enable printer %s: %s", printer_name, e)
return False
async def disable_printer(self, printer_name: str) -> bool:
"""Désactive l'imprimante CUPS (cupsdisable)."""
try:
result = subprocess.run(["cupsdisable", printer_name], capture_output=True, timeout=10)
return result.returncode == 0
except Exception as e:
logger.error("Erreur disable printer %s: %s", printer_name, e)
return False
async def reject_jobs(self, printer_name: str) -> bool:
"""Refuse les nouveaux jobs (cupsreject) sans stopper l'impression en cours."""
try:
result = subprocess.run(["cupsreject", printer_name], capture_output=True, timeout=10)
return result.returncode == 0
except Exception as e:
logger.error("Erreur reject printer %s: %s", printer_name, e)
return False