1use super::*;
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(|error: Error| Box::new(error) 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(|error: Error| Box::new(error) 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 match Command::new("kill")
220 .arg("-TERM")
221 .arg(pid.to_string())
222 .output()
223 {
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 unsafe extern "system" {
247 fn OpenProcess(
248 dwDesiredAccess: u32,
249 bInheritHandle: i32,
250 dwProcessId: u32,
251 ) -> *mut c_void;
252 fn TerminateProcess(hProcess: *mut c_void, uExitCode: u32) -> i32;
253 fn CloseHandle(hObject: *mut c_void) -> i32;
254 fn GetLastError() -> u32;
255 }
256 let process_id: u32 = pid as u32;
257 let mut process_handle: *mut c_void = unsafe { OpenProcess(0x0001, 0, process_id) };
258 if process_handle.is_null() {
259 process_handle = unsafe { OpenProcess(0x1F0FFF, 0, process_id) };
260 }
261 if process_handle.is_null() {
262 let error_code = unsafe { GetLastError() };
263 return Err(format!(
264 "Failed to open process with pid: {pid}. Error code: {error_code}"
265 )
266 .into());
267 }
268 let terminate_result: i32 = unsafe { TerminateProcess(process_handle, 1) };
269 if terminate_result == 0 {
270 let error_code = unsafe { GetLastError() };
271 unsafe {
272 CloseHandle(process_handle);
273 }
274 return Err(format!(
275 "Failed to terminate process with pid: {pid}. Error code: {error_code}"
276 )
277 .into());
278 }
279 unsafe {
280 CloseHandle(process_handle);
281 }
282 Ok(())
283 }
284
285 async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
296 (self.start_hook)().await;
297 let cargo_watch_installed: Output = Command::new("cargo")
298 .arg("install")
299 .arg("--list")
300 .output()?;
301 if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
302 eprintln!("Cargo-watch not found. Attempting to install...");
303 let install_status: ExitStatus = Command::new("cargo")
304 .arg("install")
305 .arg("cargo-watch")
306 .stdout(Stdio::inherit())
307 .stderr(Stdio::inherit())
308 .spawn()?
309 .wait()?;
310 if !install_status.success() {
311 return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
312 }
313 eprintln!("Cargo-watch installed successfully.");
314 }
315 let mut command: Command = Command::new("cargo-watch");
316 command
317 .args(run_args)
318 .stdout(Stdio::inherit())
319 .stderr(Stdio::inherit())
320 .stdin(Stdio::inherit());
321 let mut child: Child = command
322 .spawn()
323 .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
324 if wait {
325 child
326 .wait()
327 .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
328 }
329 exit(0);
330 }
331
332 pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
344 self.run_with_cargo_watch(run_args, false).await
345 }
346
347 pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
359 self.run_with_cargo_watch(run_args, true).await
360 }
361}