mod defs;
use clap::Parser;
use defs::{ChrootCommands, Cli, Commands, DaemonCommands, TickCommands};
use crate::chroot::{init_chroot, list_chroots};
use crate::daemon::{self};
use crate::home::UnifierHome;
use crate::postbox::{
ack, delete_key, get_key, list_dir, poll_cron, poll_mailbox, post_cron, put_key, send_from,
Message,
};
use crate::Result;
#[cfg(unix)]
use crate::daemon::{Client, Request, response_found, response_messages, response_ok, response_uuid, response_value};
pub fn run() -> Result<()> {
let cli = Cli::parse();
let home = UnifierHome::resolve(cli.home, cli.chroot)?;
match &cli.cmd {
Commands::Daemon(DaemonCommands::Start) => {
daemon::start(&home, false)?;
println!("daemon started");
Ok(())
}
Commands::Daemon(DaemonCommands::Run) => {
#[cfg(unix)]
{
daemon::run_server(home)
}
#[cfg(not(unix))]
{
Err(crate::Error::msg("hot daemon requires a Unix platform"))
}
}
Commands::Daemon(DaemonCommands::Stop) => {
daemon::stop(&home)?;
println!("daemon stopped");
Ok(())
}
Commands::Daemon(DaemonCommands::Status) => daemon::status(&home),
Commands::Daemon(DaemonCommands::Flush) => daemon::flush(&home),
Commands::Daemon(DaemonCommands::Watch) => {
#[cfg(unix)]
{
daemon::ensure_running(&home)?;
daemon::watch(&home)
}
#[cfg(not(unix))]
{
Err(crate::Error::msg("hot daemon requires a Unix platform"))
}
}
Commands::Chroot(ChrootCommands::Init { name }) => {
init_chroot(home.global_path(), name)?;
println!(
"chroot initialized: {}",
home.global_path().join("chroots").join(name).display()
);
Ok(())
}
Commands::Chroot(ChrootCommands::List) => {
for name in list_chroots(home.global_path())? {
println!("{name}");
}
Ok(())
}
_ => {
home.ensure()?;
if cli.no_daemon {
dispatch_data(&home, cli.cmd)
} else {
#[cfg(unix)]
{
daemon::ensure_running(&home)?;
dispatch_via_daemon(&home, cli.cmd)
}
#[cfg(not(unix))]
{
dispatch_data(&home, cli.cmd)
}
}
}
}
}
fn dispatch_data(home: &UnifierHome, cmd: Commands) -> Result<()> {
match cmd {
Commands::Put { key, value } => {
put_key(home, &key, &value)?;
Ok(())
}
Commands::Get { key } => match get_key(home, &key)? {
Some(v) => {
println!("{v}");
Ok(())
}
None => Err(crate::Error::msg(format!("key not found: {key}"))),
},
Commands::Del { key } => {
if delete_key(home, &key)? {
Ok(())
} else {
Err(crate::Error::msg(format!("key not found: {key}")))
}
}
Commands::Send {
from,
recipient,
message,
} => {
let from = from.unwrap_or_else(|| crate::envelope::DEFAULT_SENDER.to_string());
let id = send_from(home, &from, &recipient, &message)?;
println!("{}", id.hyphenated());
Ok(())
}
Commands::Cron { schedule, message } => {
let id = post_cron(home, &schedule, &message)?;
println!("{}", id.hyphenated());
Ok(())
}
Commands::Poll { recipient, ack: do_ack } => {
let messages = poll_mailbox(home, &recipient)?;
print_messages(&messages);
if do_ack {
for msg in &messages {
ack(home, &msg.id.hyphenated().to_string())?;
}
}
Ok(())
}
Commands::PollCron { ack: do_ack } => {
let messages = poll_cron(home)?;
print_messages(&messages);
if do_ack {
for msg in &messages {
ack(home, &msg.id.hyphenated().to_string())?;
}
}
Ok(())
}
Commands::List { path } => {
let messages = list_dir(home, &path)?;
print_messages(&messages);
Ok(())
}
Commands::Ack { id_or_path } => {
if ack(home, &id_or_path)? {
Ok(())
} else {
Err(crate::Error::msg(format!("message not found: {id_or_path}")))
}
}
Commands::Root => {
println!("{}", home.path().display());
Ok(())
}
Commands::Event { .. } | Commands::Message { .. } | Commands::Tick(_) => {
Err(crate::Error::msg(
"event, message, and tick commands require the hot daemon; omit --no-daemon",
))
}
Commands::Daemon(_) | Commands::Chroot(_) => unreachable!("handled in run()"),
}
}
#[cfg(unix)]
fn dispatch_via_daemon(home: &UnifierHome, cmd: Commands) -> Result<()> {
let mut client = Client::connect(home)?;
match cmd {
Commands::Put { key, value } => {
response_ok(client.request(Request::Put { key, value })?)?;
Ok(())
}
Commands::Get { key } => match response_value(client.request(Request::Get { key })?)? {
Some(v) => {
println!("{v}");
Ok(())
}
None => Err(crate::Error::msg("key not found")),
},
Commands::Del { key } => {
response_ok(client.request(Request::Del { key })?)?;
Ok(())
}
Commands::Send {
from,
recipient,
message,
} => {
let id = response_uuid(client.request(Request::Send {
from,
recipient,
message,
})?)?;
println!("{}", id.hyphenated());
Ok(())
}
Commands::Cron { schedule, message } => {
let id = response_uuid(client.request(Request::Cron { schedule, message })?)?;
println!("{}", id.hyphenated());
Ok(())
}
Commands::Poll { recipient, ack: do_ack } => {
let messages = response_messages(client.request(Request::Poll { recipient })?)?;
print_messages(&messages);
if do_ack {
for msg in &messages {
let found = response_found(
client.request(Request::Ack {
id_or_path: msg.id.hyphenated().to_string(),
})?,
)?;
if !found {
return Err(crate::Error::msg(format!(
"message not found: {}",
msg.id.hyphenated()
)));
}
}
}
Ok(())
}
Commands::PollCron { ack: do_ack } => {
let messages = response_messages(client.request(Request::PollCron)?)?;
print_messages(&messages);
if do_ack {
for msg in &messages {
response_found(client.request(Request::Ack {
id_or_path: msg.id.hyphenated().to_string(),
})?)?;
}
}
Ok(())
}
Commands::List { path } => {
let messages = response_messages(client.request(Request::List { path })?)?;
print_messages(&messages);
Ok(())
}
Commands::Ack { id_or_path } => {
if response_found(client.request(Request::Ack { id_or_path })?)? {
Ok(())
} else {
Err(crate::Error::msg("message not found"))
}
}
Commands::Root => {
println!("{}", home.path().display());
Ok(())
}
Commands::Event { payload } => {
let id = response_uuid(client.request(Request::Event { payload })?)?;
println!("{}", id.hyphenated());
Ok(())
}
Commands::Message { from, recipient, payload } => {
let id = response_uuid(client.request(Request::AgentMessage {
from,
to: recipient,
payload,
})?)?;
println!("{}", id.hyphenated());
Ok(())
}
Commands::Tick(cmd) => dispatch_tick_via_daemon(&mut client, cmd),
Commands::Daemon(_) | Commands::Chroot(_) => unreachable!("handled in run()"),
}
}
#[cfg(unix)]
fn dispatch_tick_via_daemon(client: &mut Client, cmd: TickCommands) -> Result<()> {
use crate::daemon::{response_found, response_ok, Response};
match cmd {
TickCommands::Start { label } => {
let resp = client.request(Request::TickStart { label })?;
match resp {
Response::Ok {
tick: Some(t), ..
} => {
println!("tick {t} started");
Ok(())
}
Response::Ok {
queued: Some(pos), ..
} => {
println!("tick start queued at position {pos}");
Ok(())
}
Response::Err { error } => Err(crate::Error::msg(error)),
_ => Err(crate::Error::msg("unexpected tick start response")),
}
}
TickCommands::End => {
let resp = client.request(Request::TickEnd)?;
match resp {
Response::Ok {
tick: Some(t), ..
} => {
println!("tick {t} committed");
Ok(())
}
Response::Err { error } => Err(crate::Error::msg(error)),
_ => Err(crate::Error::msg("unexpected tick end response")),
}
}
TickCommands::Status => {
let resp = client.request(Request::TickStatus)?;
match resp {
Response::Ok { value: Some(v), .. } => {
println!("{v}");
Ok(())
}
Response::Err { error } => Err(crate::Error::msg(error)),
_ => Err(crate::Error::msg("unexpected tick status response")),
}
}
TickCommands::Lock { key } => {
response_ok(client.request(Request::TickLock { key })?)?;
Ok(())
}
TickCommands::Unlock { key } => {
if response_found(client.request(Request::TickUnlock { key })?)? {
Ok(())
} else {
Err(crate::Error::msg("lock not held"))
}
}
}
}
fn print_messages(messages: &[Message]) {
for (i, msg) in messages.iter().enumerate() {
println!("{} {}", msg.id.hyphenated(), msg.path.display());
println!("{}", msg.body);
if i + 1 < messages.len() {
println!("---");
}
}
}