Skip to main content

pitchfork_cli/ipc/
mod.rs

1use crate::Result;
2use crate::daemon::{Daemon, RunOptions};
3use crate::daemon_id::DaemonId;
4use crate::env;
5use interprocess::local_socket::{GenericFilePath, Name, ToFsName};
6use miette::{Context, IntoDiagnostic};
7use std::path::PathBuf;
8
9pub(crate) mod batch;
10pub(crate) mod client;
11pub(crate) mod server;
12
13// #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, strum::Display, strum::EnumIs)]
14// pub enum IpcMessage {
15//     Connect(String),
16//     ConnectOK,
17//     Run(String, Vec<String>),
18//     Stop(String),
19//     DaemonAlreadyRunning(String),
20//     DaemonAlreadyStopped(String),
21//     DaemonStart(Daemon),
22//     DaemonStop { name: String },
23//     DaemonFailed { name: String, error: String },
24//     Response(String),
25// }
26
27#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, strum::Display, strum::EnumIs)]
28#[allow(clippy::large_enum_variant)]
29pub enum IpcRequest {
30    Connect,
31    Clean,
32    Stop {
33        id: DaemonId,
34    },
35    GetActiveDaemons,
36    GetDisabledDaemons,
37    Run(RunOptions),
38    Enable {
39        id: DaemonId,
40    },
41    Disable {
42        id: DaemonId,
43    },
44    UpdateShellDir {
45        shell_pid: u32,
46        dir: PathBuf,
47    },
48    GetNotifications,
49    /// Invalid request (failed to deserialize)
50    #[serde(skip)]
51    Invalid {
52        error: String,
53    },
54}
55
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, strum::Display, strum::EnumIs)]
57pub enum IpcResponse {
58    Ok,
59    Yes,
60    No,
61    Error(String),
62    Notifications(Vec<(log::LevelFilter, String)>),
63    ActiveDaemons(Vec<Daemon>),
64    DisabledDaemons(Vec<DaemonId>),
65    DaemonAlreadyRunning,
66    DaemonStart {
67        daemon: Daemon,
68    },
69    DaemonFailed {
70        error: String,
71    },
72    /// Port conflict detected with detailed process information
73    PortConflict {
74        port: u16,
75        process: String,
76        pid: u32,
77    },
78    /// No available ports found after exhausting auto-bump attempts
79    NoAvailablePort {
80        start_port: u16,
81        attempts: u32,
82    },
83    DaemonReady {
84        daemon: Daemon,
85    },
86    DaemonFailedWithCode {
87        exit_code: Option<i32>,
88    },
89    /// Process was not running but had a PID record (unexpected exit)
90    DaemonWasNotRunning,
91    /// Failed to kill the process (still running)
92    DaemonStopFailed {
93        error: String,
94    },
95    /// Daemon exists but is not running (no PID)
96    DaemonNotRunning,
97    DaemonNotFound,
98}
99fn fs_name(name: &str) -> Result<Name<'_>> {
100    let path = env::IPC_SOCK_DIR.join(name).with_extension("sock");
101    let fs_name = path.to_fs_name::<GenericFilePath>().into_diagnostic()?;
102    Ok(fs_name)
103}
104
105fn serialize<T: serde::Serialize>(msg: &T) -> Result<Vec<u8>> {
106    if *env::IPC_JSON {
107        serde_json::to_vec(msg)
108            .into_diagnostic()
109            .wrap_err("failed to serialize IPC message as JSON")
110    } else {
111        rmp_serde::to_vec(msg)
112            .into_diagnostic()
113            .wrap_err("failed to serialize IPC message as MessagePack")
114    }
115}
116
117fn deserialize<T: serde::de::DeserializeOwned>(bytes: &[u8]) -> Result<T> {
118    let mut bytes = bytes.to_vec();
119    bytes.pop();
120    let preview = std::str::from_utf8(&bytes).unwrap_or("<binary>");
121    trace!("msg: {preview:?}");
122    if *env::IPC_JSON {
123        serde_json::from_slice(&bytes)
124            .into_diagnostic()
125            .wrap_err("failed to deserialize IPC JSON response")
126    } else {
127        rmp_serde::from_slice(&bytes)
128            .into_diagnostic()
129            .wrap_err("failed to deserialize IPC MessagePack response")
130    }
131}