server_manager/manager/
impl.rs

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