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