Skip to main content

unifier/daemon/
client.rs

1//! Connect to a running hot daemon.
2
3use std::io::{BufRead, BufReader, Write};
4use std::os::unix::net::UnixStream;
5use std::path::Path;
6use std::time::Duration;
7
8use crate::daemon::paths::{pid_path, socket_path};
9use crate::daemon::protocol::{decode_response, Request, Response};
10use crate::error::{Error, Result};
11use crate::home::UnifierHome;
12use crate::postbox::Message;
13
14pub struct Client {
15    stream: UnixStream,
16}
17
18impl Client {
19    pub fn connect(home: &UnifierHome) -> Result<Self> {
20        let path = socket_path(home);
21        let stream = UnixStream::connect(&path)
22            .map_err(|e| Error::msg(format!("daemon not reachable at {}: {e}", path.display())))?;
23        stream.set_read_timeout(Some(Duration::from_secs(30)))?;
24        stream.set_write_timeout(Some(Duration::from_secs(30)))?;
25        Ok(Self { stream })
26    }
27
28    pub fn is_running(home: &UnifierHome) -> bool {
29        read_pid(home).ok().flatten().is_some_and(process_alive) && socket_path(home).exists()
30    }
31
32    pub fn request(&mut self, req: Request) -> Result<Response> {
33        let line = format!("{}\n", serde_json::to_string(&req)?);
34        self.stream.write_all(line.as_bytes())?;
35        self.stream.flush()?;
36
37        let mut reader = BufReader::new(&self.stream);
38        let mut buf = String::new();
39        reader.read_line(&mut buf)?;
40        decode_response(&buf).map_err(|e| Error::msg(format!("invalid daemon response: {e}")))
41    }
42}
43
44pub fn read_pid(home: &UnifierHome) -> Result<Option<u32>> {
45    let path = pid_path(home);
46    if !path.is_file() {
47        return Ok(None);
48    }
49    let text = std::fs::read_to_string(path)?.trim().to_string();
50    if text.is_empty() {
51        return Ok(None);
52    }
53    text.parse::<u32>()
54        .map(Some)
55        .map_err(|_| Error::msg("invalid pid file"))
56}
57
58pub fn process_alive(pid: u32) -> bool {
59    Path::new(&format!("/proc/{pid}")).exists()
60}
61
62pub fn response_messages(resp: Response) -> Result<Vec<Message>> {
63    match resp {
64        Response::Ok { messages, .. } => Ok(messages.into_iter().map(Message::from).collect()),
65        Response::Err { error } => Err(Error::msg(error)),
66    }
67}
68
69pub fn response_value(resp: Response) -> Result<Option<String>> {
70    match resp {
71        Response::Ok { value, .. } => Ok(value),
72        Response::Err { error } => Err(Error::msg(error)),
73    }
74}
75
76pub fn response_uuid(resp: Response) -> Result<uuid::Uuid> {
77    match resp {
78        Response::Ok { uuid: Some(id), .. } => Ok(id),
79        Response::Ok { .. } => Err(Error::msg("daemon response missing uuid")),
80        Response::Err { error } => Err(Error::msg(error)),
81    }
82}
83
84pub fn response_found(resp: Response) -> Result<bool> {
85    match resp {
86        Response::Ok { found: Some(v), .. } => Ok(v),
87        Response::Ok { .. } => Ok(true),
88        Response::Err { error } => Err(Error::msg(error)),
89    }
90}
91
92pub fn response_ok(resp: Response) -> Result<()> {
93    match resp {
94        Response::Ok { .. } => Ok(()),
95        Response::Err { error } => Err(Error::msg(error)),
96    }
97}
98
99pub fn response_dirty(resp: Response) -> Result<bool> {
100    match resp {
101        Response::Ok { dirty: Some(v), .. } => Ok(v),
102        Response::Ok { .. } => Ok(false),
103        Response::Err { error } => Err(Error::msg(error)),
104    }
105}
106
107pub fn ping(home: &UnifierHome) -> Result<()> {
108    let mut client = Client::connect(home)?;
109    response_ok(client.request(Request::Ping)?)
110}
111
112pub fn shutdown(home: &UnifierHome) -> Result<()> {
113    if !Client::is_running(home) {
114        return Err(Error::msg("daemon is not running"));
115    }
116    let mut client = Client::connect(home)?;
117    response_ok(client.request(Request::Shutdown)?)
118}
119
120pub fn flush(home: &UnifierHome) -> Result<bool> {
121    let mut client = Client::connect(home)?;
122    response_dirty(client.request(Request::Flush)?)
123}