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    /// Starts the server in foreground mode.
31    ///
32    /// Writes the current process ID to the PID file and executes the server function.
33    pub async fn start(&self) {
34        (self.get_start_hook())().await;
35        if let Err(e) = self.write_pid_file() {
36            eprintln!("Failed to write pid file: {e}");
37            return;
38        }
39        (self.get_server_hook())().await;
40    }
41
42    /// Stops the running server process.
43    ///
44    /// Reads PID from file and terminates the process.
45    ///
46    /// # Returns
47    ///
48    /// - `ServerManagerResult` - Operation result.
49    pub async fn stop(&self) -> ServerManagerResult {
50        (self.get_stop_hook())().await;
51        let pid: i32 = self.read_pid_file()?;
52        self.kill_process(pid)
53    }
54
55    /// Starts the server in daemon (background) mode on Unix platforms.
56    ///
57    /// # Returns
58    ///
59    /// - `ServerManagerResult` - Operation result.
60    #[cfg(not(windows))]
61    pub async fn start_daemon(&self) -> ServerManagerResult {
62        (self.get_start_hook())().await;
63        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
64            self.write_pid_file()?;
65            let rt: Runtime = Runtime::new()?;
66            rt.block_on(async {
67                (self.get_server_hook())().await;
68            });
69            return Ok(());
70        }
71        let exe_path: PathBuf = std::env::current_exe()?;
72        let mut cmd: Command = Command::new(exe_path);
73        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
74            .stdout(Stdio::null())
75            .stderr(Stdio::null())
76            .stdin(Stdio::null());
77        cmd.spawn()
78            .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
79        Ok(())
80    }
81
82    /// Starts the server in daemon (background) mode on Windows platforms.
83    ///
84    /// # Returns
85    ///
86    /// - `ServerManagerResult` - Operation result.
87    #[cfg(windows)]
88    pub async fn start_daemon(&self) -> ServerManagerResult {
89        (self.get_start_hook())().await;
90        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
91            self.write_pid_file()?;
92            let rt: Runtime = Runtime::new()?;
93            rt.block_on(async {
94                (self.get_server_hook())().await;
95            });
96            return Ok(());
97        }
98        let exe_path: PathBuf = std::env::current_exe()?;
99        let mut cmd: Command = Command::new(exe_path);
100        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
101            .stdout(Stdio::null())
102            .stderr(Stdio::null())
103            .stdin(Stdio::null())
104            .creation_flags(0x00000008);
105        cmd.spawn()
106            .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
107        Ok(())
108    }
109
110    /// Reads process ID from the PID file.
111    ///
112    /// # Returns
113    ///
114    /// - `Result<i32, Box<dyn std::error::Error>>` - Process ID if successful.
115    fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
116        let pid_str: String = fs::read_to_string(self.get_pid_file())?;
117        let pid: i32 = pid_str.trim().parse::<i32>()?;
118        Ok(pid)
119    }
120
121    /// Writes current process ID to the PID file.
122    ///
123    /// # Returns
124    ///
125    /// - `ServerManagerResult` - Operation result.
126    fn write_pid_file(&self) -> ServerManagerResult {
127        if let Some(parent) = Path::new(self.get_pid_file()).parent() {
128            fs::create_dir_all(parent)?;
129        }
130        let pid: u32 = id();
131        fs::write(self.get_pid_file(), pid.to_string())?;
132        Ok(())
133    }
134
135    /// Kills process by PID on Unix platforms.
136    ///
137    /// # Arguments
138    ///
139    /// - `i32` - The ID of the process to terminate.
140    ///
141    /// # Returns
142    ///
143    /// - `ServerManagerResult` - Operation result.
144    #[cfg(not(windows))]
145    fn kill_process(&self, pid: i32) -> ServerManagerResult {
146        match Command::new(KILL)
147            .arg(KILL_SIGNAL)
148            .arg(pid.to_string())
149            .output()
150        {
151            Ok(output) if output.status.success() => Ok(()),
152            Ok(output) => Err(format!(
153                "Failed to kill process with pid: {}, error: {}",
154                pid,
155                String::from_utf8_lossy(&output.stderr)
156            )
157            .into()),
158            Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
159        }
160    }
161
162    /// Kills process by PID on Windows platforms.
163    ///
164    /// # Arguments
165    ///
166    /// - `i32` - The ID of the process to terminate.
167    ///
168    /// # Returns
169    ///
170    /// - `ServerManagerResult` - Operation result.
171    #[cfg(windows)]
172    fn kill_process(&self, pid: i32) -> ServerManagerResult {
173        unsafe extern "system" {
174            /// Opens a process object with the requested access rights.
175            ///
176            /// # Arguments
177            ///
178            /// - `u32` - The access rights requested for the process handle.
179            /// - `i32` - Whether the returned handle is inheritable by child processes.
180            /// - `u32` - The identifier of the process to open.
181            ///
182            /// # Returns
183            ///
184            /// - `*mut c_void` - The process handle, or a null pointer on failure.
185            fn OpenProcess(
186                dwDesiredAccess: u32,
187                bInheritHandle: i32,
188                dwProcessId: u32,
189            ) -> *mut c_void;
190            /// Terminates a process and all of its child processes.
191            ///
192            /// # Arguments
193            ///
194            /// - `*mut c_void` - The process handle returned by `OpenProcess`.
195            /// - `u32` - The exit code reported for the terminated process.
196            ///
197            /// # Returns
198            ///
199            /// - `i32` - Non-zero when the process was terminated, zero on failure.
200            fn TerminateProcess(hProcess: *mut c_void, uExitCode: u32) -> i32;
201            /// Closes an open process handle.
202            ///
203            /// # Arguments
204            ///
205            /// - `*mut c_void` - The process handle to close.
206            ///
207            /// # Returns
208            ///
209            /// - `i32` - Non-zero when the handle was closed, zero on failure.
210            fn CloseHandle(hObject: *mut c_void) -> i32;
211            /// Reads the calling thread's last recorded Win32 error code.
212            ///
213            /// # Returns
214            ///
215            /// - `u32` - The last recorded error code.
216            fn GetLastError() -> u32;
217        }
218        let process_id: u32 = pid as u32;
219        let mut process_handle: *mut c_void = unsafe { OpenProcess(0x0001, 0, process_id) };
220        if process_handle.is_null() {
221            process_handle = unsafe { OpenProcess(0x1F0FFF, 0, process_id) };
222        }
223        if process_handle.is_null() {
224            let error_code: u32 = unsafe { GetLastError() };
225            return Err(format!(
226                "Failed to open process with pid: {pid}. Error code: {error_code}"
227            )
228            .into());
229        }
230        let terminate_result: i32 = unsafe { TerminateProcess(process_handle, 1) };
231        if terminate_result == 0 {
232            let error_code: u32 = unsafe { GetLastError() };
233            unsafe {
234                CloseHandle(process_handle);
235            }
236            return Err(format!(
237                "Failed to terminate process with pid: {pid}. Error code: {error_code}"
238            )
239            .into());
240        }
241        unsafe {
242            CloseHandle(process_handle);
243        }
244        Ok(())
245    }
246
247    /// Runs the server with cargo-watch.
248    ///
249    /// # Arguments
250    ///
251    /// - `&[&str]` - A slice of string arguments to pass to `cargo-watch`.
252    /// - `bool` - A boolean indicating whether to wait for the `cargo-watch` process to complete.
253    ///
254    /// # Returns
255    ///
256    /// - `ServerManagerResult` - Operation result.
257    async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
258        (self.get_start_hook())().await;
259        let cargo_watch_installed: Output = Command::new(CARGO)
260            .arg(INSTALL)
261            .args(CARGO_WATCH_INSTALL_LIST_ARGS)
262            .output()?;
263        if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains(CARGO_WATCH) {
264            eprintln!("{CARGO_WATCH_ABSENT_MESSAGE}");
265            let install_status: ExitStatus = Command::new(CARGO)
266                .args(CARGO_INSTALL_ARGS)
267                .stdout(Stdio::inherit())
268                .stderr(Stdio::inherit())
269                .spawn()?
270                .wait()?;
271            if !install_status.success() {
272                return Err(CARGO_WATCH_INSTALL_FAILED_MESSAGE.into());
273            }
274            eprintln!("{CARGO_WATCH_INSTALLED_MESSAGE}");
275        }
276        let mut command: Command = Command::new(CARGO_WATCH);
277        command
278            .args(run_args)
279            .stdout(Stdio::inherit())
280            .stderr(Stdio::inherit())
281            .stdin(Stdio::inherit());
282        let mut child: Child = command
283            .spawn()
284            .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
285        if wait {
286            child
287                .wait()
288                .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
289        }
290        exit(0);
291    }
292
293    /// Starts the server with hot-reloading using `cargo-watch` in detached mode.
294    ///
295    /// This function spawns `cargo-watch` and returns immediately.
296    ///
297    /// # Arguments
298    ///
299    /// - `&[&str]` - A slice of string arguments to pass to `cargo-watch`.
300    ///
301    /// # Returns
302    ///
303    /// - `ServerManagerResult` - Operation result.
304    pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
305        self.run_with_cargo_watch(run_args, false).await
306    }
307
308    /// Starts the server with hot-reloading using `cargo-watch` and waits for it to complete.
309    ///
310    /// This function is blocking and will wait for the `cargo-watch` process to exit.
311    ///
312    /// # Arguments
313    ///
314    /// - `&[&str]` - A slice of string arguments to pass to `cargo-watch`.
315    ///
316    /// # Returns
317    ///
318    /// - `ServerManagerResult` - Operation result.
319    pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
320        self.run_with_cargo_watch(run_args, true).await
321    }
322}