Skip to main content

syspulse_core/ipc/
client.rs

1use std::path::PathBuf;
2
3use tracing::debug;
4
5use crate::error::{Result, SyspulseError};
6use crate::ipc::protocol::{read_message, write_message, Request, Response};
7
8#[cfg(unix)]
9use interprocess::local_socket::GenericFilePath as NameType;
10#[cfg(windows)]
11use interprocess::local_socket::GenericNamespaced as NameType;
12
13use interprocess::local_socket::tokio::prelude::*;
14
15pub struct IpcClient {
16    socket_path: PathBuf,
17}
18
19impl IpcClient {
20    pub fn new(socket_path: PathBuf) -> Self {
21        Self { socket_path }
22    }
23
24    /// Send a request to the daemon manager and return the response.
25    /// Each call creates a fresh connection (simple request-response model).
26    pub async fn send(&self, request: Request) -> Result<Response> {
27        let name = self.socket_name()?;
28        let stream = LocalSocketStream::connect(name)
29            .await
30            .map_err(|e| SyspulseError::Ipc(format!("Failed to connect: {}", e)))?;
31
32        let (mut reader, mut writer) = tokio::io::split(stream);
33
34        write_message(&mut writer, &request).await?;
35        debug!("Sent IPC request: {:?}", request);
36
37        let response: Response = read_message(&mut reader).await?.ok_or_else(|| {
38            SyspulseError::Ipc("Server closed connection without response".into())
39        })?;
40
41        debug!("Received IPC response: {:?}", response);
42        Ok(response)
43    }
44
45    /// Check if the daemon manager is reachable by sending a Ping.
46    pub async fn is_manager_running(&self) -> bool {
47        match self.send(Request::Ping).await {
48            Ok(Response::Pong) => true,
49            _ => false,
50        }
51    }
52
53    fn socket_name(&self) -> Result<interprocess::local_socket::Name<'_>> {
54        #[cfg(unix)]
55        {
56            let path_str = self
57                .socket_path
58                .to_str()
59                .ok_or_else(|| SyspulseError::Ipc("Invalid socket path".into()))?;
60            path_str
61                .to_ns_name::<NameType>()
62                .map_err(|e| SyspulseError::Ipc(format!("Invalid socket name: {}", e)))
63        }
64        #[cfg(windows)]
65        {
66            let name_str = self
67                .socket_path
68                .file_name()
69                .and_then(|n| n.to_str())
70                .unwrap_or("syspulse");
71            name_str
72                .to_ns_name::<NameType>()
73                .map_err(|e| SyspulseError::Ipc(format!("Invalid pipe name: {}", e)))
74        }
75    }
76}