Skip to main content

irosh/client/
ipc.rs

1//! Client-side Inter-Process Communication (IPC) for daemon control.
2//!
3//! This module provides a client that can send commands to a running
4//! irosh background service via a local socket.
5
6use crate::error::Result;
7use crate::server::ipc::{IpcCommand, IpcResponse};
8use std::path::PathBuf;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10
11/// A client for communicating with a running irosh daemon.
12pub struct IpcClient {
13    socket_path: PathBuf,
14}
15
16impl IpcClient {
17    /// Creates a new IPC client targeting the daemon in the specified state directory.
18    pub fn new(_state_dir: PathBuf) -> Self {
19        #[cfg(unix)]
20        let socket_path = _state_dir.join("irosh.sock");
21        #[cfg(windows)]
22        let socket_path = {
23            use std::hash::{Hash, Hasher};
24            let mut hasher = std::collections::hash_map::DefaultHasher::new();
25            _state_dir.hash(&mut hasher);
26            let hash = hasher.finish();
27            PathBuf::from(format!(r"\\.\pipe\irosh-service-{:x}", hash))
28        };
29
30        Self { socket_path }
31    }
32
33    /// Sends a command to the daemon and waits for a response.
34    pub async fn send(&self, command: IpcCommand) -> Result<IpcResponse> {
35        let mut stream = self.connect().await?;
36
37        let buf = serde_json::to_vec(&command).map_err(|e| {
38            crate::error::IroshError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e))
39        })?;
40
41        stream.write_all(&buf).await?;
42        // Shutdown writing so the server knows the command is complete.
43        // For Unix sockets, we use shutdown(Write).
44        #[cfg(unix)]
45        stream.shutdown().await?;
46
47        let mut res_buf = Vec::new();
48        stream.read_to_end(&mut res_buf).await?;
49
50        let response: IpcResponse = serde_json::from_slice(&res_buf).map_err(|e| {
51            crate::error::IroshError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e))
52        })?;
53
54        Ok(response)
55    }
56
57    #[cfg(unix)]
58    async fn connect(&self) -> Result<tokio::net::UnixStream> {
59        tokio::net::UnixStream::connect(&self.socket_path)
60            .await
61            .map_err(crate::error::IroshError::Io)
62    }
63
64    #[cfg(windows)]
65    async fn connect(&self) -> Result<tokio::net::windows::named_pipe::NamedPipeClient> {
66        use tokio::net::windows::named_pipe::ClientOptions;
67
68        let client = ClientOptions::new()
69            .open(&*self.socket_path.to_string_lossy())
70            .map_err(crate::error::IroshError::Io)?;
71
72        Ok(client)
73    }
74}