1mod 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}