From cd37c9088e75699faca3805ad9abee0b64948803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Luiz=20Bortot=20Monteiro=20do=20Ros=C3=A1rio?= <60971278+PedroBMR@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:11:32 -0300 Subject: [PATCH] Honor print job copies --- db/migrations.py | 8 +++++++- db/store.py | 8 +++++++- printing/queue.py | 15 +++++++++++++-- tests/db/test_print_jobs_queue.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/db/migrations.py b/db/migrations.py index c7a0447..1320e18 100644 --- a/db/migrations.py +++ b/db/migrations.py @@ -496,7 +496,13 @@ def resolve_template_id(data_value: str) -> int | None: ) -MIGRATIONS: list[Callable[[sqlite3.Connection], None]] = [_migration_1] +def _migration_2(conn: sqlite3.Connection) -> None: + add_column_if_missing( + conn, "print_jobs", "copies", "INTEGER NOT NULL DEFAULT 1" + ) + + +MIGRATIONS: list[Callable[[sqlite3.Connection], None]] = [_migration_1, _migration_2] SCHEMA_VERSION = len(MIGRATIONS) diff --git a/db/store.py b/db/store.py index 6251ec9..640f119 100644 --- a/db/store.py +++ b/db/store.py @@ -845,6 +845,7 @@ def insert_print_job( *, payload_hash: str, payload: bytes | memoryview, + copies: int | None = None, template_name: str | None = None, template_backend: str | None = None, printer_name: str | None = None, @@ -868,6 +869,7 @@ def insert_print_job( ) payload_bytes = bytes(payload) + copies_value = copies if copies and copies > 0 else 1 with contextlib.closing(get_conn(database)) as conn: with conn: @@ -876,6 +878,7 @@ def insert_print_job( INSERT INTO print_jobs( payload_hash, payload, + copies, template_name, template_backend, printer_name, @@ -887,11 +890,12 @@ def insert_print_job( status, pending_at ) - VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?) """, ( payload_hash, sqlite3.Binary(payload_bytes), + copies_value, template_name, template_backend, printer_name, @@ -1031,6 +1035,7 @@ def get_print_job( id, created_at, payload_hash, + copies, template_name, template_backend, printer_name, @@ -1085,6 +1090,7 @@ def list_print_jobs( id, created_at, payload_hash, + copies, template_name, template_backend, printer_name, diff --git a/printing/queue.py b/printing/queue.py index 70912e6..2bec87b 100644 --- a/printing/queue.py +++ b/printing/queue.py @@ -95,8 +95,9 @@ def _run(self) -> None: def _process(self, job_id: int) -> None: target = self.target logger = target.logger or logging.getLogger("app") + record = db_store.get_print_job(job_id=job_id) payload = db_store.get_job_payload(job_id=job_id) - if payload is None: + if record is None or payload is None: db_store.update_print_job_status( job_id=job_id, status="failed", @@ -105,13 +106,22 @@ def _process(self, job_id: int) -> None: logger.error("Job %s sem payload para impressora %s", job_id, target.name) return + copies_value = record.get("copies", 1) + try: + copies = int(copies_value) + except (TypeError, ValueError): + copies = 1 + if copies < 1: + copies = 1 + last_error: Optional[str] = None for attempt, delay in enumerate(_RETRY_DELAYS, start=1): if self._stop_event.is_set(): break db_store.update_print_job_status(job_id=job_id, status="running") try: - target.send(payload) + for _ in range(copies): + target.send(payload) except Exception as exc: # noqa: BLE001 last_error = str(exc) db_store.update_print_job_status( @@ -213,6 +223,7 @@ def enqueue_job( job_id = db_store.insert_print_job( payload_hash=payload_hash, payload=payload_bytes, + copies=max(1, int(copies)), template_name=template_id, template_backend=params.get("template_backend"), printer_name=printer.name, diff --git a/tests/db/test_print_jobs_queue.py b/tests/db/test_print_jobs_queue.py index 323f95f..14958ee 100644 --- a/tests/db/test_print_jobs_queue.py +++ b/tests/db/test_print_jobs_queue.py @@ -113,6 +113,37 @@ def send(data: bytes) -> None: assert payloads == [b"payload-data"] +def test_print_job_multiple_copies(): + payloads: List[bytes] = [] + processed = threading.Event() + + def send(data: bytes) -> None: + payloads.append(data) + if len(payloads) >= 3: + processed.set() + + printer = print_queue.PrinterTarget( + name="Printer Multi", backend="mock", transport="usb", send=send + ) + + job_id, status = print_queue.enqueue_job( + template_id=None, + payload=b"copy-payload", + printer=printer, + copies=3, + ) + + assert status == "pending" + assert print_queue.wait_for_all(timeout=2.0) + assert processed.wait(timeout=1.0) + + record = db_store.get_print_job(job_id=job_id) + assert record is not None + assert record["status"] == "done" + assert record.get("copies") == 3 + assert payloads == [b"copy-payload"] * 3 + + def test_print_job_retries_and_failure(monkeypatch: pytest.MonkeyPatch): status_transitions: Dict[int, List[str]] = {} real_update = db_store.update_print_job_status