server_manager/manager/
impl.rs1use crate::*;
2
3impl Default for ServerManager {
5 #[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
18impl ServerManager {
22 #[inline(always)]
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 #[inline(always)]
36 pub fn set_pid_file<P: ToString>(&mut self, pid_file: P) -> &mut Self {
37 self.pid_file = pid_file.to_string();
38 self
39 }
40
41 #[inline(always)]
47 pub fn set_start_hook<F, Fut>(&mut self, func: F) -> &mut Self
48 where
49 F: Fn() -> Fut + Send + Sync + 'static,
50 Fut: Future<Output = ()> + Send + 'static,
51 {
52 self.start_hook = Arc::new(move || Box::pin(func()));
53 self
54 }
55
56 #[inline(always)]
62 pub fn set_server_hook<F, Fut>(&mut self, func: F) -> &mut Self
63 where
64 F: Fn() -> Fut + Send + Sync + 'static,
65 Fut: Future<Output = ()> + Send + 'static,
66 {
67 self.server_hook = Arc::new(move || Box::pin(func()));
68 self
69 }
70
71 #[inline(always)]
77 pub fn set_stop_hook<F, Fut>(&mut self, func: F) -> &mut Self
78 where
79 F: Fn() -> Fut + Send + Sync + 'static,
80 Fut: Future<Output = ()> + Send + 'static,
81 {
82 self.stop_hook = Arc::new(move || Box::pin(func()));
83 self
84 }
85
86 #[inline(always)]
88 pub fn get_pid_file(&self) -> &str {
89 &self.pid_file
90 }
91
92 #[inline(always)]
94 pub fn get_start_hook(&self) -> &ServerManagerHook {
95 &self.start_hook
96 }
97
98 #[inline(always)]
100 pub fn get_server_hook(&self) -> &ServerManagerHook {
101 &self.server_hook
102 }
103
104 #[inline(always)]
106 pub fn get_stop_hook(&self) -> &ServerManagerHook {
107 &self.stop_hook
108 }
109
110 pub async fn start(&self) {
114 (self.start_hook)().await;
115 if let Err(e) = self.write_pid_file() {
116 eprintln!("Failed to write pid file: {e}");
117 return;
118 }
119 (self.server_hook)().await;
120 }
121
122 pub async fn stop(&self) -> ServerManagerResult {
130 (self.stop_hook)().await;
131 let pid: i32 = self.read_pid_file()?;
132 self.kill_process(pid)
133 }
134
135 #[cfg(not(windows))]
137 pub async fn start_daemon(&self) -> ServerManagerResult {
138 (self.start_hook)().await;
139 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
140 self.write_pid_file()?;
141 let rt: Runtime = Runtime::new()?;
142 rt.block_on(async {
143 (self.server_hook)().await;
144 });
145 return Ok(());
146 }
147 let exe_path: PathBuf = std::env::current_exe()?;
148 let mut cmd: Command = Command::new(exe_path);
149 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
150 .stdout(Stdio::null())
151 .stderr(Stdio::null())
152 .stdin(Stdio::null());
153 cmd.spawn()
154 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
155 Ok(())
156 }
157
158 #[cfg(windows)]
160 pub async fn start_daemon(&self) -> ServerManagerResult {
161 (self.start_hook)().await;
162 use std::os::windows::process::CommandExt;
163 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
164 self.write_pid_file()?;
165 let rt: Runtime = Runtime::new()?;
166 rt.block_on(async {
167 (self.server_hook)().await;
168 });
169 return Ok(());
170 }
171 let exe_path: PathBuf = std::env::current_exe()?;
172 let mut cmd: Command = Command::new(exe_path);
173 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
174 .stdout(Stdio::null())
175 .stderr(Stdio::null())
176 .stdin(Stdio::null())
177 .creation_flags(0x00000008);
178 cmd.spawn()
179 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
180 Ok(())
181 }
182
183 fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
189 let pid_str: String = fs::read_to_string(&self.pid_file)?;
190 let pid: i32 = pid_str.trim().parse::<i32>()?;
191 Ok(pid)
192 }
193
194 fn write_pid_file(&self) -> ServerManagerResult {
200 if let Some(parent) = Path::new(&self.pid_file).parent() {
201 fs::create_dir_all(parent)?;
202 }
203 let pid: u32 = id();
204 fs::write(&self.pid_file, pid.to_string())?;
205 Ok(())
206 }
207
208 #[cfg(not(windows))]
218 fn kill_process(&self, pid: i32) -> ServerManagerResult {
219 let result: Result<Output, std::io::Error> = Command::new("kill")
220 .arg("-TERM")
221 .arg(pid.to_string())
222 .output();
223 match result {
224 Ok(output) if output.status.success() => Ok(()),
225 Ok(output) => Err(format!(
226 "Failed to kill process with pid: {}, error: {}",
227 pid,
228 String::from_utf8_lossy(&output.stderr)
229 )
230 .into()),
231 Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
232 }
233 }
234
235 #[cfg(windows)]
245 fn kill_process(&self, pid: i32) -> ServerManagerResult {
246 use std::ffi::c_void;
247 type Dword = u32;
248 type Bool = i32;
249 type Handle = *mut c_void;
250 type Uint = u32;
251 const PROCESS_TERMINATE: Dword = 0x0001;
252 const PROCESS_ALL_ACCESS: Dword = 0x1F0FFF;
253 unsafe extern "system" {
254 fn OpenProcess(
255 dwDesiredAccess: Dword,
256 bInheritHandle: Bool,
257 dwProcessId: Dword,
258 ) -> Handle;
259 fn TerminateProcess(hProcess: Handle, uExitCode: Uint) -> Bool;
260 fn CloseHandle(hObject: Handle) -> Bool;
261 fn GetLastError() -> Dword;
262 }
263 let process_id: Dword = pid as Dword;
264 let mut process_handle: Handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
265 if process_handle.is_null() {
266 process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
267 }
268 if process_handle.is_null() {
269 let error_code = unsafe { GetLastError() };
270 return Err(format!(
271 "Failed to open process with pid: {pid}. Error code: {error_code}"
272 )
273 .into());
274 }
275 let terminate_result: Bool = unsafe { TerminateProcess(process_handle, 1) };
276 if terminate_result == 0 {
277 let error_code = unsafe { GetLastError() };
278 unsafe {
279 CloseHandle(process_handle);
280 }
281 return Err(format!(
282 "Failed to terminate process with pid: {pid}. Error code: {error_code}"
283 )
284 .into());
285 }
286 unsafe {
287 CloseHandle(process_handle);
288 }
289 Ok(())
290 }
291
292 async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
303 (self.start_hook)().await;
304 let cargo_watch_installed: Output = Command::new("cargo")
305 .arg("install")
306 .arg("--list")
307 .output()?;
308 if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
309 eprintln!("Cargo-watch not found. Attempting to install...");
310 let install_status: ExitStatus = Command::new("cargo")
311 .arg("install")
312 .arg("cargo-watch")
313 .stdout(Stdio::inherit())
314 .stderr(Stdio::inherit())
315 .spawn()?
316 .wait()?;
317 if !install_status.success() {
318 return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
319 }
320 eprintln!("Cargo-watch installed successfully.");
321 }
322 let mut command: Command = Command::new("cargo-watch");
323 command
324 .args(run_args)
325 .stdout(Stdio::inherit())
326 .stderr(Stdio::inherit())
327 .stdin(Stdio::inherit());
328 let mut child: Child = command
329 .spawn()
330 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
331 if wait {
332 child
333 .wait()
334 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
335 }
336 exit(0);
337 }
338
339 pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
351 self.run_with_cargo_watch(run_args, false).await
352 }
353
354 pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
366 self.run_with_cargo_watch(run_args, true).await
367 }
368}