server_manager/manager/
impl.rs1use crate::*;
2
3impl Default for ServerManager {
5    fn default() -> Self {
7        let empty_hook: Hook = Arc::new(|| Box::pin(async {}));
8        Self {
9            pid_file: Default::default(),
10            stop_hook: empty_hook.clone(),
11            server_hook: empty_hook.clone(),
12            start_hook: empty_hook,
13        }
14    }
15}
16
17impl ServerManager {
21    pub fn new() -> Self {
25        Self::default()
26    }
27
28    pub fn set_pid_file<P: ToString>(&mut self, pid_file: P) -> &mut Self {
34        self.pid_file = pid_file.to_string();
35        self
36    }
37
38    pub fn set_start_hook<F, Fut>(&mut self, f: F) -> &mut Self
44    where
45        F: Fn() -> Fut + Send + Sync + 'static,
46        Fut: Future<Output = ()> + Send + 'static,
47    {
48        self.start_hook = Arc::new(move || Box::pin(f()));
49        self
50    }
51
52    pub fn set_server_hook<F, Fut>(&mut self, f: F) -> &mut Self
58    where
59        F: Fn() -> Fut + Send + Sync + 'static,
60        Fut: Future<Output = ()> + Send + 'static,
61    {
62        self.server_hook = Arc::new(move || Box::pin(f()));
63        self
64    }
65
66    pub fn set_stop_hook<F, Fut>(&mut self, f: F) -> &mut Self
72    where
73        F: Fn() -> Fut + Send + Sync + 'static,
74        Fut: Future<Output = ()> + Send + 'static,
75    {
76        self.stop_hook = Arc::new(move || Box::pin(f()));
77        self
78    }
79
80    pub fn get_pid_file(&self) -> &str {
82        &self.pid_file
83    }
84
85    pub fn get_start_hook(&self) -> &Hook {
87        &self.start_hook
88    }
89
90    pub fn get_server_hook(&self) -> &Hook {
92        &self.server_hook
93    }
94
95    pub fn get_stop_hook(&self) -> &Hook {
97        &self.stop_hook
98    }
99
100    pub async fn start(&self) {
104        (self.start_hook)().await;
105        if let Err(e) = self.write_pid_file() {
106            eprintln!("Failed to write pid file: {}", e);
107            return;
108        }
109        (self.server_hook)().await;
110    }
111
112    pub async fn stop(&self) -> ServerManagerResult {
120        (self.stop_hook)().await;
121        let pid: i32 = self.read_pid_file()?;
122        self.kill_process(pid)
123    }
124
125    #[cfg(not(windows))]
127    pub async fn start_daemon(&self) -> ServerManagerResult {
128        (self.start_hook)().await;
129        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
130            self.write_pid_file()?;
131            let rt: Runtime = Runtime::new()?;
132            rt.block_on(async {
133                (self.server_hook)().await;
134            });
135            return Ok(());
136        }
137        let exe_path: PathBuf = std::env::current_exe()?;
138        let mut cmd: Command = Command::new(exe_path);
139        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
140            .stdout(Stdio::null())
141            .stderr(Stdio::null())
142            .stdin(Stdio::null());
143        cmd.spawn()
144            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
145        Ok(())
146    }
147
148    #[cfg(windows)]
150    pub async fn start_daemon(&self) -> ServerManagerResult {
151        (self.start_hook)().await;
152        use std::os::windows::process::CommandExt;
153        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
154            self.write_pid_file()?;
155            let rt: Runtime = Runtime::new()?;
156            rt.block_on(async {
157                (self.server_hook)().await;
158            });
159            return Ok(());
160        }
161        let exe_path: PathBuf = std::env::current_exe()?;
162        let mut cmd: Command = Command::new(exe_path);
163        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
164            .stdout(Stdio::null())
165            .stderr(Stdio::null())
166            .stdin(Stdio::null())
167            .creation_flags(0x00000008);
168        cmd.spawn()
169            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
170        Ok(())
171    }
172
173    fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
179        let pid_str: String = fs::read_to_string(&self.pid_file)?;
180        let pid: i32 = pid_str.trim().parse::<i32>()?;
181        Ok(pid)
182    }
183
184    fn write_pid_file(&self) -> ServerManagerResult {
190        if let Some(parent) = Path::new(&self.pid_file).parent() {
191            fs::create_dir_all(parent)?;
192        }
193        let pid: u32 = id();
194        fs::write(&self.pid_file, pid.to_string())?;
195        Ok(())
196    }
197
198    #[cfg(not(windows))]
208    fn kill_process(&self, pid: i32) -> ServerManagerResult {
209        let result: Result<Output, std::io::Error> = Command::new("kill")
210            .arg("-TERM")
211            .arg(pid.to_string())
212            .output();
213        match result {
214            Ok(output) if output.status.success() => Ok(()),
215            Ok(output) => Err(format!(
216                "Failed to kill process with pid: {}, error: {}",
217                pid,
218                String::from_utf8_lossy(&output.stderr)
219            )
220            .into()),
221            Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
222        }
223    }
224
225    #[cfg(windows)]
235    fn kill_process(&self, pid: i32) -> ServerManagerResult {
236        use std::ffi::c_void;
237        type DWORD = u32;
238        type BOOL = i32;
239        type HANDLE = *mut c_void;
240        type UINT = u32;
241        const PROCESS_TERMINATE: DWORD = 0x0001;
242        const PROCESS_ALL_ACCESS: DWORD = 0x1F0FFF;
243        unsafe extern "system" {
244            fn OpenProcess(
245                dwDesiredAccess: DWORD,
246                bInheritHandle: BOOL,
247                dwProcessId: DWORD,
248            ) -> HANDLE;
249            fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
250            fn CloseHandle(hObject: HANDLE) -> BOOL;
251            fn GetLastError() -> DWORD;
252        }
253        let process_id: DWORD = pid as DWORD;
254        let mut process_handle: HANDLE = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
255        if process_handle.is_null() {
256            process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
257        }
258        if process_handle.is_null() {
259            let error_code = unsafe { GetLastError() };
260            return Err(format!(
261                "Failed to open process with pid: {}. Error code: {}",
262                pid, error_code
263            )
264            .into());
265        }
266        let terminate_result: BOOL = unsafe { TerminateProcess(process_handle, 1) };
267        if terminate_result == 0 {
268            let error_code = unsafe { GetLastError() };
269            unsafe {
270                CloseHandle(process_handle);
271            }
272            return Err(format!(
273                "Failed to terminate process with pid: {}. Error code: {}",
274                pid, error_code
275            )
276            .into());
277        }
278        unsafe {
279            CloseHandle(process_handle);
280        }
281        Ok(())
282    }
283
284    async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
295        (self.start_hook)().await;
296        let cargo_watch_installed: Output = Command::new("cargo")
297            .arg("install")
298            .arg("--list")
299            .output()?;
300        if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
301            eprintln!("Cargo-watch not found. Attempting to install...");
302            let install_status: ExitStatus = Command::new("cargo")
303                .arg("install")
304                .arg("cargo-watch")
305                .stdout(Stdio::inherit())
306                .stderr(Stdio::inherit())
307                .spawn()?
308                .wait()?;
309            if !install_status.success() {
310                return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
311            }
312            eprintln!("Cargo-watch installed successfully.");
313        }
314        let mut command: Command = Command::new("cargo-watch");
315        command
316            .args(run_args)
317            .stdout(Stdio::inherit())
318            .stderr(Stdio::inherit())
319            .stdin(Stdio::inherit());
320        let mut child: Child = command
321            .spawn()
322            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
323        if wait {
324            child
325                .wait()
326                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
327        }
328        exit(0);
329    }
330
331    pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
343        self.run_with_cargo_watch(run_args, false).await
344    }
345
346    pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
358        self.run_with_cargo_watch(run_args, true).await
359    }
360}