server_manager/manager/
impl.rs

1use crate::*;
2
3impl Default for ServerManager {
4    fn default() -> Self {
5        let empty_hook: Hook = Arc::new(|| Box::pin(async {}));
6        Self {
7            pid_file: Default::default(),
8            stop_hook: empty_hook.clone(),
9            server_hook: empty_hook.clone(),
10            start_hook: empty_hook,
11        }
12    }
13}
14
15/// Implementation of server management operations.
16///
17/// Provides methods for starting, stopping and managing server processes.
18impl ServerManager {
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    pub fn set_pid_file<P: ToString>(&mut self, pid_file: P) -> &mut Self {
24        self.pid_file = pid_file.to_string();
25        self
26    }
27
28    pub fn set_start_hook<F, Fut>(&mut self, f: F) -> &mut Self
29    where
30        F: Fn() -> Fut + Send + Sync + 'static,
31        Fut: Future<Output = ()> + Send + 'static,
32    {
33        self.start_hook = Arc::new(move || Box::pin(f()));
34        self
35    }
36
37    pub fn set_server_hook<F, Fut>(&mut self, f: F) -> &mut Self
38    where
39        F: Fn() -> Fut + Send + Sync + 'static,
40        Fut: Future<Output = ()> + Send + 'static,
41    {
42        self.server_hook = Arc::new(move || Box::pin(f()));
43        self
44    }
45
46    pub fn set_stop_hook<F, Fut>(&mut self, f: F) -> &mut Self
47    where
48        F: Fn() -> Fut + Send + Sync + 'static,
49        Fut: Future<Output = ()> + Send + 'static,
50    {
51        self.stop_hook = Arc::new(move || Box::pin(f()));
52        self
53    }
54
55    pub fn get_pid_file(&self) -> &str {
56        &self.pid_file
57    }
58
59    pub fn get_start_hook(&self) -> &Hook {
60        &self.start_hook
61    }
62
63    pub fn get_server_hook(&self) -> &Hook {
64        &self.server_hook
65    }
66
67    pub fn get_stop_hook(&self) -> &Hook {
68        &self.stop_hook
69    }
70
71    /// Starts the server in foreground mode.
72    ///
73    /// Writes the current process ID to the PID file and executes the server function.
74    pub async fn start(&self) {
75        (self.start_hook)().await;
76        if let Err(e) = self.write_pid_file() {
77            eprintln!("Failed to write pid file: {}", e);
78            return;
79        }
80        (self.server_hook)().await;
81    }
82
83    /// Stops the running server process.
84    ///
85    /// Reads PID from file and terminates the process.
86    ///
87    /// # Returns
88    ///
89    /// - `ServerManagerResult` - Operation result.
90    pub async fn stop(&self) -> ServerManagerResult {
91        (self.stop_hook)().await;
92        let pid: i32 = self.read_pid_file()?;
93        self.kill_process(pid)
94    }
95
96    /// Starts the server in daemon (background) mode on Unix platforms.
97    #[cfg(not(windows))]
98    pub async fn start_daemon(&self) -> ServerManagerResult {
99        (self.start_hook)().await;
100        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
101            self.write_pid_file()?;
102            let rt: Runtime = Runtime::new()?;
103            rt.block_on(async {
104                (self.server_hook)().await;
105            });
106            return Ok(());
107        }
108        let exe_path: PathBuf = std::env::current_exe()?;
109        let mut cmd: Command = Command::new(exe_path);
110        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
111            .stdout(Stdio::null())
112            .stderr(Stdio::null())
113            .stdin(Stdio::null());
114        cmd.spawn()
115            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
116        Ok(())
117    }
118
119    /// Starts the server in daemon (background) mode on Windows platforms.
120    #[cfg(windows)]
121    pub async fn start_daemon(&self) -> ServerManagerResult {
122        (self.start_hook)().await;
123        use std::os::windows::process::CommandExt;
124        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
125            self.write_pid_file()?;
126            let rt: Runtime = Runtime::new()?;
127            rt.block_on(async {
128                (self.server_hook)().await;
129            });
130            return Ok(());
131        }
132        let exe_path: PathBuf = std::env::current_exe()?;
133        let mut cmd: Command = Command::new(exe_path);
134        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
135            .stdout(Stdio::null())
136            .stderr(Stdio::null())
137            .stdin(Stdio::null())
138            .creation_flags(0x00000008);
139        cmd.spawn()
140            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
141        Ok(())
142    }
143
144    /// Reads process ID from the PID file.
145    ///
146    /// # Returns
147    ///
148    /// - `Result<i32, Box<dyn std::error::Error>>` - Process ID if successful.
149    fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
150        let pid_str: String = fs::read_to_string(&self.pid_file)?;
151        let pid: i32 = pid_str.trim().parse::<i32>()?;
152        Ok(pid)
153    }
154
155    /// Writes current process ID to the PID file.
156    ///
157    /// # Returns
158    ///
159    /// - `ServerManagerResult` - Operation result.
160    fn write_pid_file(&self) -> ServerManagerResult {
161        if let Some(parent) = Path::new(&self.pid_file).parent() {
162            fs::create_dir_all(parent)?;
163        }
164        let pid: u32 = id();
165        fs::write(&self.pid_file, pid.to_string())?;
166        Ok(())
167    }
168
169    /// Kills process by PID on Unix platforms.
170    ///
171    /// # Arguments
172    ///
173    /// - `i32` - Process ID to terminate.
174    ///
175    /// # Returns
176    ///
177    /// - `ServerManagerResult` - Operation result.
178    #[cfg(not(windows))]
179    fn kill_process(&self, pid: i32) -> ServerManagerResult {
180        let result: Result<Output, std::io::Error> = Command::new("kill")
181            .arg("-TERM")
182            .arg(pid.to_string())
183            .output();
184        match result {
185            Ok(output) if output.status.success() => Ok(()),
186            Ok(output) => Err(format!(
187                "Failed to kill process with pid: {}, error: {}",
188                pid,
189                String::from_utf8_lossy(&output.stderr)
190            )
191            .into()),
192            Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
193        }
194    }
195
196    /// Kills process by PID on Windows platforms.
197    ///
198    /// # Arguments
199    ///
200    /// - `i32` - Process ID to terminate.
201    ///
202    /// # Returns
203    ///
204    /// - `ServerManagerResult` - Operation result.
205    #[cfg(windows)]
206    fn kill_process(&self, pid: i32) -> ServerManagerResult {
207        use std::ffi::c_void;
208        type DWORD = u32;
209        type BOOL = i32;
210        type HANDLE = *mut c_void;
211        type UINT = u32;
212        const PROCESS_TERMINATE: DWORD = 0x0001;
213        const PROCESS_ALL_ACCESS: DWORD = 0x1F0FFF;
214        unsafe extern "system" {
215            fn OpenProcess(
216                dwDesiredAccess: DWORD,
217                bInheritHandle: BOOL,
218                dwProcessId: DWORD,
219            ) -> HANDLE;
220            fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
221            fn CloseHandle(hObject: HANDLE) -> BOOL;
222            fn GetLastError() -> DWORD;
223        }
224        let process_id: DWORD = pid as DWORD;
225        let mut process_handle: HANDLE = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
226        if process_handle.is_null() {
227            process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
228        }
229        if process_handle.is_null() {
230            let error_code = unsafe { GetLastError() };
231            return Err(format!(
232                "Failed to open process with pid: {}. Error code: {}",
233                pid, error_code
234            )
235            .into());
236        }
237        let terminate_result: BOOL = unsafe { TerminateProcess(process_handle, 1) };
238        if terminate_result == 0 {
239            let error_code = unsafe { GetLastError() };
240            unsafe {
241                CloseHandle(process_handle);
242            }
243            return Err(format!(
244                "Failed to terminate process with pid: {}. Error code: {}",
245                pid, error_code
246            )
247            .into());
248        }
249        unsafe {
250            CloseHandle(process_handle);
251        }
252        Ok(())
253    }
254
255    /// Runs the server with cargo-watch.
256    ///
257    /// # Arguments
258    ///
259    /// - `&[&str]` - Arguments for cargo-watch.
260    /// - `bool` - Whether to wait for process completion.
261    ///
262    /// # Returns
263    ///
264    /// - `ServerManagerResult` - Operation result.
265    async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
266        (self.start_hook)().await;
267        let cargo_watch_installed: Output = Command::new("cargo")
268            .arg("install")
269            .arg("--list")
270            .output()?;
271        if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
272            eprintln!("Cargo-watch not found. Attempting to install...");
273            let install_status: ExitStatus = Command::new("cargo")
274                .arg("install")
275                .arg("cargo-watch")
276                .stdout(Stdio::inherit())
277                .stderr(Stdio::inherit())
278                .spawn()?
279                .wait()?;
280            if !install_status.success() {
281                return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
282            }
283            eprintln!("Cargo-watch installed successfully.");
284        }
285        let mut command: Command = Command::new("cargo-watch");
286        command
287            .args(run_args)
288            .stdout(Stdio::inherit())
289            .stderr(Stdio::inherit())
290            .stdin(Stdio::inherit());
291        let mut child: Child = command
292            .spawn()
293            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
294        if wait {
295            child
296                .wait()
297                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
298        }
299        exit(0);
300    }
301
302    /// Starts the server with hot-reloading using cargo-watch.
303    ///
304    /// # Arguments
305    ///
306    /// - `&[&str]` - Arguments for cargo-watch.
307    ///
308    /// # Returns
309    ///
310    /// - `ServerManagerResult` - Operation result.
311    pub async fn hot_restart(&self, run_args: &[&str]) -> ServerManagerResult {
312        self.run_with_cargo_watch(run_args, false).await
313    }
314
315    /// Starts the server with hot-reloading and waits for completion.
316    ///
317    /// # Arguments
318    ///
319    /// - `&[&str]` - Arguments for cargo-watch.
320    ///
321    /// # Returns
322    ///
323    /// - `ServerManagerResult` - Operation result.
324    pub async fn hot_restart_wait(&self, run_args: &[&str]) -> ServerManagerResult {
325        self.run_with_cargo_watch(run_args, true).await
326    }
327}