Skip to content
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(())
    }
}