use clap::{CommandFactory, FromArgMatches, Subcommand};
use log::{error, info};
use shell_compose::*;
use std::process::{self, Child, ExitCode, Stdio};
use std::time::Duration;
use std::{env, thread};
struct DispatcherProc(Child);
impl DispatcherProc {
fn spawn() -> Result<DispatcherProc, DispatcherError> {
let mut exe = env::current_exe().unwrap();
exe.set_file_name(
exe.file_name()
.unwrap()
.to_os_string()
.into_string()
.unwrap()
.replace("compose", "composed"),
);
let mut proc = process::Command::new(exe);
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x08000000;
proc.creation_flags(CREATE_NO_WINDOW);
}
if env::var("RUST_LOG").unwrap_or("".to_string()) == "debug" {
proc.env("RUST_LOG", "debug")
} else {
proc.stdout(Stdio::null()).stderr(Stdio::null())
};
let child = proc
.spawn()
.map_err(DispatcherError::DispatcherSpawnError)?;
Ok(DispatcherProc(child))
}
fn wait(&self, max_ms: u64) -> Result<(), DispatcherError> {
let mut wait_ms = 0;
while IpcStream::check_connection().is_err() {
if wait_ms >= max_ms {
return Err(DispatcherError::DispatcherSpawnTimeoutError);
}
thread::sleep(Duration::from_millis(50));
wait_ms += 50;
}
Ok(())
}
fn kill(mut self) -> Result<(), DispatcherError> {
self.0.kill().map_err(DispatcherError::KillError)?;
self.0.wait().map_err(DispatcherError::KillError)?;
Ok(())
}
}
fn cli() -> Result<(), DispatcherError> {
let cli = Cli::command();
let cli = ExecCommand::augment_subcommands(cli);
let cli = CliCommand::augment_subcommands(cli);
let mut cli = cli.about(env!("CARGO_PKG_DESCRIPTION")); let matches = cli.clone().get_matches();
let exec_command = ExecCommand::from_arg_matches(&matches);
let cli_command = CliCommand::from_arg_matches(&matches);
if exec_command.is_err() && cli_command.is_err() {
cli.print_help().ok();
return Ok(());
}
init_cli_logger();
if IpcStream::check_connection().is_err() {
if matches!(cli_command, Ok(CliCommand::Exit)) {
return Ok(());
}
info!(target: "dispatcher", "Starting background process");
let dispatcher = DispatcherProc::spawn()?;
if let Err(e) = dispatcher.wait(2000) {
dispatcher.kill()?;
return Err(e);
}
}
let mut stream = IpcStream::connect("cli")?;
let msg: Message = exec_command
.map(Into::into)
.or_else(|_| cli_command.map(Into::into))?;
stream.send_message(&msg)?;
if matches!(msg, Message::CliCommand(CliCommand::Exit)) {
return Ok(());
}
let formatter = Formatter::default();
loop {
let response = stream.receive_message();
match response {
Ok(Message::Connect) => {}
Ok(Message::Ok) => {
match msg {
Message::ExecCommand(_, _) | Message::CliCommand(CliCommand::Stop { .. }) => {
info!(target: "dispatcher", "Command successful");
}
_ => {}
}
return Ok(());
}
Ok(Message::JobsStarted(job_ids)) => {
match job_ids.len() {
0 => error!(target: "dispatcher", "No jobs started (services running)"),
1 => {
info!(target: "dispatcher", "Job {} started", job_ids.first().unwrap_or(&0))
}
_ => {
info!(target: "dispatcher", "Jobs {} started", job_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>().join(", "))
}
}
return Ok(());
}
Ok(Message::Err(msg)) => {
error!(target: "dispatcher", "{msg}");
return Ok(());
}
Ok(Message::PsInfo(proc_infos)) => {
proc_info_table(&proc_infos);
return Ok(());
}
Ok(Message::JobInfo(job_infos)) => {
job_info_table(&job_infos);
return Ok(());
}
Ok(Message::LogLine(log_line)) => {
log_line.log(&formatter);
}
Err(e) => return Err(e.into()),
_ => return Err(DispatcherError::UnexpectedMessageError),
}
}
}
fn main() -> ExitCode {
if let Err(e) = cli() {
error!(target: "dispatcher", "{e}");
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}