use tracing::{debug, info};
use crate::{
commands::RasterCommands,
connection::printer_connection::sealed::ConnectionImpl,
error::{PrintError, PrintErrorSource, StatusError},
printjob::PrintJob,
status::{Phase, StatusInformation, StatusType},
};
pub(super) mod sealed {
use std::time::Duration;
use strum::IntoEnumIterator;
use tracing::debug;
use crate::{
commands::RasterCommand,
error::{ConnectionError, ProtocolError, StatusError},
media::Media,
status::{Phase, StatusInformation, StatusType},
};
pub trait ConnectionImpl {
type Error: std::error::Error + Send + Sync + 'static + ConnectionError;
fn write(&mut self, data: &[u8]) -> Result<(), Self::Error>;
fn read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>;
fn read_status_reply(&mut self) -> Result<StatusInformation, StatusError<Self::Error>> {
let mut read_buffer = [0u8; 32];
self.read_exact(&mut read_buffer)?;
let status =
StatusInformation::try_from(&read_buffer[..]).map_err(StatusError::Parsing)?;
debug!(?status, "Printer sent status information");
Ok(status)
}
fn read_exact(&mut self, buffer: &mut [u8]) -> Result<(), StatusError<Self::Error>> {
const MAX_RETRIES: u8 = 60;
const RETRY_DELAY: Duration = Duration::from_millis(50);
let mut total_read = 0;
let mut retries = 0;
while total_read < buffer.len() {
match self.read(&mut buffer[total_read..]) {
Ok(0) => {
retries += 1;
if retries > MAX_RETRIES {
return Err(StatusError::NoResponse);
}
std::thread::sleep(RETRY_DELAY);
}
Ok(n) => {
total_read += n;
retries = 0; }
Err(e) => return Err(e.into()),
}
}
Ok(())
}
fn send_status_request(&mut self) -> Result<(), Self::Error> {
debug!("Sending status information request to the printer...");
let status_request_bytes: Vec<u8> = RasterCommand::StatusInformationRequest.into();
self.write(&status_request_bytes)?;
Ok(())
}
fn validate_status(
status: &StatusInformation,
job_media: Media,
expected_type: StatusType,
expected_phase: Phase,
) -> Result<(), ProtocolError> {
fn status_matches_media(status: &StatusInformation, media: Media) -> bool {
let media_type_matches = status.media_type == Some(media.label_type());
let media_width_matches = status.media_width == media.width_mm();
let media_length_matches = match media.length_mm() {
None => status.media_length == 0,
Some(length_mm) => status.media_length == length_mm,
};
media_type_matches && media_width_matches && media_length_matches
}
if !status_matches_media(status, job_media) {
let likely_match = Media::iter().find(|&m| status_matches_media(status, m));
return Err(ProtocolError::MediaMismatch {
expected_media: job_media,
reported_media: likely_match,
});
}
if status.has_errors() {
return Err(ProtocolError::PrinterError(status.errors));
}
if status.status_type != expected_type || status.phase != expected_phase {
return Err(ProtocolError::UnexpectedStatus {
expected_type,
expected_phase,
actual_type: status.status_type,
actual_phase: status.phase,
});
}
Ok(())
}
}
}
pub trait PrinterConnection: ConnectionImpl {
fn print(&mut self, job: PrintJob) -> Result<(), PrintError<Self::Error>> {
info!("Starting print job...");
debug!("{job:#?}");
let no_pages = job.page_count();
let expected_media = job.media;
let parts = job.into_parts();
self.write(&parts.preamble.build())
.map_err(PrintError::err_source_mapper(0))?;
let status = self
.get_status()
.map_err(PrintError::err_source_mapper(0))?;
Self::validate_status(
&status,
expected_media,
StatusType::StatusRequestReply,
Phase::Receiving,
)
.map_err(|e| PrintError::with_page(e, 0))?;
for (page_no, page) in parts.page_data.into_iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
let current_page = (page_no + 1) as u32;
let page_res: Result<(), PrintErrorSource<Self::Error>> = (|| {
self.write(&page.build())?;
let status = self.read_status_reply()?;
Self::validate_status(
&status,
expected_media,
StatusType::PhaseChange,
Phase::Printing,
)?;
let status = self.read_status_reply()?;
Self::validate_status(
&status,
expected_media,
StatusType::PrintingCompleted,
Phase::Printing,
)?;
let status = self.read_status_reply()?;
Self::validate_status(
&status,
expected_media,
StatusType::PhaseChange,
Phase::Receiving,
)?;
Ok(())
})();
page_res.map_err(PrintError::err_source_mapper(current_page))?;
debug!(
"Sending print data for page {}/{}...",
current_page, no_pages
);
info!("Page {}/{} printed successfully!", current_page, no_pages);
}
info!("Print job completed successfully!");
Ok(())
}
fn get_status(&mut self) -> Result<StatusInformation, StatusError<Self::Error>> {
let preamble_bytes = RasterCommands::create_preamble().build();
self.write(&preamble_bytes)?;
self.send_status_request()?;
self.read_status_reply()
}
}