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