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 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, func: 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(func()));
49 self
50 }
51
52 pub fn set_server_hook<F, Fut>(&mut self, func: 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(func()));
63 self
64 }
65
66 pub fn set_stop_hook<F, Fut>(&mut self, func: 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(func()));
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) -> &ServerManagerHook {
87 &self.start_hook
88 }
89
90 pub fn get_server_hook(&self) -> &ServerManagerHook {
92 &self.server_hook
93 }
94
95 pub fn get_stop_hook(&self) -> &ServerManagerHook {
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}