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