mod defs;
use clap::Parser;
use defs::{ChrootCommands, Cli, Commands, DaemonCommands, NamespaceCommands, TickCommands};
use crate::chroot::{init_chroot, list_chroots};
use crate::daemon::{self};
use crate::home::UnifierHome;
use crate::namespace;
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::{
response_found, response_messages, response_ok, response_uuid, response_value, Client, Request,
};
pub fn run() -> Result<()> {
let cli = Cli::parse();
let home = UnifierHome::resolve(cli.home, cli.chroot)?;
let ns = cli.namespace;
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(())
}
Commands::Namespace(cmd) => {
home.ensure()?;
dispatch_namespace(&home, cmd, ns.as_deref())
}
cmd => {
home.ensure()?;
if cli.no_daemon {
dispatch_data(&home, cmd, ns.as_deref())
} else {
#[cfg(unix)]
{
daemon::ensure_running(&home)?;
dispatch_via_daemon(&home, cmd, ns.as_deref())
}
#[cfg(not(unix))]
{
dispatch_data(&home, cmd, ns.as_deref())
}
}
}
}
}
fn dispatch_namespace(
home: &UnifierHome,
cmd: NamespaceCommands,
override_ns: Option<&str>,
) -> Result<()> {
match cmd {
NamespaceCommands::Set { name } => {
namespace::set(home, &name)?;
println!("{name}");
Ok(())
}
NamespaceCommands::Get => match namespace::effective(home, override_ns)? {
Some(name) => {
println!("{name}");
Ok(())
}
None => Err(crate::Error::msg("no namespace")),
},
NamespaceCommands::Clear => {
if namespace::clear(home)? {
Ok(())
} else {
Err(crate::Error::msg("no namespace"))
}
}
}
}
fn qualify(home: &UnifierHome, ns: Option<&str>, key: String) -> Result<String> {
namespace::qualify_key(home, ns, &key)
}
fn dispatch_data(home: &UnifierHome, cmd: Commands, ns: Option<&str>) -> Result<()> {
match cmd {
Commands::Put { key, value } => {
let key = qualify(home, ns, key)?;
put_key(home, &key, &value)?;
Ok(())
}
Commands::Get { key } => {
let key = qualify(home, ns, key)?;
match get_key(home, &key)? {
Some(v) => {
println!("{v}");
Ok(())
}
None => Err(crate::Error::msg(format!("key not found: {key}"))),
}
}
Commands::Del { key } => {
let key = qualify(home, ns, 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(_) | Commands::Namespace(_) => {
unreachable!("handled in run()")
}
}
}
#[cfg(unix)]
fn dispatch_via_daemon(home: &UnifierHome, cmd: Commands, ns: Option<&str>) -> Result<()> {
let mut client = Client::connect(home)?;
match cmd {
Commands::Put { key, value } => {
let key = qualify(home, ns, key)?;
response_ok(client.request(Request::Put { key, value })?)?;
Ok(())
}
Commands::Get { key } => {
let key = qualify(home, ns, 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 } => {
let key = qualify(home, ns, 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(home, &mut client, cmd, ns),
Commands::Daemon(_) | Commands::Chroot(_) | Commands::Namespace(_) => {
unreachable!("handled in run()")
}
}
}
#[cfg(unix)]
fn dispatch_tick_via_daemon(
home: &UnifierHome,
client: &mut Client,
cmd: TickCommands,
ns: Option<&str>,
) -> 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 } => {
let key = qualify(home, ns, key)?;
response_ok(client.request(Request::TickLock { key })?)?;
Ok(())
}
TickCommands::Unlock { key } => {
let key = qualify(home, ns, 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!("---");
}
}
}