use std::io::{self, Write};
use std::net::{Shutdown, SocketAddr, TcpStream};
use std::time::Duration;
use socket2::{SockRef, TcpKeepalive};
use crate::addr::resolve_printer_addr;
use crate::frame::{DEFAULT_MAX_FRAME_SIZE, expected_frame_count, read_frames};
use crate::{PrintError, Printer, PrinterConfig, StatusQuery};
pub struct TcpPrinter {
stream: TcpStream,
config: PrinterConfig,
addr: SocketAddr,
}
impl TcpPrinter {
pub fn connect(addr: &str, config: PrinterConfig) -> Result<Self, PrintError> {
let socket_addr = resolve_printer_addr(addr)?;
let stream = Self::open_stream(&socket_addr, &config)?;
Ok(Self {
stream,
config,
addr: socket_addr,
})
}
pub fn reconnect(&mut self) -> Result<(), PrintError> {
let _ = self.stream.shutdown(Shutdown::Both);
self.stream = Self::open_stream(&self.addr, &self.config)?;
Ok(())
}
fn open_stream(addr: &SocketAddr, config: &PrinterConfig) -> Result<TcpStream, PrintError> {
let stream =
TcpStream::connect_timeout(addr, config.timeouts.connect).map_err(|e| {
match e.kind() {
io::ErrorKind::ConnectionRefused => PrintError::ConnectionRefused {
addr: addr.to_string(),
source: e,
},
io::ErrorKind::TimedOut => PrintError::ConnectionTimeout {
addr: addr.to_string(),
timeout: config.timeouts.connect,
source: e,
},
_ => PrintError::ConnectionFailed {
addr: addr.to_string(),
source: e,
},
}
})?;
configure_stream(&stream, addr, config)?;
Ok(stream)
}
pub fn remote_addr(&self) -> SocketAddr {
self.addr
}
pub fn wait_for_completion(
&mut self,
poll_interval: Duration,
timeout: Duration,
) -> Result<(), PrintError> {
crate::wait_for_completion(self, poll_interval, timeout)
}
}
impl Printer for TcpPrinter {
fn send_raw(&mut self, data: &[u8]) -> Result<(), PrintError> {
self.stream
.write_all(data)
.map_err(PrintError::WriteFailed)?;
self.stream.flush().map_err(PrintError::WriteFailed)?;
Ok(())
}
}
impl StatusQuery for TcpPrinter {
fn query_raw(&mut self, cmd: &[u8]) -> Result<Vec<Vec<u8>>, PrintError> {
self.stream
.write_all(cmd)
.map_err(PrintError::WriteFailed)?;
self.stream.flush().map_err(PrintError::WriteFailed)?;
let expected_frames = expected_frame_count(cmd);
read_frames(
&mut self.stream,
expected_frames,
self.config.timeouts.read,
DEFAULT_MAX_FRAME_SIZE,
)
}
}
impl Drop for TcpPrinter {
fn drop(&mut self) {
let _ = self.stream.shutdown(Shutdown::Both);
}
}
impl crate::Reconnectable for TcpPrinter {
fn reconnect(&mut self) -> Result<(), crate::PrintError> {
TcpPrinter::reconnect(self)
}
}
fn configure_stream(
stream: &TcpStream,
addr: &SocketAddr,
config: &PrinterConfig,
) -> Result<(), PrintError> {
stream
.set_nodelay(true)
.map_err(|e| PrintError::ConnectionFailed {
addr: addr.to_string(),
source: e,
})?;
configure_keepalive(stream, Duration::from_secs(60)).map_err(|e| {
PrintError::ConnectionFailed {
addr: addr.to_string(),
source: e,
}
})?;
stream
.set_write_timeout(Some(config.timeouts.write))
.map_err(|e| PrintError::ConnectionFailed {
addr: addr.to_string(),
source: e,
})?;
stream
.set_read_timeout(Some(config.timeouts.read))
.map_err(|e| PrintError::ConnectionFailed {
addr: addr.to_string(),
source: e,
})?;
Ok(())
}
fn configure_keepalive(stream: &TcpStream, interval: Duration) -> io::Result<()> {
let keepalive = TcpKeepalive::new().with_time(interval);
#[cfg(any(target_os = "linux", target_os = "macos"))]
let keepalive = keepalive.with_interval(interval);
SockRef::from(stream).set_tcp_keepalive(&keepalive)?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::frame::expected_frame_count;
#[test]
fn expected_frame_count_for_commands() {
assert_eq!(expected_frame_count(b"~HS"), 3);
assert_eq!(expected_frame_count(b"~HI"), 1);
assert_eq!(expected_frame_count(b"~HD"), 1);
}
}