Skip to main content

mail4agent_messenger_shell/cli/
web_client.rs

1//! The web machine client as a process: [`MachineClient::from_env`], then
2//! [`MachineClient::tick`] in a loop, and
3//! [`MachineClient::poll_agent_directory`] on `M4A_AGENT_RESCAN_SECS`.
4//!
5//! Settings come from the environment, then from the env file
6//! (`M4A_ENV_FILE`, default `~/.config/mail4agent/web-client.env`) for
7//! anything unset. The client listens on the local send socket
8//! (`M4A_SEND_SOCK`, default `web-client.sock` under the store root) for
9//! `m4a-send`.
10//!
11//! Environment: everything `from_env` reads, plus `M4A_DRIVE_SECS` (full
12//! drive period, default 15) and `M4A_RUN_SECS` (exit after that many
13//! seconds; unset or 0 runs until killed). Prints nicks, event ids, room
14//! ids, and HTTP statuses only. Never prints URLs, keys, or bearers.
15
16use crate::MachineClient;
17use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
18
19fn env_secs(key: &str, default: u64) -> u64 {
20    std::env::var(key)
21        .ok()
22        .and_then(|value| value.parse().ok())
23        .unwrap_or(default)
24}
25
26fn now_ms() -> i64 {
27    SystemTime::now()
28        .duration_since(UNIX_EPOCH)
29        .map(|elapsed| elapsed.as_millis() as i64)
30        .unwrap_or(0)
31}
32
33pub fn run(_args: Vec<String>) {
34    crate::load_env_file();
35    let mut client = match MachineClient::from_env() {
36        Ok(client) => client,
37        Err(err) => {
38            eprintln!("open failed: {err}");
39            std::process::exit(1);
40        }
41    };
42    match client.listen_for_sends_from_env() {
43        Ok(path) => println!("send socket {}", path.display()),
44        Err(err) => eprintln!("send socket not opened: {err}"),
45    }
46    println!("open ok; waiting on the push socket");
47    let drive_secs = env_secs("M4A_DRIVE_SECS", 15);
48    let run_secs = env_secs("M4A_RUN_SECS", 0);
49    let started = Instant::now();
50    let mut seen = 0usize;
51    loop {
52        let report = client.tick(now_ms(), drive_secs);
53        for (nick, event_id) in &report.pushed {
54            println!("push {nick} event={event_id}");
55        }
56        for (nick, text) in &report.alerts {
57            println!("alert {nick}: {text}");
58        }
59        for (nick, room) in &report.joined {
60            println!("joined {nick} room={room}");
61        }
62        for (from, to, reply) in &report.sent {
63            match (&reply.event_id, &reply.error) {
64                (_, Some(err)) => println!("send {from} -> {to} failed: {err}"),
65                (Some(event_id), None) => println!("send {from} -> {to} event={event_id}"),
66                (None, None) => println!("send {from} -> {to} ok"),
67            }
68        }
69        for (nick, err) in &report.errors {
70            eprintln!("drive {nick}: {err}");
71        }
72        let log = client.wake_log();
73        for (nick, attempt) in log.iter().skip(seen) {
74            let status = attempt
75                .status
76                .map(|code| code.to_string())
77                .unwrap_or_else(|| "none".to_string());
78            println!("wake {nick} event={} status={status}", attempt.event_id);
79        }
80        seen = log.len();
81        if let Err(err) = client.poll_agent_directory() {
82            eprintln!("agent poll: {err}");
83        }
84        if run_secs > 0 && started.elapsed().as_secs() >= run_secs {
85            println!("run time reached; exiting");
86            break;
87        }
88        std::thread::sleep(Duration::from_millis(250));
89    }
90}