taiga_plugin_api/daemon/
socket.rs

1//! Cross-platform socket utilities for daemon-based plugins
2//!
3//! Consolidates platform-specific socket handling for both Unix and Windows.
4
5use interprocess::local_socket::traits::tokio::Stream as _;
6use interprocess::local_socket::{
7    GenericFilePath, GenericNamespaced, ListenerOptions, ToFsName, ToNsName,
8    tokio::Listener, tokio::Stream,
9};
10use crate::PluginError;
11
12/// Create a local socket listener at the given path
13///
14/// On Unix, this creates a filesystem socket.
15/// On Windows, this creates a named pipe.
16pub fn create_listener(path: &str) -> Result<Listener, PluginError> {
17    let listener = if cfg!(windows) {
18        let name = path
19            .to_ns_name::<GenericNamespaced>()
20            .map_err(|e| PluginError::ipc_connection(format!("Invalid socket name: {}", e)))?;
21        ListenerOptions::new()
22            .name(name)
23            .create_tokio()
24            .map_err(|e| PluginError::ipc_connection(format!("Failed to create listener: {}", e)))?
25    } else {
26        let name = path
27            .to_fs_name::<GenericFilePath>()
28            .map_err(|e| PluginError::ipc_connection(format!("Invalid socket name: {}", e)))?;
29        ListenerOptions::new()
30            .name(name)
31            .create_tokio()
32            .map_err(|e| PluginError::ipc_connection(format!("Failed to create listener: {}", e)))?
33    };
34    Ok(listener)
35}
36
37/// Connect to a local socket at the given path
38///
39/// On Unix, this connects to a filesystem socket.
40/// On Windows, this connects to a named pipe.
41pub async fn connect(path: &str) -> Result<Stream, PluginError> {
42    let stream = if cfg!(windows) {
43        let name = path
44            .to_ns_name::<GenericNamespaced>()
45            .map_err(|e| PluginError::ipc_connection(format!("Invalid socket name: {}", e)))?;
46        Stream::connect(name)
47            .await
48            .map_err(|e| PluginError::ipc_connection(format!("Connection failed: {}", e)))?
49    } else {
50        let name = path
51            .to_fs_name::<GenericFilePath>()
52            .map_err(|e| PluginError::ipc_connection(format!("Invalid socket name: {}", e)))?;
53        Stream::connect(name)
54            .await
55            .map_err(|e| PluginError::ipc_connection(format!("Connection failed: {}", e)))?
56    };
57    Ok(stream)
58}
59
60/// Clean up a socket file on Unix systems
61///
62/// On Windows, named pipes don't need explicit cleanup.
63pub fn cleanup_socket(path: &str) {
64    if !cfg!(windows) && std::fs::metadata(path).is_ok() {
65        std::fs::remove_file(path).ok();
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_cleanup_nonexistent_socket() {
75        // Should not panic when socket doesn't exist
76        cleanup_socket("/nonexistent/socket/path");
77    }
78}