server_manager/manager/
impl.rs

1use crate::*;
2
3/// Implementation of server management operations.
4///
5/// Provides methods for starting, stopping and managing server processes.
6impl<F, Fut> ServerManager<F>
7where
8    F: Fn() -> Fut,
9    Fut: std::future::Future<Output = ()>,
10{
11    /// Creates a new ServerManager instance.
12    ///
13    /// # Arguments
14    ///
15    /// - `ServerManagerConfig` - Server configuration parameters.
16    /// - `F` - Asynchronous server function.
17    ///
18    /// # Returns
19    ///
20    /// - `ServerManager<F>` - New server manager instance.
21    pub fn new(config: ServerManagerConfig, server_fn: F) -> Self {
22        Self { config, server_fn }
23    }
24
25    /// Starts the server in foreground mode.
26    ///
27    /// Writes the current process ID to the PID file and executes the server function.
28    pub async fn start(&self) {
29        if let Err(e) = self.write_pid_file() {
30            eprintln!("Failed to write pid file: {}", e);
31            return;
32        }
33        (self.server_fn)().await;
34    }
35
36    /// Stops the running server process.
37    ///
38    /// Reads PID from file and terminates the process.
39    ///
40    /// # Returns
41    ///
42    /// - `ServerManagerResult` - Operation result.
43    pub fn stop(&self) -> ServerManagerResult {
44        let pid: i32 = self.read_pid_file()?;
45        self.kill_process(pid)
46    }
47
48    /// Starts the server in daemon (background) mode on Unix platforms.
49    #[cfg(not(windows))]
50    pub fn start_daemon(&self) -> ServerManagerResult {
51        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
52            self.write_pid_file()?;
53            let rt: Runtime = Runtime::new()?;
54            rt.block_on(async {
55                (self.server_fn)().await;
56            });
57            return Ok(());
58        }
59        let exe_path: PathBuf = std::env::current_exe()?;
60        let mut cmd: Command = Command::new(exe_path);
61        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
62            .stdout(Stdio::null())
63            .stderr(Stdio::null())
64            .stdin(Stdio::null());
65        cmd.spawn()
66            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
67        Ok(())
68    }
69
70    /// Starts the server in daemon (background) mode on Windows platforms.
71    #[cfg(windows)]
72    pub fn start_daemon(&self) -> ServerManagerResult {
73        use std::os::windows::process::CommandExt;
74        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
75            self.write_pid_file()?;
76            let rt: Runtime = Runtime::new()?;
77            rt.block_on(async {
78                (self.server_fn)().await;
79            });
80            return Ok(());
81        }
82        let exe_path: PathBuf = std::env::current_exe()?;
83        let mut cmd: Command = Command::new(exe_path);
84        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
85            .stdout(Stdio::null())
86            .stderr(Stdio::null())
87            .stdin(Stdio::null())
88            .creation_flags(0x00000008);
89        cmd.spawn()
90            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
91        Ok(())
92    }
93
94    /// Reads process ID from the PID file.
95    ///
96    /// # Returns
97    ///
98    /// - `Result<i32, Box<dyn std::error::Error>>` - Process ID if successful.
99    fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
100        let pid_str: String = fs::read_to_string(&self.config.pid_file)?;
101        let pid: i32 = pid_str.trim().parse::<i32>()?;
102        Ok(pid)
103    }
104
105    /// Writes current process ID to the PID file.
106    ///
107    /// # Returns
108    ///
109    /// - `ServerManagerResult` - Operation result.
110    fn write_pid_file(&self) -> ServerManagerResult {
111        if let Some(parent) = Path::new(&self.config.pid_file).parent() {
112            fs::create_dir_all(parent)?;
113        }
114        let pid: u32 = id();
115        fs::write(&self.config.pid_file, pid.to_string())?;
116        Ok(())
117    }
118
119    /// Kills process by PID on Unix platforms.
120    ///
121    /// # Arguments
122    ///
123    /// - `i32` - Process ID to terminate.
124    ///
125    /// # Returns
126    ///
127    /// - `ServerManagerResult` - Operation result.
128    #[cfg(not(windows))]
129    fn kill_process(&self, pid: i32) -> ServerManagerResult {
130        let result: Result<Output, std::io::Error> = Command::new("kill")
131            .arg("-TERM")
132            .arg(pid.to_string())
133            .output();
134        match result {
135            Ok(output) if output.status.success() => Ok(()),
136            Ok(output) => Err(format!(
137                "Failed to kill process with pid: {}, error: {}",
138                pid,
139                String::from_utf8_lossy(&output.stderr)
140            )
141            .into()),
142            Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
143        }
144    }
145
146    /// Kills process by PID on Windows platforms.
147    ///
148    /// # Arguments
149    ///
150    /// - `i32` - Process ID to terminate.
151    ///
152    /// # Returns
153    ///
154    /// - `ServerManagerResult` - Operation result.
155    #[cfg(windows)]
156    fn kill_process(&self, pid: i32) -> ServerManagerResult {
157        use std::ffi::c_void;
158        type DWORD = u32;
159        type BOOL = i32;
160        type HANDLE = *mut c_void;
161        type UINT = u32;
162        const PROCESS_TERMINATE: DWORD = 0x0001;
163        const PROCESS_ALL_ACCESS: DWORD = 0x1F0FFF;
164        unsafe extern "system" {
165            fn OpenProcess(
166                dwDesiredAccess: DWORD,
167                bInheritHandle: BOOL,
168                dwProcessId: DWORD,
169            ) -> HANDLE;
170            fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
171            fn CloseHandle(hObject: HANDLE) -> BOOL;
172            fn GetLastError() -> DWORD;
173        }
174        let process_id: DWORD = pid as DWORD;
175        let mut process_handle: HANDLE = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
176        if process_handle.is_null() {
177            process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
178        }
179        if process_handle.is_null() {
180            let error_code = unsafe { GetLastError() };
181            return Err(format!(
182                "Failed to open process with pid: {}. Error code: {}",
183                pid, error_code
184            )
185            .into());
186        }
187        let terminate_result: BOOL = unsafe { TerminateProcess(process_handle, 1) };
188        if terminate_result == 0 {
189            let error_code = unsafe { GetLastError() };
190            unsafe {
191                CloseHandle(process_handle);
192            }
193            return Err(format!(
194                "Failed to terminate process with pid: {}. Error code: {}",
195                pid, 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    /// - `&[&str]` - Arguments for cargo-watch.
210    /// - `bool` - Whether to wait for process completion.
211    ///
212    /// # Returns
213    ///
214    /// - `ServerManagerResult` - Operation result.
215    fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
216        let cargo_watch_installed: Output = Command::new("cargo")
217            .arg("install")
218            .arg("--list")
219            .output()?;
220        if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
221            eprintln!("Cargo-watch not found. Attempting to install...");
222            let install_status: ExitStatus = Command::new("cargo")
223                .arg("install")
224                .arg("cargo-watch")
225                .stdout(Stdio::inherit())
226                .stderr(Stdio::inherit())
227                .spawn()?
228                .wait()?;
229            if !install_status.success() {
230                return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
231            }
232            eprintln!("Cargo-watch installed successfully.");
233        }
234        let mut command: Command = Command::new("cargo-watch");
235        command
236            .args(run_args)
237            .stdout(Stdio::inherit())
238            .stderr(Stdio::inherit())
239            .stdin(Stdio::inherit());
240        let mut child: Child = command
241            .spawn()
242            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
243        if wait {
244            child
245                .wait()
246                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
247        }
248        exit(0);
249    }
250
251    /// Starts the server with hot-reloading using cargo-watch.
252    ///
253    /// # Arguments
254    ///
255    /// - `&[&str]` - Arguments for cargo-watch.
256    ///
257    /// # Returns
258    ///
259    /// - `ServerManagerResult` - Operation result.
260    pub fn hot_restart(&self, run_args: &[&str]) -> ServerManagerResult {
261        self.run_with_cargo_watch(run_args, false)
262    }
263
264    /// Starts the server with hot-reloading and waits for completion.
265    ///
266    /// # Arguments
267    ///
268    /// - `&[&str]` - Arguments for cargo-watch.
269    ///
270    /// # Returns
271    ///
272    /// - `ServerManagerResult` - Operation result.
273    pub fn hot_restart_wait(&self, run_args: &[&str]) -> ServerManagerResult {
274        self.run_with_cargo_watch(run_args, true)
275    }
276}