all
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user