use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::time::Duration;
use crate::protocol::{Request, Response, socket_path};
pub struct Client;
impl Client {
/// Send a request to the service, auto-starting it if not running.
pub fn send(request: &Request) -> Result<Response, String> {
let sock = socket_path();
let stream = Self::connect(&sock).or_else(|_| {
let _ = Self::start_service();
std::thread::sleep(Duration::from_millis(500));
Self::connect(&sock)
});
let stream = if let Ok(s) = stream {
s
} else {
// Retry once more after a longer wait
std::thread::sleep(Duration::from_millis(500));
Self::connect(&sock).map_err(|e| format!("Failed to connect to service: {e}"))?
};
Self::exchange(stream, request)
}
fn connect(path: &Path) -> Result<UnixStream, std::io::Error> {
let stream = UnixStream::connect(path)?;
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
stream.set_write_timeout(Some(Duration::from_secs(5)))?;
Ok(stream)
}
fn exchange(mut stream: UnixStream, request: &Request) -> Result<Response, String> {
let mut payload =
serde_json::to_string(request).map_err(|e| format!("Serialize error: {e}"))?;
payload.push('\n');
stream
.write_all(payload.as_bytes())
.map_err(|e| format!("Send error: {e}"))?;
let mut reader = BufReader::new(stream);
let mut line = String::new();
reader
.read_line(&mut line)
.map_err(|e| format!("Read error: {e}"))?;
serde_json::from_str(&line).map_err(|e| format!("Parse error: {e}"))
}
fn start_service() -> Result<(), String> {
let exe =
std::env::current_exe().map_err(|e| format!("Failed to find own executable: {e}"))?;
std::process::Command::new(exe)
.args(["service", "start"])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|e| format!("Failed to spawn service: {e}"))?;
Ok(())
}
}