testsAndMisc/python_pkg/brother_printer/_page_delivery.py
Krzysztof kuhy Rudnicki a438652aaa Split brother_printer dropped-page detection out of consumables
consumables.py 279 -> 217. check_page_delivery and _snapshot_counters
move to _page_delivery.py.

The state-file primitives stay put: test_consumables.py patches
_load_consumable_state, _save_consumable_state and CONSUMABLE_STATE_FILE
on consumables, and moving them would have let a test write to the real
~/.config/brother_printer/state.json. Verified by mtime after the run,
not just by a green suite.

test_consumables_part2.py patches the same three primitives from both
sides of the seam, so it gains a second MOD_PD applied per class rather
than a blanket swap. Pass count is 402 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 17:07:21 +02:00

77 lines
2.7 KiB
Python

"""Dropped-page detection: CUPS's page log versus the printer's own counter.
Split out of :mod:`python_pkg.brother_printer.consumables` to keep it under
the 250-line cap. The state-file I/O stays in ``consumables`` because
``tests/test_consumables.py`` patches it there; it is imported here so
``tests/test_consumables_part2.py`` can patch it on this module for the two
functions that moved.
"""
from __future__ import annotations
from python_pkg.brother_printer.constants import PAGE_DROP_WARN_THRESHOLD
from python_pkg.brother_printer.consumables import (
_get_cups_total_pages,
_load_consumable_state,
_save_consumable_state,
)
from python_pkg.brother_printer.data_classes import PageDeliveryCheck
def check_page_delivery(printer_total: int, *, queue_idle: bool) -> PageDeliveryCheck:
"""Compare pages CUPS logged against pages the printer actually counted.
Only meaningful between jobs: mid-job, CUPS has logged pages the printer has
not yet pulled off the wire, which would look identical to dropping them.
Records a fresh snapshot of both counters whenever it runs cleanly.
Args:
printer_total: Lifetime count from the printer's own counter.
queue_idle: False when a job is queued or printing, which makes any
comparison meaningless.
Returns:
The comparison. suspected is True only when CUPS claims materially more
pages than the printer recorded.
"""
check = PageDeliveryCheck()
if printer_total <= 0 or not queue_idle:
return check
state = _load_consumable_state()
cups_total = _get_cups_total_pages()
last_printer = state["last_printer_count"]
last_cups = state["last_cups_total"]
_snapshot_counters(state, printer_total, cups_total)
if last_printer <= 0 or last_cups <= 0:
# No baseline yet: this run establishes one.
return check
printer_delta = printer_total - last_printer
cups_delta = cups_total - last_cups
if printer_delta < 0 or cups_delta < 0:
# Counter reset or the page log rotated; nothing to conclude.
return check
check.cups_pages = cups_delta
check.printer_pages = printer_delta
check.dropped = cups_delta - printer_delta
check.suspected = check.dropped >= PAGE_DROP_WARN_THRESHOLD
return check
def _snapshot_counters(
state: dict[str, int],
printer_total: int,
cups_total: int,
) -> None:
"""Persist where both counters stood, for the next run to compare against."""
if (
state["last_printer_count"] == printer_total
and state["last_cups_total"] == cups_total
):
return
updated = dict(state)
updated["last_printer_count"] = printer_total
updated["last_cups_total"] = cups_total
_save_consumable_state(updated)