Skip to main content

cpdb_rs/
events.rs

1//! Discovery events emitted by CPDB backends.
2//!
3//! These map directly to the D-Bus signals defined in the
4//! `org.openprinting.PrintBackend` interface.
5
6use crate::types::PrinterState;
7
8/// An event emitted during printer discovery or state monitoring.
9///
10/// Marked `#[non_exhaustive]` so a future backend event kind can be
11/// added without breaking downstream matches — always include a `_`
12/// arm.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14#[non_exhaustive]
15pub enum DiscoveryEvent {
16    /// A printer was discovered or re-announced.
17    PrinterAdded(PrinterSnapshot),
18    /// A printer was removed from the backend.
19    PrinterRemoved {
20        /// The printer's unique ID.
21        id: String,
22        /// The backend that reported the removal.
23        backend: String,
24    },
25    /// A printer's state or accepting-jobs status changed.
26    PrinterStateChanged {
27        /// The printer's unique ID.
28        id: String,
29        /// The backend that reported the change.
30        backend: String,
31        /// The new state.
32        state: PrinterState,
33        /// Whether the printer is accepting new jobs.
34        accepting_jobs: bool,
35    },
36}
37
38/// Snapshot of a printer's identity and status at a point in time.
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub struct PrinterSnapshot {
41    /// The backend-assigned unique printer ID.
42    pub id: String,
43    /// The human-readable display name.
44    pub name: String,
45    /// A free-form description of the printer.
46    pub info: String,
47    /// Physical location string as reported by the backend.
48    pub location: String,
49    /// Make and model string (e.g. `"HP LaserJet Pro"`).
50    pub make_model: String,
51    /// Current state (e.g. idle, processing, stopped).
52    pub state: PrinterState,
53    /// Whether the printer is currently accepting new jobs.
54    pub accepting_jobs: bool,
55    /// The backend that owns this printer (e.g. `"CUPS"`).
56    pub backend: String,
57}
58
59impl PrinterSnapshot {
60    /// Returns `true` when the printer is idle and accepting jobs.
61    pub fn is_ready(&self) -> bool {
62        self.state == PrinterState::Idle && self.accepting_jobs
63    }
64}
65
66impl std::fmt::Display for PrinterSnapshot {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        write!(f, "{} [{}] ({})", self.name, self.id, self.state)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    fn sample_snapshot() -> PrinterSnapshot {
77        PrinterSnapshot {
78            id: "HP-LaserJet-Pro".to_string(),
79            name: "HP LaserJet Pro".to_string(),
80            info: "Office printer".to_string(),
81            location: "Room 42".to_string(),
82            make_model: "HP LaserJet Pro MFP".to_string(),
83            state: PrinterState::Idle,
84            accepting_jobs: true,
85            backend: "CUPS".to_string(),
86        }
87    }
88
89    #[test]
90    fn snapshot_is_ready_when_idle_and_accepting() {
91        let snap = sample_snapshot();
92        assert!(snap.is_ready());
93    }
94
95    #[test]
96    fn snapshot_not_ready_when_busy() {
97        let mut snap = sample_snapshot();
98        snap.state = PrinterState::Processing;
99        assert!(!snap.is_ready());
100    }
101
102    #[test]
103    fn snapshot_not_ready_when_not_accepting() {
104        let mut snap = sample_snapshot();
105        snap.accepting_jobs = false;
106        assert!(!snap.is_ready());
107    }
108
109    #[test]
110    fn snapshot_clones_correctly() {
111        let snap = sample_snapshot();
112        let clone = snap.clone();
113        assert_eq!(snap.id, clone.id);
114        assert_eq!(snap.backend, clone.backend);
115    }
116
117    #[test]
118    fn discovery_event_printer_added() {
119        let event = DiscoveryEvent::PrinterAdded(sample_snapshot());
120        assert!(matches!(event, DiscoveryEvent::PrinterAdded(_)));
121    }
122
123    #[test]
124    fn discovery_event_printer_removed() {
125        let event = DiscoveryEvent::PrinterRemoved {
126            id: "HP-123".to_string(),
127            backend: "CUPS".to_string(),
128        };
129        match &event {
130            DiscoveryEvent::PrinterRemoved { id, backend } => {
131                assert_eq!(id, "HP-123");
132                assert_eq!(backend, "CUPS");
133            }
134            _ => panic!("Expected PrinterRemoved"),
135        }
136    }
137
138    #[test]
139    fn discovery_event_state_changed() {
140        let event = DiscoveryEvent::PrinterStateChanged {
141            id: "HP-123".to_string(),
142            backend: "CUPS".to_string(),
143            state: PrinterState::Processing,
144            accepting_jobs: true,
145        };
146        match &event {
147            DiscoveryEvent::PrinterStateChanged {
148                state,
149                accepting_jobs,
150                ..
151            } => {
152                assert_eq!(state, &PrinterState::Processing);
153                assert!(*accepting_jobs);
154            }
155            _ => panic!("Expected PrinterStateChanged"),
156        }
157    }
158
159    #[test]
160    fn events_are_clone() {
161        let event = DiscoveryEvent::PrinterAdded(sample_snapshot());
162        let clone = event.clone();
163        assert!(matches!(clone, DiscoveryEvent::PrinterAdded(_)));
164    }
165
166    #[test]
167    fn snapshot_display() {
168        let snap = sample_snapshot();
169        assert_eq!(
170            format!("{}", snap),
171            "HP LaserJet Pro [HP-LaserJet-Pro] (idle)"
172        );
173    }
174
175    #[test]
176    fn snapshot_equality_and_hashing() {
177        use std::collections::HashSet;
178        let snap1 = sample_snapshot();
179        let snap2 = sample_snapshot();
180        assert_eq!(snap1, snap2);
181
182        let mut set = HashSet::new();
183        set.insert(snap1.clone());
184        assert!(set.contains(&snap2));
185    }
186}