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    #[cfg(not(windows))]
57    pub async fn start_daemon(&self) -> ServerManagerResult {
58        (self.get_start_hook())().await;
59        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
60            self.write_pid_file()?;
61            let rt: Runtime = Runtime::new()?;
62            rt.block_on(async {
63                (self.get_server_hook())().await;
64            });
65            return Ok(());
66        }
67        let exe_path: PathBuf = std::env::current_exe()?;
68        let mut cmd: Command = Command::new(exe_path);
69        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
70            .stdout(Stdio::null())
71            .stderr(Stdio::null())
72            .stdin(Stdio::null());
73        cmd.spawn()
74            .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
75        Ok(())
76    }
77
78    /// Starts the server in daemon (background) mode on Windows platforms.
79    #[cfg(windows)]
80    pub async fn start_daemon(&self) -> ServerManagerResult {
81        (self.get_start_hook())().await;
82        use std::os::windows::process::CommandExt;
83        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
84            self.write_pid_file()?;
85            let rt: Runtime = Runtime::new()?;
86            rt.block_on(async {
87                (self.get_server_hook())().await;
88            });
89            return Ok(());
90        }
91        let exe_path: PathBuf = std::env::current_exe()?;
92        let mut cmd: Command = Command::new(exe_path);
93        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
94            .stdout(Stdio::null())
95            .stderr(Stdio::null())
96            .stdin(Stdio::null())
97            .creation_flags(0x00000008);
98        cmd.spawn()
99            .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
100        Ok(())
101    }
102
103    /// Reads process ID from the PID file.
104    ///
105    /// # Returns
106    ///
107    /// - `Result<i32, Box<dyn std::error::Error>>` - Process ID if successful.
108    fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
109        let pid_str: String = fs::read_to_string(self.get_pid_file())?;
110        let pid: i32 = pid_str.trim().parse::<i32>()?;
111        Ok(pid)
112    }
113
114    /// Writes current process ID to the PID file.
115    ///
116    /// # Returns
117    ///
118    /// - `ServerManagerResult` - Operation result.
119    fn write_pid_file(&self) -> ServerManagerResult {
120        if let Some(parent) = Path::new(self.get_pid_file()).parent() {
121            fs::create_dir_all(parent)?;
122        }
123        let pid: u32 = id();
124        fs::write(self.get_pid_file(), pid.to_string())?;
125        Ok(())
126    }
127
128    /// Kills process by PID on Unix platforms.
129    ///
130    /// # Arguments
131    ///
132    /// - `pid` - The ID of the process to terminate.
133    ///
134    /// # Returns
135    ///
136    /// - `ServerManagerResult` - Operation result.
137    #[cfg(not(windows))]
138    fn kill_process(&self, pid: i32) -> ServerManagerResult {
139        match Command::new("kill")
140            .arg("-TERM")
141            .arg(pid.to_string())
142            .output()
143        {
144            Ok(output) if output.status.success() => Ok(()),
145            Ok(output) => Err(format!(
146                "Failed to kill process with pid: {}, error: {}",
147                pid,
148                String::from_utf8_lossy(&output.stderr)
149            )
150            .into()),
151            Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
152        }
153    }
154
155    /// Kills process by PID on Windows platforms.
156    ///
157    /// # Arguments
158    ///
159    /// - `pid` - The ID of the process to terminate.
160    ///
161    /// # Returns
162    ///
163    /// - `ServerManagerResult` - Operation result.
164    #[cfg(windows)]
165    fn kill_process(&self, pid: i32) -> ServerManagerResult {
166        unsafe extern "system" {
167            fn OpenProcess(
168                dwDesiredAccess: u32,
169                bInheritHandle: i32,
170                dwProcessId: u32,
171            ) -> *mut c_void;
172            fn TerminateProcess(hProcess: *mut c_void, uExitCode: u32) -> i32;
173            fn CloseHandle(hObject: *mut c_void) -> i32;
174            fn GetLastError() -> u32;
175        }
176        let process_id: u32 = pid as u32;
177        let mut process_handle: *mut c_void = unsafe { OpenProcess(0x0001, 0, process_id) };
178        if process_handle.is_null() {
179            process_handle = unsafe { OpenProcess(0x1F0FFF, 0, process_id) };
180        }
181        if process_handle.is_null() {
182            let error_code = unsafe { GetLastError() };
183            return Err(format!(
184                "Failed to open process with pid: {pid}. Error code: {error_code}"
185            )
186            .into());
187        }
188        let terminate_result: i32 = unsafe { TerminateProcess(process_handle, 1) };
189        if terminate_result == 0 {
190            let error_code = unsafe { GetLastError() };
191            unsafe {
192                CloseHandle(process_handle);
193            }
194            return Err(format!(
195                "Failed to terminate process with pid: {pid}. Error code: {error_code}"
196            )
197            .into());
198        }
199        unsafe {
200            CloseHandle(process_handle);
201        }
202        Ok(())
203    }
204
205    /// Runs the server with cargo-watch.
206    ///
207    /// # Arguments
208    ///
209    /// - `run_args` - A slice of string arguments to pass to `cargo-watch`.
210    /// - `wait` - A boolean indicating whether to wait for the `cargo-watch` process to complete.
211    ///
212    /// # Returns
213    ///
214    /// - `ServerManagerResult` - Operation result.
215    async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
216        (self.get_start_hook())().await;
217        let cargo_watch_installed: Output = Command::new("cargo")
218            .arg("install")
219            .arg("--list")
220            .output()?;
221        if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
222            eprintln!("Cargo-watch not found. Attempting to install...");
223            let install_status: ExitStatus = Command::new("cargo")
224                .arg("install")
225                .arg("cargo-watch")
226                .stdout(Stdio::inherit())
227                .stderr(Stdio::inherit())
228                .spawn()?
229                .wait()?;
230            if !install_status.success() {
231                return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
232            }
233            eprintln!("Cargo-watch installed successfully.");
234        }
235        let mut command: Command = Command::new("cargo-watch");
236        command
237            .args(run_args)
238            .stdout(Stdio::inherit())
239            .stderr(Stdio::inherit())
240            .stdin(Stdio::inherit());
241        let mut child: Child = command
242            .spawn()
243            .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
244        if wait {
245            child
246                .wait()
247                .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
248        }
249        exit(0);
250    }
251
252    /// Starts the server with hot-reloading using `cargo-watch` in detached mode.
253    ///
254    /// This function spawns `cargo-watch` and returns immediately.
255    ///
256    /// # Arguments
257    ///
258    /// - `run_args` - A slice of string arguments to pass to `cargo-watch`.
259    ///
260    /// # Returns
261    ///
262    /// - `ServerManagerResult` - Operation result.
263    pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
264        self.run_with_cargo_watch(run_args, false).await
265    }
266
267    /// Starts the server with hot-reloading using `cargo-watch` and waits for it to complete.
268    ///
269    /// This function is blocking and will wait for the `cargo-watch` process to exit.
270    ///
271    /// # Arguments
272    ///
273    /// - `run_args` - A slice of string arguments to pass to `cargo-watch`.
274    ///
275    /// # Returns
276    ///
277    /// - `ServerManagerResult` - Operation result.
278    pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
279        self.run_with_cargo_watch(run_args, true).await
280    }
281}