Skip to main content

unifier/cli/
mod.rs

1//! CLI dispatch.
2
3mod defs;
4
5use clap::Parser;
6use defs::{ChrootCommands, Cli, Commands, DaemonCommands, TickCommands};
7
8use crate::chroot::{init_chroot, list_chroots};
9use crate::daemon::{self};
10use crate::home::UnifierHome;
11use crate::postbox::{
12    ack, delete_key, get_key, list_dir, poll_cron, poll_mailbox, post_cron, put_key, send_from,
13    Message,
14};
15use crate::Result;
16
17#[cfg(unix)]
18use crate::daemon::{Client, Request, response_found, response_messages, response_ok, response_uuid, response_value};
19
20pub fn run() -> Result<()> {
21    let cli = Cli::parse();
22    let home = UnifierHome::resolve(cli.home, cli.chroot)?;
23
24    match &cli.cmd {
25        Commands::Daemon(DaemonCommands::Start) => {
26            daemon::start(&home, false)?;
27            println!("daemon started");
28            Ok(())
29        }
30        Commands::Daemon(DaemonCommands::Run) => {
31            #[cfg(unix)]
32            {
33                daemon::run_server(home)
34            }
35            #[cfg(not(unix))]
36            {
37                Err(crate::Error::msg("hot daemon requires a Unix platform"))
38            }
39        }
40        Commands::Daemon(DaemonCommands::Stop) => {
41            daemon::stop(&home)?;
42            println!("daemon stopped");
43            Ok(())
44        }
45        Commands::Daemon(DaemonCommands::Status) => daemon::status(&home),
46        Commands::Daemon(DaemonCommands::Flush) => daemon::flush(&home),
47        Commands::Daemon(DaemonCommands::Watch) => {
48            #[cfg(unix)]
49            {
50                daemon::ensure_running(&home)?;
51                daemon::watch(&home)
52            }
53            #[cfg(not(unix))]
54            {
55                Err(crate::Error::msg("hot daemon requires a Unix platform"))
56            }
57        }
58        Commands::Chroot(ChrootCommands::Init { name }) => {
59            init_chroot(home.global_path(), name)?;
60            println!(
61                "chroot initialized: {}",
62                home.global_path().join("chroots").join(name).display()
63            );
64            Ok(())
65        }
66        Commands::Chroot(ChrootCommands::List) => {
67            for name in list_chroots(home.global_path())? {
68                println!("{name}");
69            }
70            Ok(())
71        }
72        _ => {
73            home.ensure()?;
74            if cli.no_daemon {
75                dispatch_data(&home, cli.cmd)
76            } else {
77                #[cfg(unix)]
78                {
79                    daemon::ensure_running(&home)?;
80                    dispatch_via_daemon(&home, cli.cmd)
81                }
82                #[cfg(not(unix))]
83                {
84                    dispatch_data(&home, cli.cmd)
85                }
86            }
87        }
88    }
89}
90
91fn dispatch_data(home: &UnifierHome, cmd: Commands) -> Result<()> {
92    match cmd {
93        Commands::Put { key, value } => {
94            put_key(home, &key, &value)?;
95            Ok(())
96        }
97        Commands::Get { key } => match get_key(home, &key)? {
98            Some(v) => {
99                println!("{v}");
100                Ok(())
101            }
102            None => Err(crate::Error::msg(format!("key not found: {key}"))),
103        },
104        Commands::Del { key } => {
105            if delete_key(home, &key)? {
106                Ok(())
107            } else {
108                Err(crate::Error::msg(format!("key not found: {key}")))
109            }
110        }
111        Commands::Send {
112            from,
113            recipient,
114            message,
115        } => {
116            let from = from.unwrap_or_else(|| crate::envelope::DEFAULT_SENDER.to_string());
117            let id = send_from(home, &from, &recipient, &message)?;
118            println!("{}", id.hyphenated());
119            Ok(())
120        }
121        Commands::Cron { schedule, message } => {
122            let id = post_cron(home, &schedule, &message)?;
123            println!("{}", id.hyphenated());
124            Ok(())
125        }
126        Commands::Poll { recipient, ack: do_ack } => {
127            let messages = poll_mailbox(home, &recipient)?;
128            print_messages(&messages);
129            if do_ack {
130                for msg in &messages {
131                    ack(home, &msg.id.hyphenated().to_string())?;
132                }
133            }
134            Ok(())
135        }
136        Commands::PollCron { ack: do_ack } => {
137            let messages = poll_cron(home)?;
138            print_messages(&messages);
139            if do_ack {
140                for msg in &messages {
141                    ack(home, &msg.id.hyphenated().to_string())?;
142                }
143            }
144            Ok(())
145        }
146        Commands::List { path } => {
147            let messages = list_dir(home, &path)?;
148            print_messages(&messages);
149            Ok(())
150        }
151        Commands::Ack { id_or_path } => {
152            if ack(home, &id_or_path)? {
153                Ok(())
154            } else {
155                Err(crate::Error::msg(format!("message not found: {id_or_path}")))
156            }
157        }
158        Commands::Root => {
159            println!("{}", home.path().display());
160            Ok(())
161        }
162        Commands::Event { .. } | Commands::Message { .. } | Commands::Tick(_) => {
163            Err(crate::Error::msg(
164                "event, message, and tick commands require the hot daemon; omit --no-daemon",
165            ))
166        }
167        Commands::Daemon(_) | Commands::Chroot(_) => unreachable!("handled in run()"),
168    }
169}
170
171#[cfg(unix)]
172fn dispatch_via_daemon(home: &UnifierHome, cmd: Commands) -> Result<()> {
173    let mut client = Client::connect(home)?;
174    match cmd {
175        Commands::Put { key, value } => {
176            response_ok(client.request(Request::Put { key, value })?)?;
177            Ok(())
178        }
179        Commands::Get { key } => match response_value(client.request(Request::Get { key })?)? {
180            Some(v) => {
181                println!("{v}");
182                Ok(())
183            }
184            None => Err(crate::Error::msg("key not found")),
185        },
186        Commands::Del { key } => {
187            response_ok(client.request(Request::Del { key })?)?;
188            Ok(())
189        }
190        Commands::Send {
191            from,
192            recipient,
193            message,
194        } => {
195            let id = response_uuid(client.request(Request::Send {
196                from,
197                recipient,
198                message,
199            })?)?;
200            println!("{}", id.hyphenated());
201            Ok(())
202        }
203        Commands::Cron { schedule, message } => {
204            let id = response_uuid(client.request(Request::Cron { schedule, message })?)?;
205            println!("{}", id.hyphenated());
206            Ok(())
207        }
208        Commands::Poll { recipient, ack: do_ack } => {
209            let messages = response_messages(client.request(Request::Poll { recipient })?)?;
210            print_messages(&messages);
211            if do_ack {
212                for msg in &messages {
213                    let found = response_found(
214                        client.request(Request::Ack {
215                            id_or_path: msg.id.hyphenated().to_string(),
216                        })?,
217                    )?;
218                    if !found {
219                        return Err(crate::Error::msg(format!(
220                            "message not found: {}",
221                            msg.id.hyphenated()
222                        )));
223                    }
224                }
225            }
226            Ok(())
227        }
228        Commands::PollCron { ack: do_ack } => {
229            let messages = response_messages(client.request(Request::PollCron)?)?;
230            print_messages(&messages);
231            if do_ack {
232                for msg in &messages {
233                    response_found(client.request(Request::Ack {
234                        id_or_path: msg.id.hyphenated().to_string(),
235                    })?)?;
236                }
237            }
238            Ok(())
239        }
240        Commands::List { path } => {
241            let messages = response_messages(client.request(Request::List { path })?)?;
242            print_messages(&messages);
243            Ok(())
244        }
245        Commands::Ack { id_or_path } => {
246            if response_found(client.request(Request::Ack { id_or_path })?)? {
247                Ok(())
248            } else {
249                Err(crate::Error::msg("message not found"))
250            }
251        }
252        Commands::Root => {
253            println!("{}", home.path().display());
254            Ok(())
255        }
256        Commands::Event { payload } => {
257            let id = response_uuid(client.request(Request::Event { payload })?)?;
258            println!("{}", id.hyphenated());
259            Ok(())
260        }
261        Commands::Message { from, recipient, payload } => {
262            let id = response_uuid(client.request(Request::AgentMessage {
263                from,
264                to: recipient,
265                payload,
266            })?)?;
267            println!("{}", id.hyphenated());
268            Ok(())
269        }
270        Commands::Tick(cmd) => dispatch_tick_via_daemon(&mut client, cmd),
271        Commands::Daemon(_) | Commands::Chroot(_) => unreachable!("handled in run()"),
272    }
273}
274
275#[cfg(unix)]
276fn dispatch_tick_via_daemon(client: &mut Client, cmd: TickCommands) -> Result<()> {
277    use crate::daemon::{response_found, response_ok, Response};
278
279    match cmd {
280        TickCommands::Start { label } => {
281            let resp = client.request(Request::TickStart { label })?;
282            match resp {
283                Response::Ok {
284                    tick: Some(t), ..
285                } => {
286                    println!("tick {t} started");
287                    Ok(())
288                }
289                Response::Ok {
290                    queued: Some(pos), ..
291                } => {
292                    println!("tick start queued at position {pos}");
293                    Ok(())
294                }
295                Response::Err { error } => Err(crate::Error::msg(error)),
296                _ => Err(crate::Error::msg("unexpected tick start response")),
297            }
298        }
299        TickCommands::End => {
300            let resp = client.request(Request::TickEnd)?;
301            match resp {
302                Response::Ok {
303                    tick: Some(t), ..
304                } => {
305                    println!("tick {t} committed");
306                    Ok(())
307                }
308                Response::Err { error } => Err(crate::Error::msg(error)),
309                _ => Err(crate::Error::msg("unexpected tick end response")),
310            }
311        }
312        TickCommands::Status => {
313            let resp = client.request(Request::TickStatus)?;
314            match resp {
315                Response::Ok { value: Some(v), .. } => {
316                    println!("{v}");
317                    Ok(())
318                }
319                Response::Err { error } => Err(crate::Error::msg(error)),
320                _ => Err(crate::Error::msg("unexpected tick status response")),
321            }
322        }
323        TickCommands::Lock { key } => {
324            response_ok(client.request(Request::TickLock { key })?)?;
325            Ok(())
326        }
327        TickCommands::Unlock { key } => {
328            if response_found(client.request(Request::TickUnlock { key })?)? {
329                Ok(())
330            } else {
331                Err(crate::Error::msg("lock not held"))
332            }
333        }
334    }
335}
336
337fn print_messages(messages: &[Message]) {
338    for (i, msg) in messages.iter().enumerate() {
339        println!("{} {}", msg.id.hyphenated(), msg.path.display());
340        println!("{}", msg.body);
341        if i + 1 < messages.len() {
342            println!("---");
343        }
344    }
345}