Skip to main content

ipp_printer_app/
status.rs

1//! Background `printer-state-reasons` poller.
2//!
3//! A single tokio task per server walks the printer registry every poll
4//! interval (default 30 s, override with `IPP_PRINTER_APP_POLL_SECS`) and
5//! asks the backend for fresh status. Backends that don't override
6//! [`DeviceBackend::poll_status`] return `None` and leave the registry
7//! untouched.
8
9use std::sync::Arc;
10use std::time::Duration;
11
12use crate::device::DeviceBackend;
13use crate::printer::PrinterRegistry;
14
15/// Default cadence (configurable via `IPP_PRINTER_APP_POLL_SECS`).
16const POLL_INTERVAL: Duration = Duration::from_secs(30);
17
18/// Spawn the polling task. Returns immediately. Drop the returned
19/// [`tokio::task::JoinHandle`] to abort the loop on server shutdown.
20pub fn spawn(
21    backend: Arc<dyn DeviceBackend>,
22    registry: PrinterRegistry,
23) -> tokio::task::JoinHandle<()> {
24    let interval = std::env::var("IPP_PRINTER_APP_POLL_SECS")
25        .ok()
26        .and_then(|s| s.parse().ok())
27        .map(Duration::from_secs)
28        .unwrap_or(POLL_INTERVAL);
29
30    tokio::spawn(async move {
31        // First poll happens after `interval` so the server has time to
32        // bootstrap printers before the first status query lands on a device.
33        let mut ticker = tokio::time::interval(interval);
34        ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
35        ticker.tick().await; // immediate first tick
36        loop {
37            ticker.tick().await;
38            poll_once(backend.as_ref(), &registry).await;
39        }
40    })
41}
42
43async fn poll_once(backend: &dyn DeviceBackend, registry: &PrinterRegistry) {
44    use crate::printer::IppPrinterState;
45    // Snapshot the idle printers; skip ones with a job in flight so we don't
46    // contend with the device.
47    let configs: Vec<_> = {
48        let g = registry.read();
49        g.iter()
50            .filter(|r| r.state == IppPrinterState::Idle)
51            .map(|r| r.config.clone())
52            .collect()
53    };
54
55    for cfg in configs {
56        // `poll_status` may block on a HID read; let it run in the blocking
57        // section so other tasks (Get-Printer-Attributes, etc.) stay responsive.
58        let reasons = tokio::task::block_in_place(|| backend.poll_status(&cfg));
59        if let Some(reasons) = reasons {
60            let mut g = registry.write();
61            if let Some(rec) = g.iter_mut().find(|r| r.config.name == cfg.name) {
62                if rec.reasons != reasons {
63                    log::debug!(
64                        "status: {} reasons {:?} -> {:?}",
65                        cfg.name,
66                        rec.reasons,
67                        reasons
68                    );
69                    rec.reasons = reasons;
70                }
71            }
72        }
73    }
74}