use std::io::{BufRead, BufReader, Read};
use std::process::Stdio;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use clap::{Args, Subcommand};
use crate::contexts::daemon::application::lifecycle::{self, Port};
const DAEMON_INNER_ENV: &str = "MERGE_READY_DAEMON_INNER";
const START_TIMEOUT_SECS: u64 = 2;
#[derive(Subcommand, Clone, Copy)]
pub enum DaemonCommand {
Start,
Stop,
Status,
}
#[derive(Args, Clone, Copy)]
pub struct DaemonArgs {
#[command(subcommand)]
pub subcommand: DaemonCommand,
}
pub fn run(subcommand: DaemonCommand, port: &impl Port) {
match subcommand {
DaemonCommand::Start => start(port),
DaemonCommand::Stop => stop(port),
DaemonCommand::Status => status(port),
}
}
fn start(port: &impl Port) {
if std::env::var(DAEMON_INNER_ENV).is_ok() {
lifecycle::start(port);
return;
}
let Ok(exe) = std::env::current_exe() else {
eprintln!("merge-ready: failed to locate executable");
std::process::exit(1);
};
let mut child = match std::process::Command::new(exe)
.args(["daemon", "start"])
.env(DAEMON_INNER_ENV, "1")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Ok(c) => c,
Err(e) => {
eprintln!("merge-ready: failed to spawn daemon: {e}");
std::process::exit(1);
}
};
let Some(stdout) = child.stdout.take() else {
let _ = child.kill();
eprintln!("merge-ready: failed to capture daemon stdout");
std::process::exit(1);
};
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut line = String::new();
let mut reader = BufReader::new(stdout);
let _ = reader.read_line(&mut line);
let _ = tx.send(line);
});
let deadline = Instant::now() + Duration::from_secs(START_TIMEOUT_SECS);
loop {
if let Ok(Some(status)) = child.try_wait() {
if let Some(mut err) = child.stderr.take() {
let mut buf = String::new();
let _ = err.read_to_string(&mut buf);
if !buf.is_empty() {
eprint!("{buf}");
}
}
let code = if status.success() {
1
} else {
status.code().unwrap_or(1)
};
std::process::exit(code);
}
if matches!(rx.try_recv().ok().as_deref(), Some("ready\n")) {
println!("daemon started");
return;
}
if Instant::now() >= deadline {
let _ = child.kill();
eprintln!("merge-ready: daemon did not start within {START_TIMEOUT_SECS}s");
std::process::exit(1);
}
std::thread::sleep(Duration::from_millis(10));
}
}
fn stop(port: &impl Port) {
if lifecycle::stop(port) {
println!("daemon stopped");
} else {
eprintln!("daemon is not running");
}
}
fn status(port: &impl Port) {
match lifecycle::get_status(port) {
Some(s) => {
println!(
"running pid={} entries={} uptime={}s version={}",
s.pid, s.entries, s.uptime_secs, s.version
);
}
None => println!("not running"),
}
}