taiga_plugin_api/daemon/
ipc.rs

1//! IPC message utilities for daemon-based plugins
2//!
3//! Provides generic serialization patterns and daemon process management.
4
5use interprocess::local_socket::tokio::Stream;
6use serde::{Deserialize, Serialize};
7use std::process::{Child, Command, Stdio};
8use crate::PluginError;
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10
11/// Send a serialized message over an IPC stream
12pub async fn send_message<T: Serialize>(
13    stream: &mut Stream,
14    msg: &T,
15) -> Result<(), PluginError> {
16    let bytes = serde_json::to_vec(msg)?;
17    stream.write_all(&bytes).await?;
18    Ok(())
19}
20
21/// Receive and deserialize a message from an IPC stream
22///
23/// # Arguments
24/// * `stream` - The IPC stream to read from
25/// * `buffer_size` - Size of the buffer to allocate for reading
26pub async fn receive_message<T: for<'de> Deserialize<'de>>(
27    stream: &mut Stream,
28    buffer_size: usize,
29) -> Result<T, PluginError> {
30    let mut buffer = vec![0u8; buffer_size];
31    let n = stream.read(&mut buffer).await?;
32
33    if n == 0 {
34        return Err(PluginError::ipc_connection(
35            "Connection closed without response",
36        ));
37    }
38
39    let msg: T = serde_json::from_slice(&buffer[0..n])?;
40    Ok(msg)
41}
42
43/// Configuration for spawning a daemon process
44#[derive(Debug, Clone)]
45pub struct DaemonSpawnConfig {
46    /// Plugin name (used as first argument)
47    pub plugin_name: String,
48    /// Daemon command (used as second argument)
49    pub daemon_command: String,
50    /// Additional arguments to pass to the daemon
51    pub additional_args: Vec<String>,
52}
53
54impl DaemonSpawnConfig {
55    /// Create a new daemon spawn configuration
56    pub fn new(plugin_name: impl Into<String>, daemon_command: impl Into<String>) -> Self {
57        Self {
58            plugin_name: plugin_name.into(),
59            daemon_command: daemon_command.into(),
60            additional_args: Vec::new(),
61        }
62    }
63
64    /// Add additional arguments
65    pub fn with_args(mut self, args: Vec<String>) -> Self {
66        self.additional_args = args;
67        self
68    }
69}
70
71/// Spawn the daemon as a detached background process
72///
73/// # Arguments
74/// * `config` - Configuration for the daemon process
75///
76/// Returns the spawned child process handle, or an error if spawning fails.
77pub fn spawn_daemon_process(config: &DaemonSpawnConfig) -> Result<Child, std::io::Error> {
78    let current_exe = std::env::current_exe()?;
79
80    let mut args = vec![config.plugin_name.clone(), config.daemon_command.clone()];
81    args.extend(config.additional_args.clone());
82
83    Command::new(&current_exe)
84        .args(&args)
85        .stdin(Stdio::null())
86        .stdout(Stdio::null())
87        .stderr(Stdio::null())
88        .spawn()
89        .map_err(|e| {
90            std::io::Error::new(
91                e.kind(),
92                format!(
93                    "Failed to spawn daemon process '{}': {}",
94                    current_exe.display(),
95                    e
96                ),
97            )
98        })
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn test_daemon_spawn_config_creation() {
107        let config = DaemonSpawnConfig::new("test", "daemon");
108        assert_eq!(config.plugin_name, "test");
109        assert_eq!(config.daemon_command, "daemon");
110        assert!(config.additional_args.is_empty());
111    }
112
113    #[test]
114    fn test_daemon_spawn_config_with_args() {
115        let config = DaemonSpawnConfig::new("test", "daemon")
116            .with_args(vec!["--verbose".to_string()]);
117        assert_eq!(config.additional_args.len(), 1);
118    }
119}