Skip to main content

server_manager/
impl.rs

1use super::*;
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(|error: Error| Box::new(error) 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(|error: Error| Box::new(error) 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        match Command::new("kill")
220            .arg("-TERM")
221            .arg(pid.to_string())
222            .output()
223        {
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        unsafe extern "system" {
247            fn OpenProcess(
248                dwDesiredAccess: u32,
249                bInheritHandle: i32,
250                dwProcessId: u32,
251            ) -> *mut c_void;
252            fn TerminateProcess(hProcess: *mut c_void, uExitCode: u32) -> i32;
253            fn CloseHandle(hObject: *mut c_void) -> i32;
254            fn GetLastError() -> u32;
255        }
256        let process_id: u32 = pid as u32;
257        let mut process_handle: *mut c_void = unsafe { OpenProcess(0x0001, 0, process_id) };
258        if process_handle.is_null() {
259            process_handle = unsafe { OpenProcess(0x1F0FFF, 0, process_id) };
260        }
261        if process_handle.is_null() {
262            let error_code = unsafe { GetLastError() };
263            return Err(format!(
264                "Failed to open process with pid: {pid}. Error code: {error_code}"
265            )
266            .into());
267        }
268        let terminate_result: i32 = 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: {pid}. Error code: {error_code}"
276            )
277            .into());
278        }
279        unsafe {
280            CloseHandle(process_handle);
281        }
282        Ok(())
283    }
284
285    /// Runs the server with cargo-watch.
286    ///
287    /// # Arguments
288    ///
289    /// - `run_args` - A slice of string arguments to pass to `cargo-watch`.
290    /// - `wait` - A boolean indicating whether to wait for the `cargo-watch` process to complete.
291    ///
292    /// # Returns
293    ///
294    /// - `ServerManagerResult` - Operation result.
295    async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
296        (self.start_hook)().await;
297        let cargo_watch_installed: Output = Command::new("cargo")
298            .arg("install")
299            .arg("--list")
300            .output()?;
301        if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
302            eprintln!("Cargo-watch not found. Attempting to install...");
303            let install_status: ExitStatus = Command::new("cargo")
304                .arg("install")
305                .arg("cargo-watch")
306                .stdout(Stdio::inherit())
307                .stderr(Stdio::inherit())
308                .spawn()?
309                .wait()?;
310            if !install_status.success() {
311                return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
312            }
313            eprintln!("Cargo-watch installed successfully.");
314        }
315        let mut command: Command = Command::new("cargo-watch");
316        command
317            .args(run_args)
318            .stdout(Stdio::inherit())
319            .stderr(Stdio::inherit())
320            .stdin(Stdio::inherit());
321        let mut child: Child = command
322            .spawn()
323            .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
324        if wait {
325            child
326                .wait()
327                .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
328        }
329        exit(0);
330    }
331
332    /// Starts the server with hot-reloading using `cargo-watch` in detached mode.
333    ///
334    /// This function spawns `cargo-watch` and returns immediately.
335    ///
336    /// # Arguments
337    ///
338    /// - `run_args` - A slice of string arguments to pass to `cargo-watch`.
339    ///
340    /// # Returns
341    ///
342    /// - `ServerManagerResult` - Operation result.
343    pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
344        self.run_with_cargo_watch(run_args, false).await
345    }
346
347    /// Starts the server with hot-reloading using `cargo-watch` and waits for it to complete.
348    ///
349    /// This function is blocking and will wait for the `cargo-watch` process to exit.
350    ///
351    /// # Arguments
352    ///
353    /// - `run_args` - A slice of string arguments to pass to `cargo-watch`.
354    ///
355    /// # Returns
356    ///
357    /// - `ServerManagerResult` - Operation result.
358    pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
359        self.run_with_cargo_watch(run_args, true).await
360    }
361}