1use crate::storage::XdgDirectories;
2use std::fs;
3use std::io::{Read, Write};
4use std::path::PathBuf;
5use std::process;
6use std::sync::Arc;
7
8pub struct ServerLifecycle {
10 xdg_directories: Arc<XdgDirectories>,
11}
12
13impl ServerLifecycle {
14 pub fn new(xdg_directories: Arc<XdgDirectories>) -> Self {
16 Self { xdg_directories }
17 }
18
19 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 match self.read_pid() {
29 Some(pid) => {
30 self.is_process_alive(pid)
32 }
33 None => false,
34 }
35 }
36
37 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 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 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 #[cfg(unix)]
76 fn is_process_alive(&self, pid: u32) -> bool {
77 unsafe {
78 libc::kill(pid as i32, 0) == 0
80 }
81 }
82
83 #[cfg(not(unix))]
84 fn is_process_alive(&self, _pid: u32) -> bool {
85 true
87 }
88
89 pub fn socket_path(&self) -> PathBuf {
91 self.xdg_directories.socket_path()
92 }
93
94 pub fn cleanup(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
96 self.remove_pid()?;
98
99 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 assert!(!lifecycle.is_server_running());
131
132 lifecycle.write_pid().unwrap();
134
135 assert!(lifecycle.is_server_running());
137
138 let pid = lifecycle.read_pid().unwrap();
140 assert_eq!(pid, process::id());
141
142 lifecycle.cleanup().unwrap();
144 assert!(!lifecycle.is_server_running());
145 }
146}