use crate::{Config, WallSwitchError, WallSwitchResult};
use std::{
io::Write,
process::{Command, Output},
thread,
time::Duration,
};
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System, UpdateKind};
pub fn is_process_running(process_name: &str) -> bool {
let mut sys = System::new();
sys.refresh_processes_specifics(
ProcessesToUpdate::All,
true, ProcessRefreshKind::nothing().with_exe(UpdateKind::Always),
);
sys.processes().values().any(|process| {
process.exe().is_some_and(|exe_path| {
exe_path
.file_name()
.is_some_and(|name| name.to_string_lossy() == process_name)
})
})
}
pub struct DaemonConfig {
pub name: &'static str,
pub spawn_cmd: &'static str,
pub kill_cmd: Option<&'static str>,
}
pub struct DaemonManager;
impl DaemonManager {
pub fn ensure_running<F>(
config: &Config,
daemon: &DaemonConfig,
spawn: F,
) -> WallSwitchResult<()>
where
F: FnOnce() -> WallSwitchResult<()>,
{
if is_process_running(daemon.name) {
return Ok(());
}
if config.dry_run {
println!(
"[DRY-RUN] {name} is down; would perform clean start.",
name = daemon.name
);
return Ok(());
}
if config.verbose {
println!(
"{name} is down. Performing clean start...",
name = daemon.name
);
}
if let Some(kill_name) = daemon.kill_cmd {
let _ = Command::new("killall").arg(kill_name).output();
thread::sleep(Duration::from_millis(150));
}
spawn()?;
let max_wait = 5.0;
let step = 0.1;
let mut elapsed = 0.0;
while elapsed < max_wait {
if is_process_running(daemon.name) {
if config.verbose {
println!("\n{name} successfully initialized.", name = daemon.name);
}
return Ok(());
}
if config.verbose {
print!(
"\rWait to initialize {name}. Time: {elapsed:0.1}/{max_wait:0.1}",
name = daemon.name
);
std::io::stdout().flush().ok();
}
std::thread::sleep(std::time::Duration::from_secs_f32(step));
elapsed += step;
}
if config.verbose {
println!();
}
Err(WallSwitchError::UnableToFind(format!(
"{name} daemon failed to respond after initialization.",
name = daemon.name
)))
}
}
pub trait CommandExt {
fn run_with_config(&mut self, config: &Config, context: &str) -> WallSwitchResult<Output>;
}
impl CommandExt for Command {
fn run_with_config(&mut self, config: &Config, context: &str) -> WallSwitchResult<Output> {
let output = self.output().map_err(|e| {
eprintln!("Failed to execute command: {:?}", self.get_program());
WallSwitchError::Io(e)
})?;
let program = self.get_program();
let arguments: Vec<_> = self.get_args().collect();
if !output.status.success() || config.verbose {
println!("\nprogram: {program:?}");
println!("arguments: {arguments:#?}");
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.trim().is_empty() {
println!("stdout:'{}'\n", stdout.trim());
}
}
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let status = output.status;
eprintln!("{context} status: {status}");
eprintln!("{context} stderr: {stderr}");
return Err(WallSwitchError::CommandFailed {
program: format!("{:?}", program),
status: status.to_string(),
stderr: stderr.to_string(),
});
}
Ok(output)
}
}
#[cfg(test)]
mod tests_common_daemon {
use super::*;
#[test]
fn test_is_process_running_stale_name() {
assert!(!is_process_running("non_existent_daemon_xyz_123"));
}
}