use super::*;
impl Default for ServerManager {
#[inline(always)]
fn default() -> Self {
let empty_hook: ServerManagerHook = Arc::new(|| Box::pin(async {}));
Self {
pid_file: Default::default(),
stop_hook: empty_hook.clone(),
server_hook: empty_hook.clone(),
start_hook: empty_hook,
}
}
}
impl ServerManager {
#[inline(always)]
pub fn new() -> Self {
Self::default()
}
pub async fn start(&self) {
(self.get_start_hook())().await;
if let Err(e) = self.write_pid_file() {
eprintln!("Failed to write pid file: {e}");
return;
}
(self.get_server_hook())().await;
}
pub async fn stop(&self) -> ServerManagerResult {
(self.get_stop_hook())().await;
let pid: i32 = self.read_pid_file()?;
self.kill_process(pid)
}
#[cfg(not(windows))]
pub async fn start_daemon(&self) -> ServerManagerResult {
(self.get_start_hook())().await;
if std::env::var(RUNNING_AS_DAEMON).is_ok() {
self.write_pid_file()?;
let rt: Runtime = Runtime::new()?;
rt.block_on(async {
(self.get_server_hook())().await;
});
return Ok(());
}
let exe_path: PathBuf = std::env::current_exe()?;
let mut cmd: Command = Command::new(exe_path);
cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(Stdio::null());
cmd.spawn()
.map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
Ok(())
}
#[cfg(windows)]
pub async fn start_daemon(&self) -> ServerManagerResult {
(self.get_start_hook())().await;
if std::env::var(RUNNING_AS_DAEMON).is_ok() {
self.write_pid_file()?;
let rt: Runtime = Runtime::new()?;
rt.block_on(async {
(self.get_server_hook())().await;
});
return Ok(());
}
let exe_path: PathBuf = std::env::current_exe()?;
let mut cmd: Command = Command::new(exe_path);
cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(Stdio::null())
.creation_flags(0x00000008);
cmd.spawn()
.map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
Ok(())
}
fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
let pid_str: String = fs::read_to_string(self.get_pid_file())?;
let pid: i32 = pid_str.trim().parse::<i32>()?;
Ok(pid)
}
fn write_pid_file(&self) -> ServerManagerResult {
if let Some(parent) = Path::new(self.get_pid_file()).parent() {
fs::create_dir_all(parent)?;
}
let pid: u32 = id();
fs::write(self.get_pid_file(), pid.to_string())?;
Ok(())
}
#[cfg(not(windows))]
fn kill_process(&self, pid: i32) -> ServerManagerResult {
match Command::new(KILL)
.arg(KILL_SIGNAL)
.arg(pid.to_string())
.output()
{
Ok(output) if output.status.success() => Ok(()),
Ok(output) => Err(format!(
"Failed to kill process with pid: {}, error: {}",
pid,
String::from_utf8_lossy(&output.stderr)
)
.into()),
Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
}
}
#[cfg(windows)]
fn kill_process(&self, pid: i32) -> ServerManagerResult {
unsafe extern "system" {
fn OpenProcess(
dwDesiredAccess: u32,
bInheritHandle: i32,
dwProcessId: u32,
) -> *mut c_void;
fn TerminateProcess(hProcess: *mut c_void, uExitCode: u32) -> i32;
fn CloseHandle(hObject: *mut c_void) -> i32;
fn GetLastError() -> u32;
}
let process_id: u32 = pid as u32;
let mut process_handle: *mut c_void = unsafe { OpenProcess(0x0001, 0, process_id) };
if process_handle.is_null() {
process_handle = unsafe { OpenProcess(0x1F0FFF, 0, process_id) };
}
if process_handle.is_null() {
let error_code: u32 = unsafe { GetLastError() };
return Err(format!(
"Failed to open process with pid: {pid}. Error code: {error_code}"
)
.into());
}
let terminate_result: i32 = unsafe { TerminateProcess(process_handle, 1) };
if terminate_result == 0 {
let error_code: u32 = unsafe { GetLastError() };
unsafe {
CloseHandle(process_handle);
}
return Err(format!(
"Failed to terminate process with pid: {pid}. Error code: {error_code}"
)
.into());
}
unsafe {
CloseHandle(process_handle);
}
Ok(())
}
async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
(self.get_start_hook())().await;
let cargo_watch_installed: Output = Command::new(CARGO)
.arg(INSTALL)
.args(CARGO_WATCH_INSTALL_LIST_ARGS)
.output()?;
if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains(CARGO_WATCH) {
eprintln!("{CARGO_WATCH_ABSENT_MESSAGE}");
let install_status: ExitStatus = Command::new(CARGO)
.args(CARGO_INSTALL_ARGS)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
.wait()?;
if !install_status.success() {
return Err(CARGO_WATCH_INSTALL_FAILED_MESSAGE.into());
}
eprintln!("{CARGO_WATCH_INSTALLED_MESSAGE}");
}
let mut command: Command = Command::new(CARGO_WATCH);
command
.args(run_args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(Stdio::inherit());
let mut child: Child = command
.spawn()
.map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
if wait {
child
.wait()
.map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
}
exit(0);
}
pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
self.run_with_cargo_watch(run_args, false).await
}
pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
self.run_with_cargo_watch(run_args, true).await
}
}