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    #[cfg(unix)]
77    #[test]
78    fn daemon_client_live_pid_with_dead_socket_returns_none() {
79        let dir = std::env::temp_dir().join(format!("syncweb-route-socket-{}", uuid::Uuid::new_v4()));
80        std::fs::create_dir_all(&dir).unwrap();
81        let lock = PidLock::new(&dir);
82        assert!(lock.try_acquire().unwrap());
83        let result = daemon_client(&dir);
84        lock.release().unwrap();
85        assert!(result.is_ok());
86        assert!(result.unwrap().is_none());
87        let _ = std::fs::remove_dir_all(&dir);
88    }
89
90    #[cfg(unix)]
91    #[test]
92    fn daemon_client_status_ping_timeout_returns_none() {
93        let dir = std::env::temp_dir().join(format!("syncweb-route-timeout-{}", uuid::Uuid::new_v4()));
94        std::fs::create_dir_all(&dir).unwrap();
95        let lock = PidLock::new(&dir);
96        assert!(lock.try_acquire().unwrap());
97        let result = daemon_client(&dir);
98        lock.release().unwrap();
99        assert!(result.is_ok());
100        assert!(result.unwrap().is_none());
101        let _ = std::fs::remove_dir_all(&dir);
102    }
103}