use crate::*;
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()
}
#[inline(always)]
pub fn set_pid_file<P: ToString>(&mut self, pid_file: P) -> &mut Self {
self.pid_file = pid_file.to_string();
self
}
#[inline(always)]
pub fn set_start_hook<F, Fut>(&mut self, func: F) -> &mut Self
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
self.start_hook = Arc::new(move || Box::pin(func()));
self
}
#[inline(always)]
pub fn set_server_hook<F, Fut>(&mut self, func: F) -> &mut Self
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
self.server_hook = Arc::new(move || Box::pin(func()));
self
}
#[inline(always)]
pub fn set_stop_hook<F, Fut>(&mut self, func: F) -> &mut Self
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
self.stop_hook = Arc::new(move || Box::pin(func()));
self
}
#[inline(always)]
pub fn get_pid_file(&self) -> &str {
&self.pid_file
}
#[inline(always)]
pub fn get_start_hook(&self) -> &ServerManagerHook {
&self.start_hook
}
#[inline(always)]
pub fn get_server_hook(&self) -> &ServerManagerHook {
&self.server_hook
}
#[inline(always)]
pub fn get_stop_hook(&self) -> &ServerManagerHook {
&self.stop_hook
}
pub async fn start(&self) {
(self.start_hook)().await;
if let Err(e) = self.write_pid_file() {
eprintln!("Failed to write pid file: {e}");
return;
}
(self.server_hook)().await;
}
pub async fn stop(&self) -> ServerManagerResult {
(self.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.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.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(|e| Box::new(e) as Box<dyn std::error::Error>)?;
Ok(())
}
#[cfg(windows)]
pub async fn start_daemon(&self) -> ServerManagerResult {
(self.start_hook)().await;
use std::os::windows::process::CommandExt;
if std::env::var(RUNNING_AS_DAEMON).is_ok() {
self.write_pid_file()?;
let rt: Runtime = Runtime::new()?;
rt.block_on(async {
(self.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(|e| Box::new(e) 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.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.pid_file).parent() {
fs::create_dir_all(parent)?;
}
let pid: u32 = id();
fs::write(&self.pid_file, pid.to_string())?;
Ok(())
}
#[cfg(not(windows))]
fn kill_process(&self, pid: i32) -> ServerManagerResult {
let result: Result<Output, std::io::Error> = Command::new("kill")
.arg("-TERM")
.arg(pid.to_string())
.output();
match result {
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 {
use std::ffi::c_void;
type Dword = u32;
type Bool = i32;
type Handle = *mut c_void;
type Uint = u32;
const PROCESS_TERMINATE: Dword = 0x0001;
const PROCESS_ALL_ACCESS: Dword = 0x1F0FFF;
unsafe extern "system" {
fn OpenProcess(
dwDesiredAccess: Dword,
bInheritHandle: Bool,
dwProcessId: Dword,
) -> Handle;
fn TerminateProcess(hProcess: Handle, uExitCode: Uint) -> Bool;
fn CloseHandle(hObject: Handle) -> Bool;
fn GetLastError() -> Dword;
}
let process_id: Dword = pid as Dword;
let mut process_handle: Handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
if process_handle.is_null() {
process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
}
if process_handle.is_null() {
let error_code = unsafe { GetLastError() };
return Err(format!(
"Failed to open process with pid: {pid}. Error code: {error_code}"
)
.into());
}
let terminate_result: Bool = unsafe { TerminateProcess(process_handle, 1) };
if terminate_result == 0 {
let error_code = 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.start_hook)().await;
let cargo_watch_installed: Output = Command::new("cargo")
.arg("install")
.arg("--list")
.output()?;
if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
eprintln!("Cargo-watch not found. Attempting to install...");
let install_status: ExitStatus = Command::new("cargo")
.arg("install")
.arg("cargo-watch")
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
.wait()?;
if !install_status.success() {
return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
}
eprintln!("Cargo-watch installed successfully.");
}
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(|e| Box::new(e) as Box<dyn std::error::Error>)?;
if wait {
child
.wait()
.map_err(|e| Box::new(e) 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
}
}