Skip to main content

syncweb_core/daemon/
route.rs

1use std::path::Path;
2
3use crate::error::Result;
4
5use super::{DaemonStatus, IpcClient, PidLock};
6
7/// Return an IPC client only when a lock owner answers a status probe.
8///
9/// # Errors
10///
11/// Returns an error when the lock cannot be inspected.
12pub fn daemon_client(data_dir: &Path) -> Result<Option<IpcClient>> {
13    let lock = PidLock::new(data_dir);
14    if lock.try_acquire()? {
15        lock.release()?;
16        return Ok(None);
17    }
18    let client = IpcClient::new(data_dir);
19    match client.status_sync() {
20        Ok(DaemonStatus::Starting | DaemonStatus::Running) => Ok(Some(client)),
21        Ok(_) | Err(_) => Ok(None),
22    }
23}
24
25/// Alias for the one-shot command routing check.
26///
27/// # Errors
28///
29/// Returns an error when the lock cannot be inspected.
30pub fn try_daemon(data_dir: &Path) -> Result<Option<IpcClient>> {
31    daemon_client(data_dir)
32}
33
34/// Run a node operation with an already-running daemon client.
35///
36/// The operation is deliberately synchronous so callers can construct and
37/// pass a client without opening the node in the CLI process.
38///
39/// # Errors
40///
41/// Returns an error when no responsive daemon owns the data directory.
42pub async fn with_node<F, R>(data_dir: &Path, operation: F) -> Result<R>
43where
44    F: FnOnce(IpcClient) -> R,
45{
46    let client = daemon_client(data_dir)?
47        .ok_or_else(|| crate::error::SyncwebError::operation("daemon not running", "start with `syncweb start`"))?;
48    let result = operation(client);
49    Ok(result)
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn daemon_client_returns_none_when_no_lock() {
58        let dir = std::env::temp_dir().join(format!("syncweb-route-test-{}", uuid::Uuid::new_v4()));
59        std::fs::create_dir_all(&dir).unwrap();
60        let result = daemon_client(&dir);
61        assert!(result.is_ok());
62        assert!(result.unwrap().is_none());
63        let _ = std::fs::remove_dir_all(&dir);
64    }
65
66    #[test]
67    fn try_daemon_returns_none_when_no_lock() {
68        let dir = std::env::temp_dir().join(format!("syncweb-route-test-{}", uuid::Uuid::new_v4()));
69        std::fs::create_dir_all(&dir).unwrap();
70        let result = try_daemon(&dir);
71        assert!(result.is_ok());
72        assert!(result.unwrap().is_none());
73        let _ = std::fs::remove_dir_all(&dir);
74    }
75
76    #[test]
77    fn daemon_client_live_pid_with_dead_socket_returns_none() {
78        let dir = std::env::temp_dir().join(format!("syncweb-route-socket-{}", uuid::Uuid::new_v4()));
79        std::fs::create_dir_all(&dir).unwrap();
80        let lock = PidLock::new(&dir);
81        assert!(lock.try_acquire().unwrap());
82        let result = daemon_client(&dir);
83        lock.release().unwrap();
84        assert!(result.is_ok());
85        assert!(result.unwrap().is_none());
86        let _ = std::fs::remove_dir_all(&dir);
87    }
88
89    #[test]
90    fn daemon_client_status_ping_timeout_returns_none() {
91        let dir = std::env::temp_dir().join(format!("syncweb-route-timeout-{}", uuid::Uuid::new_v4()));
92        std::fs::create_dir_all(&dir).unwrap();
93        let lock = PidLock::new(&dir);
94        assert!(lock.try_acquire().unwrap());
95        let result = daemon_client(&dir);
96        lock.release().unwrap();
97        assert!(result.is_ok());
98        assert!(result.unwrap().is_none());
99        let _ = std::fs::remove_dir_all(&dir);
100    }
101}