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