server_manager/manager/
impl.rs1use crate::*;
2
3impl Default for ServerManager {
5 fn default() -> Self {
7 let empty_hook: ServerManagerHook = 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 #[inline]
25 pub fn new() -> Self {
26 Self::default()
27 }
28
29 pub fn set_pid_file<P: ToString>(&mut self, pid_file: P) -> &mut Self {
35 self.pid_file = pid_file.to_string();
36 self
37 }
38
39 pub fn set_start_hook<F, Fut>(&mut self, func: F) -> &mut Self
45 where
46 F: Fn() -> Fut + Send + Sync + 'static,
47 Fut: Future<Output = ()> + Send + 'static,
48 {
49 self.start_hook = Arc::new(move || Box::pin(func()));
50 self
51 }
52
53 pub fn set_server_hook<F, Fut>(&mut self, func: F) -> &mut Self
59 where
60 F: Fn() -> Fut + Send + Sync + 'static,
61 Fut: Future<Output = ()> + Send + 'static,
62 {
63 self.server_hook = Arc::new(move || Box::pin(func()));
64 self
65 }
66
67 pub fn set_stop_hook<F, Fut>(&mut self, func: F) -> &mut Self
73 where
74 F: Fn() -> Fut + Send + Sync + 'static,
75 Fut: Future<Output = ()> + Send + 'static,
76 {
77 self.stop_hook = Arc::new(move || Box::pin(func()));
78 self
79 }
80
81 #[inline]
83 pub fn get_pid_file(&self) -> &str {
84 &self.pid_file
85 }
86
87 pub fn get_start_hook(&self) -> &ServerManagerHook {
89 &self.start_hook
90 }
91
92 pub fn get_server_hook(&self) -> &ServerManagerHook {
94 &self.server_hook
95 }
96
97 pub fn get_stop_hook(&self) -> &ServerManagerHook {
99 &self.stop_hook
100 }
101
102 pub async fn start(&self) {
106 (self.start_hook)().await;
107 if let Err(e) = self.write_pid_file() {
108 eprintln!("Failed to write pid file: {e}");
109 return;
110 }
111 (self.server_hook)().await;
112 }
113
114 pub async fn stop(&self) -> ServerManagerResult {
122 (self.stop_hook)().await;
123 let pid: i32 = self.read_pid_file()?;
124 self.kill_process(pid)
125 }
126
127 #[cfg(not(windows))]
129 pub async fn start_daemon(&self) -> ServerManagerResult {
130 (self.start_hook)().await;
131 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
132 self.write_pid_file()?;
133 let rt: Runtime = Runtime::new()?;
134 rt.block_on(async {
135 (self.server_hook)().await;
136 });
137 return Ok(());
138 }
139 let exe_path: PathBuf = std::env::current_exe()?;
140 let mut cmd: Command = Command::new(exe_path);
141 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
142 .stdout(Stdio::null())
143 .stderr(Stdio::null())
144 .stdin(Stdio::null());
145 cmd.spawn()
146 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
147 Ok(())
148 }
149
150 #[cfg(windows)]
152 pub async fn start_daemon(&self) -> ServerManagerResult {
153 (self.start_hook)().await;
154 use std::os::windows::process::CommandExt;
155 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
156 self.write_pid_file()?;
157 let rt: Runtime = Runtime::new()?;
158 rt.block_on(async {
159 (self.server_hook)().await;
160 });
161 return Ok(());
162 }
163 let exe_path: PathBuf = std::env::current_exe()?;
164 let mut cmd: Command = Command::new(exe_path);
165 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
166 .stdout(Stdio::null())
167 .stderr(Stdio::null())
168 .stdin(Stdio::null())
169 .creation_flags(0x00000008);
170 cmd.spawn()
171 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
172 Ok(())
173 }
174
175 fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
181 let pid_str: String = fs::read_to_string(&self.pid_file)?;
182 let pid: i32 = pid_str.trim().parse::<i32>()?;
183 Ok(pid)
184 }
185
186 fn write_pid_file(&self) -> ServerManagerResult {
192 if let Some(parent) = Path::new(&self.pid_file).parent() {
193 fs::create_dir_all(parent)?;
194 }
195 let pid: u32 = id();
196 fs::write(&self.pid_file, pid.to_string())?;
197 Ok(())
198 }
199
200 #[cfg(not(windows))]
210 fn kill_process(&self, pid: i32) -> ServerManagerResult {
211 let result: Result<Output, std::io::Error> = Command::new("kill")
212 .arg("-TERM")
213 .arg(pid.to_string())
214 .output();
215 match result {
216 Ok(output) if output.status.success() => Ok(()),
217 Ok(output) => Err(format!(
218 "Failed to kill process with pid: {}, error: {}",
219 pid,
220 String::from_utf8_lossy(&output.stderr)
221 )
222 .into()),
223 Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
224 }
225 }
226
227 #[cfg(windows)]
237 fn kill_process(&self, pid: i32) -> ServerManagerResult {
238 use std::ffi::c_void;
239 type Dword = u32;
240 type Bool = i32;
241 type Handle = *mut c_void;
242 type Uint = u32;
243 const PROCESS_TERMINATE: Dword = 0x0001;
244 const PROCESS_ALL_ACCESS: Dword = 0x1F0FFF;
245 unsafe extern "system" {
246 fn OpenProcess(
247 dwDesiredAccess: Dword,
248 bInheritHandle: Bool,
249 dwProcessId: Dword,
250 ) -> Handle;
251 fn TerminateProcess(hProcess: Handle, uExitCode: Uint) -> Bool;
252 fn CloseHandle(hObject: Handle) -> Bool;
253 fn GetLastError() -> Dword;
254 }
255 let process_id: Dword = pid as Dword;
256 let mut process_handle: Handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
257 if process_handle.is_null() {
258 process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
259 }
260 if process_handle.is_null() {
261 let error_code = unsafe { GetLastError() };
262 return Err(format!(
263 "Failed to open process with pid: {pid}. Error code: {error_code}"
264 )
265 .into());
266 }
267 let terminate_result: Bool = unsafe { TerminateProcess(process_handle, 1) };
268 if terminate_result == 0 {
269 let error_code = unsafe { GetLastError() };
270 unsafe {
271 CloseHandle(process_handle);
272 }
273 return Err(format!(
274 "Failed to terminate process with pid: {pid}. Error code: {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}