1#![allow(dead_code)]
7
8use alloc::{collections::BTreeMap, string::String, vec::Vec};
9
10pub type PrintJobId = u64;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PrintJobStatus {
20 Queued,
22 Printing,
24 Completed,
26 Failed,
28 Cancelled,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34pub enum PaperSize {
35 #[default]
37 Letter,
38 A4,
40 Legal,
42}
43
44impl PaperSize {
45 pub fn width_pts(&self) -> u32 {
47 match self {
48 Self::Letter => 612,
49 Self::A4 => 595,
50 Self::Legal => 612,
51 }
52 }
53
54 pub fn height_pts(&self) -> u32 {
56 match self {
57 Self::Letter => 792,
58 Self::A4 => 842,
59 Self::Legal => 1008,
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct PrintJob {
67 pub id: PrintJobId,
69 pub document_name: String,
71 pub data: Vec<u8>,
73 pub status: PrintJobStatus,
75 pub copies: u32,
77 pub pages: u32,
79 pub page_start: u32,
81 pub page_end: u32,
83}
84
85impl PrintJob {
86 pub fn new(id: PrintJobId, document_name: &str, data: Vec<u8>) -> Self {
88 Self {
89 id,
90 document_name: String::from(document_name),
91 data,
92 status: PrintJobStatus::Queued,
93 copies: 1,
94 pages: 0,
95 page_start: 0,
96 page_end: 0,
97 }
98 }
99
100 pub fn data_size(&self) -> usize {
102 self.data.len()
103 }
104}
105
106#[derive(Debug)]
112pub struct PrintQueue {
113 jobs: Vec<PrintJob>,
115 max_jobs: usize,
117 total_submitted: u64,
119 total_completed: u64,
121}
122
123impl PrintQueue {
124 pub fn new(max_jobs: usize) -> Self {
126 Self {
127 jobs: Vec::new(),
128 max_jobs,
129 total_submitted: 0,
130 total_completed: 0,
131 }
132 }
133
134 pub fn enqueue(&mut self, job: PrintJob) -> bool {
136 if self.jobs.len() >= self.max_jobs {
137 return false;
138 }
139 self.jobs.push(job);
140 self.total_submitted += 1;
141 true
142 }
143
144 pub fn dequeue(&mut self) -> Option<PrintJob> {
146 if self.jobs.is_empty() {
147 None
148 } else {
149 let mut job = self.jobs.remove(0);
150 job.status = PrintJobStatus::Printing;
151 Some(job)
152 }
153 }
154
155 pub fn cancel(&mut self, job_id: PrintJobId) -> bool {
157 for job in &mut self.jobs {
158 if job.id == job_id && job.status == PrintJobStatus::Queued {
159 job.status = PrintJobStatus::Cancelled;
160 return true;
161 }
162 }
163 false
164 }
165
166 pub fn get_status(&self, job_id: PrintJobId) -> Option<PrintJobStatus> {
168 self.jobs.iter().find(|j| j.id == job_id).map(|j| j.status)
169 }
170
171 pub fn pending_count(&self) -> usize {
173 self.jobs
174 .iter()
175 .filter(|j| j.status == PrintJobStatus::Queued)
176 .count()
177 }
178
179 pub fn total_count(&self) -> usize {
181 self.jobs.len()
182 }
183
184 pub fn complete_job(&mut self, job_id: PrintJobId) -> bool {
186 for job in &mut self.jobs {
187 if job.id == job_id {
188 job.status = PrintJobStatus::Completed;
189 self.total_completed += 1;
190 return true;
191 }
192 }
193 false
194 }
195
196 pub fn purge_finished(&mut self) {
198 self.jobs.retain(|j| {
199 j.status != PrintJobStatus::Completed && j.status != PrintJobStatus::Cancelled
200 });
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
210pub enum PrinterDriver {
211 #[default]
213 Virtual,
214 Ipp,
216 Raw,
218}
219
220#[derive(Debug, Clone)]
222pub struct PrinterConfig {
223 pub name: String,
225 pub driver_type: PrinterDriver,
227 pub resolution_dpi: u32,
229 pub paper_size: PaperSize,
231 pub enabled: bool,
233 pub is_default: bool,
235}
236
237impl PrinterConfig {
238 pub fn new(name: &str, driver_type: PrinterDriver) -> Self {
240 Self {
241 name: String::from(name),
242 driver_type,
243 resolution_dpi: 300,
244 paper_size: PaperSize::Letter,
245 enabled: true,
246 is_default: false,
247 }
248 }
249}
250
251#[derive(Debug)]
257pub struct PrintSpooler {
258 queues: BTreeMap<String, PrintQueue>,
260 printers: BTreeMap<String, PrinterConfig>,
262 next_job_id: PrintJobId,
264 default_printer: Option<String>,
266}
267
268impl Default for PrintSpooler {
269 fn default() -> Self {
270 Self::new()
271 }
272}
273
274impl PrintSpooler {
275 pub fn new() -> Self {
277 Self {
278 queues: BTreeMap::new(),
279 printers: BTreeMap::new(),
280 next_job_id: 1,
281 default_printer: None,
282 }
283 }
284
285 pub fn add_printer(&mut self, config: PrinterConfig) {
287 let name = config.name.clone();
288 let is_default = config.is_default;
289 self.printers.insert(name.clone(), config);
290 self.queues.insert(name.clone(), PrintQueue::new(100));
291 if is_default || self.default_printer.is_none() {
292 self.default_printer = Some(name);
293 }
294 }
295
296 pub fn remove_printer(&mut self, name: &str) -> bool {
298 let removed = self.printers.remove(name).is_some();
299 self.queues.remove(name);
300 if self.default_printer.as_deref() == Some(name) {
301 self.default_printer = self.printers.keys().next().cloned();
302 }
303 removed
304 }
305
306 pub fn submit_job(
308 &mut self,
309 printer: Option<&str>,
310 document_name: &str,
311 data: Vec<u8>,
312 ) -> Option<PrintJobId> {
313 let printer_name = printer
314 .map(String::from)
315 .or_else(|| self.default_printer.clone())?;
316
317 let queue = self.queues.get_mut(&printer_name)?;
318
319 let id = self.next_job_id;
320 self.next_job_id += 1;
321 let job = PrintJob::new(id, document_name, data);
322
323 if queue.enqueue(job) {
324 Some(id)
325 } else {
326 None
327 }
328 }
329
330 pub fn list_jobs(&self, printer: &str) -> Vec<&PrintJob> {
332 self.queues
333 .get(printer)
334 .map(|q| q.jobs.iter().collect())
335 .unwrap_or_default()
336 }
337
338 pub fn cancel_job(&mut self, printer: &str, job_id: PrintJobId) -> bool {
340 self.queues
341 .get_mut(printer)
342 .map(|q| q.cancel(job_id))
343 .unwrap_or(false)
344 }
345
346 pub fn printer_count(&self) -> usize {
348 self.printers.len()
349 }
350
351 pub fn default_printer(&self) -> Option<&str> {
353 self.default_printer.as_deref()
354 }
355
356 pub fn get_printer(&self, name: &str) -> Option<&PrinterConfig> {
358 self.printers.get(name)
359 }
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368#[repr(u16)]
369pub enum IppOperation {
370 PrintJob = 0x0002,
372 ValidateJob = 0x0004,
374 CreateJob = 0x0005,
376 GetJobAttributes = 0x0009,
378 GetPrinterAttributes = 0x000B,
380 CancelJob = 0x0008,
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386#[repr(u16)]
387pub enum IppStatus {
388 Ok = 0x0000,
390 ClientBadRequest = 0x0400,
392 ClientNotFound = 0x0406,
394 ServerInternal = 0x0500,
396}
397
398#[derive(Debug, Clone)]
400pub struct IppRequest {
401 pub version: (u8, u8),
403 pub operation: IppOperation,
405 pub request_id: u32,
407 pub attributes: BTreeMap<String, String>,
409}
410
411impl IppRequest {
412 pub fn new(operation: IppOperation, request_id: u32) -> Self {
414 Self {
415 version: (1, 1),
416 operation,
417 request_id,
418 attributes: BTreeMap::new(),
419 }
420 }
421}
422
423#[derive(Debug, Clone)]
425pub struct IppResponse {
426 pub version: (u8, u8),
428 pub status: IppStatus,
430 pub request_id: u32,
432 pub attributes: BTreeMap<String, String>,
434}
435
436impl IppResponse {
437 pub fn ok(request_id: u32) -> Self {
439 Self {
440 version: (1, 1),
441 status: IppStatus::Ok,
442 request_id,
443 attributes: BTreeMap::new(),
444 }
445 }
446
447 pub fn error(request_id: u32, status: IppStatus) -> Self {
449 Self {
450 version: (1, 1),
451 status,
452 request_id,
453 attributes: BTreeMap::new(),
454 }
455 }
456}
457
458#[cfg(test)]
463mod tests {
464 #[allow(unused_imports)]
465 use alloc::vec;
466
467 use super::*;
468
469 #[test]
470 fn test_print_job_new() {
471 let job = PrintJob::new(1, "test.pdf", vec![0u8; 100]);
472 assert_eq!(job.id, 1);
473 assert_eq!(job.status, PrintJobStatus::Queued);
474 assert_eq!(job.data_size(), 100);
475 }
476
477 #[test]
478 fn test_print_queue_enqueue_dequeue() {
479 let mut queue = PrintQueue::new(10);
480 let job = PrintJob::new(1, "test.pdf", vec![]);
481 assert!(queue.enqueue(job));
482 assert_eq!(queue.pending_count(), 1);
483 let dequeued = queue.dequeue().unwrap();
484 assert_eq!(dequeued.id, 1);
485 assert_eq!(dequeued.status, PrintJobStatus::Printing);
486 }
487
488 #[test]
489 fn test_print_queue_full() {
490 let mut queue = PrintQueue::new(1);
491 assert!(queue.enqueue(PrintJob::new(1, "a", vec![])));
492 assert!(!queue.enqueue(PrintJob::new(2, "b", vec![])));
493 }
494
495 #[test]
496 fn test_print_queue_cancel() {
497 let mut queue = PrintQueue::new(10);
498 queue.enqueue(PrintJob::new(1, "test", vec![]));
499 assert!(queue.cancel(1));
500 assert_eq!(queue.get_status(1), Some(PrintJobStatus::Cancelled));
501 }
502
503 #[test]
504 fn test_spooler_add_printer() {
505 let mut spooler = PrintSpooler::new();
506 let config = PrinterConfig::new("pdf-printer", PrinterDriver::Virtual);
507 spooler.add_printer(config);
508 assert_eq!(spooler.printer_count(), 1);
509 assert_eq!(spooler.default_printer(), Some("pdf-printer"));
510 }
511
512 #[test]
513 fn test_spooler_submit_job() {
514 let mut spooler = PrintSpooler::new();
515 spooler.add_printer(PrinterConfig::new("lp0", PrinterDriver::Virtual));
516 let id = spooler.submit_job(Some("lp0"), "doc.pdf", vec![1, 2, 3]);
517 assert!(id.is_some());
518 let jobs = spooler.list_jobs("lp0");
519 assert_eq!(jobs.len(), 1);
520 }
521
522 #[test]
523 fn test_spooler_default_printer() {
524 let mut spooler = PrintSpooler::new();
525 let id = spooler.submit_job(None, "doc", vec![]);
526 assert!(id.is_none()); spooler.add_printer(PrinterConfig::new("lp0", PrinterDriver::Virtual));
528 let id = spooler.submit_job(None, "doc", vec![]);
529 assert!(id.is_some());
530 }
531
532 #[test]
533 fn test_paper_size() {
534 assert_eq!(PaperSize::Letter.width_pts(), 612);
535 assert_eq!(PaperSize::A4.height_pts(), 842);
536 }
537}