use std::collections::HashMap;
use crate::daemon::ipc::{self, Request};
fn ok(msg: &str) {
println!(" ✓ {msg}");
}
fn fail(msg: &str) -> ! {
eprintln!(" ✗ {msg}");
std::process::exit(1);
}
fn send_or_fail(req: &Request) -> crate::daemon::ipc::Response {
match ipc::send(req) {
Ok(resp) => resp,
Err(e) => fail(&e.to_string()),
}
}
pub fn start(
command: String,
name: Option<String>,
cwd: Option<String>,
env_pairs: Vec<String>,
max_restarts: u32,
) {
let name = name.unwrap_or_else(|| {
command
.split_whitespace()
.next()
.unwrap_or("process")
.to_string()
});
let cwd = cwd.unwrap_or_else(|| {
std::env::current_dir()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|_| "/".to_string())
});
let env: HashMap<String, String> = env_pairs
.into_iter()
.filter_map(|pair| {
let (k, v) = pair.split_once('=')?;
Some((k.to_string(), v.to_string()))
})
.collect();
let resp = send_or_fail(&Request::Start {
name,
command,
cwd,
env,
max_restarts,
});
if resp.success {
ok(&resp.message);
} else {
fail(&resp.message);
}
}
pub fn stop(name_or_id: String) {
let resp = send_or_fail(&Request::Stop {
name_or_id: name_or_id.clone(),
});
if resp.success {
ok(&resp.message);
} else {
fail(&resp.message);
}
}
pub fn restart(name_or_id: String) {
let resp = send_or_fail(&Request::Restart {
name_or_id: name_or_id.clone(),
});
if resp.success {
ok(&resp.message);
} else {
fail(&resp.message);
}
}
pub fn delete(name_or_id: String) {
let resp = send_or_fail(&Request::Delete {
name_or_id: name_or_id.clone(),
});
if resp.success {
ok(&resp.message);
} else {
fail(&resp.message);
}
}
pub fn save() {
let resp = send_or_fail(&Request::Save);
if resp.success {
ok(&resp.message);
} else {
fail(&resp.message);
}
}
pub fn resurrect() {
let resp = send_or_fail(&Request::Resurrect);
if resp.success {
ok(&resp.message);
} else {
fail(&resp.message);
}
}
pub fn show(name_or_id: String) {
let resp = send_or_fail(&Request::Show {
name_or_id: name_or_id.clone(),
});
if !resp.success {
fail(&resp.message);
}
let Some(p) = resp.process else {
fail("daemon returned no process data");
};
let status_str = format!("{}", p.status);
let uptime = if p.pid > 0 {
let secs = (chrono::Utc::now() - p.started_at).num_seconds().max(0) as u64;
format_uptime(secs)
} else {
"–".to_string()
};
println!();
println!(" {:<16} {}", "id:", p.id);
println!(" {:<16} {}", "name:", p.name);
println!(" {:<16} {status_str}", "status:");
println!(
" {:<16} {}",
"pid:",
if p.pid > 0 {
p.pid.to_string()
} else {
"–".to_string()
}
);
println!(" {:<16} {}", "restarts:", p.restarts);
println!(" {:<16} {}", "max restarts:", p.max_restarts);
println!(" {:<16} {uptime}", "uptime:");
println!(" {:<16} {}", "command:", p.command);
println!(" {:<16} {}", "cwd:", p.cwd.display());
println!(" {:<16} {}", "stdout log:", p.log_out.display());
println!(" {:<16} {}", "stderr log:", p.log_err.display());
if !p.env.is_empty() {
println!(" {:<16}", "env:");
let mut keys: Vec<&String> = p.env.keys().collect();
keys.sort();
for k in keys {
println!(" {}={}", k, p.env[k]);
}
}
println!();
}
pub fn logs(name_or_id: String, lines: usize) {
let resp = send_or_fail(&Request::Logs { name_or_id, lines });
if !resp.success {
fail(&resp.message);
}
if let Some(content) = resp.logs {
print!("{content}");
}
}
fn format_uptime(secs: u64) -> String {
if secs < 60 {
format!("{secs}s")
} else if secs < 3600 {
format!("{}m {}s", secs / 60, secs % 60)
} else if secs < 86_400 {
format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
} else {
format!("{}d {}h", secs / 86_400, (secs % 86_400) / 3600)
}
}