tsk/server/
lifecycle.rs

1use crate::storage::XdgDirectories;
2use std::fs;
3use std::io::{Read, Write};
4use std::path::PathBuf;
5use std::process;
6use std::sync::Arc;
7
8/// Manages server lifecycle (PID files, etc.)
9pub struct ServerLifecycle {
10    xdg_directories: Arc<XdgDirectories>,
11}
12
13impl ServerLifecycle {
14    /// Create a new server lifecycle manager
15    pub fn new(xdg_directories: Arc<XdgDirectories>) -> Self {
16        Self { xdg_directories }
17    }
18
19    /// Check if a server is already running
20    pub fn is_server_running(&self) -> bool {
21        let pid_file = self.xdg_directories.pid_file();
22
23        if !pid_file.exists() {
24            return false;
25        }
26
27        // Read PID from file
28        match self.read_pid() {
29            Some(pid) => {
30                // Check if process is still alive
31                self.is_process_alive(pid)
32            }
33            None => false,
34        }
35    }
36
37    /// Write current process PID to file
38    pub fn write_pid(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
39        let pid_file = self.xdg_directories.pid_file();
40        let pid = process::id();
41
42        let mut file = fs::File::create(&pid_file)?;
43        file.write_all(pid.to_string().as_bytes())?;
44
45        Ok(())
46    }
47
48    /// Read PID from file
49    pub fn read_pid(&self) -> Option<u32> {
50        let pid_file = self.xdg_directories.pid_file();
51
52        let mut file = match fs::File::open(&pid_file) {
53            Ok(f) => f,
54            Err(_) => return None,
55        };
56
57        let mut contents = String::new();
58        if file.read_to_string(&mut contents).is_err() {
59            return None;
60        }
61
62        contents.trim().parse().ok()
63    }
64
65    /// Remove PID file
66    pub fn remove_pid(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
67        let pid_file = self.xdg_directories.pid_file();
68        if pid_file.exists() {
69            fs::remove_file(&pid_file)?;
70        }
71        Ok(())
72    }
73
74    /// Check if a process with given PID is alive
75    #[cfg(unix)]
76    fn is_process_alive(&self, pid: u32) -> bool {
77        unsafe {
78            // Send signal 0 to check if process exists
79            libc::kill(pid as i32, 0) == 0
80        }
81    }
82
83    #[cfg(not(unix))]
84    fn is_process_alive(&self, _pid: u32) -> bool {
85        // On non-Unix systems, assume the process is alive if PID file exists
86        true
87    }
88
89    /// Get the server socket path
90    pub fn socket_path(&self) -> PathBuf {
91        self.xdg_directories.socket_path()
92    }
93
94    /// Clean up server resources
95    pub fn cleanup(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
96        // Remove PID file
97        self.remove_pid()?;
98
99        // Remove socket file
100        let socket_path = self.socket_path();
101        if socket_path.exists() {
102            fs::remove_file(&socket_path)?;
103        }
104
105        Ok(())
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use tempfile::TempDir;
113
114    #[test]
115    fn test_server_lifecycle() {
116        let temp_dir = TempDir::new().unwrap();
117        unsafe {
118            std::env::set_var("XDG_DATA_HOME", temp_dir.path().join("data"));
119        }
120        unsafe {
121            std::env::set_var("XDG_RUNTIME_DIR", temp_dir.path().join("runtime"));
122        }
123
124        let xdg = Arc::new(XdgDirectories::new().unwrap());
125        xdg.ensure_directories().unwrap();
126
127        let lifecycle = ServerLifecycle::new(xdg);
128
129        // Initially no server should be running
130        assert!(!lifecycle.is_server_running());
131
132        // Write PID
133        lifecycle.write_pid().unwrap();
134
135        // Now server should be detected as running
136        assert!(lifecycle.is_server_running());
137
138        // Read PID should return current process ID
139        let pid = lifecycle.read_pid().unwrap();
140        assert_eq!(pid, process::id());
141
142        // Cleanup should remove PID file
143        lifecycle.cleanup().unwrap();
144        assert!(!lifecycle.is_server_running());
145    }
146}