Skip to main content

acp_cli/queue/
ipc.rs

1use std::path::PathBuf;
2
3use serde::Serialize;
4use serde::de::DeserializeOwned;
5use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
6use tokio::net::{UnixListener, UnixStream};
7
8use crate::session::scoping::session_dir;
9
10/// Return the Unix socket path for a given session key.
11/// Uses first 12 hex chars of the key to stay within macOS SUN_LEN limit (~104 bytes).
12pub fn socket_path(session_key: &str) -> PathBuf {
13    let short_key = &session_key[..session_key.len().min(12)];
14    session_dir().join(format!("{short_key}.sock"))
15}
16
17/// Start an IPC server on a Unix socket.
18///
19/// Removes any stale socket file before binding. The caller is responsible for
20/// accepting connections on the returned listener.
21pub async fn start_ipc_server(session_key: &str) -> std::io::Result<UnixListener> {
22    let path = socket_path(session_key);
23    // Ensure the parent directory exists.
24    if let Some(parent) = path.parent() {
25        std::fs::create_dir_all(parent)?;
26    }
27    // Remove stale socket file if it exists.
28    let _ = std::fs::remove_file(&path);
29    UnixListener::bind(&path)
30}
31
32/// Connect to an existing IPC server for the given session.
33pub async fn connect_ipc(session_key: &str) -> std::io::Result<UnixStream> {
34    let path = socket_path(session_key);
35    UnixStream::connect(&path).await
36}
37
38/// Send a message over a Unix stream as a single JSON line.
39pub async fn send_message<T: Serialize>(stream: &mut UnixStream, msg: &T) -> std::io::Result<()> {
40    let json = serde_json::to_string(msg)
41        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
42    stream.write_all(json.as_bytes()).await?;
43    stream.write_all(b"\n").await?;
44    stream.flush().await
45}
46
47/// Read one message from a Unix stream.
48///
49/// Returns `Ok(None)` when the stream is closed (EOF).
50pub async fn recv_message<T: DeserializeOwned>(
51    reader: &mut BufReader<UnixStream>,
52) -> std::io::Result<Option<T>> {
53    let mut line = String::new();
54    let n = reader.read_line(&mut line).await?;
55    if n == 0 {
56        return Ok(None);
57    }
58    let msg = serde_json::from_str(&line)
59        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
60    Ok(Some(msg))
61}
62
63/// Remove the socket file for a session (best-effort cleanup).
64pub fn cleanup_socket(session_key: &str) {
65    let _ = std::fs::remove_file(socket_path(session_key));
66}