use anyhow::{anyhow, bail, Result}; use log::debug; use std::{ io::{copy, BufRead, BufReader, BufWriter, Read, Write}, net::{TcpStream, ToSocketAddrs}, }; use crate::Serial; pub struct Client { wsock: BufWriter, rsock: BufReader, } impl Client { pub fn new(address: impl ToSocketAddrs, secret: &str) -> Result { let mut sock = TcpStream::connect(address)?; writeln!(sock, "{secret}")?; Ok(Self { rsock: BufReader::new(sock.try_clone()?), wsock: BufWriter::new(sock), }) } pub fn list(&mut self) -> Result> { writeln!(self.wsock, "list")?; self.wsock.flush()?; let mut line = String::new(); let mut out = Vec::new(); loop { line.clear(); self.rsock.read_line(&mut line)?; if line.trim().is_empty() { break Ok(out); } check_error(&line)?; let (mtime, rest) = line.trim().split_once(":").ok_or(anyhow!("size missing"))?; let (size, serial) = rest.split_once(":").ok_or(anyhow!("serial missing"))?; out.push((mtime.parse()?, size.parse()?, serial.parse()?)); } } pub fn upload(&mut self, size: u64, mut reader: impl Read) -> Result<()> { debug!("announcing upload of {size} bytes"); writeln!(self.wsock, "upload,{size}")?; self.wsock.flush()?; let mut line = String::new(); self.rsock.read_line(&mut line)?; check_error(&line)?; if line.trim() != "ready" { bail!("invalid response, not ready"); } debug!("server ready"); copy(&mut reader, &mut self.wsock)?; line.clear(); self.rsock.read_line(&mut line)?; check_error(&line)?; if line.trim() != "done" { bail!("invalid response, not done"); } debug!("server done"); Ok(()) } pub fn download(&mut self, serial: Serial, mut writer: impl Write) -> Result<()> { debug!("requesting download for serial={serial}"); writeln!(self.wsock, "download,{serial}")?; self.wsock.flush()?; let mut line = String::new(); self.rsock.read_line(&mut line)?; check_error(&line)?; let Some(size) = line.trim().strip_prefix("ready,") else { bail!("server not ready") }; eprintln!("{size:?}"); let size = size.parse()?; debug!("server ready"); copy(&mut self.rsock.by_ref().take(size), &mut writer)?; line.clear(); self.rsock.read_line(&mut line)?; check_error(&line)?; if line.trim() != "done" { bail!("invalid response, not done"); } debug!("server done"); Ok(()) } pub fn quit(mut self) -> Result<()> { writeln!(self.wsock, "quit")?; self.wsock.flush()?; Ok(()) } } fn check_error(line: &str) -> Result<()> { if let Some(message) = line.trim().strip_prefix("error,") { bail!("server error: {message}") } Ok(()) }