mail4agent_messenger_shell/cli/
web_client.rs1use crate::MachineClient;
17use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
18
19const HEAL_LOG_STREAK: u32 = 10;
21
22fn env_secs(key: &str, default: u64) -> u64 {
23 std::env::var(key)
24 .ok()
25 .and_then(|value| value.parse().ok())
26 .unwrap_or(default)
27}
28
29fn now_ms() -> i64 {
30 SystemTime::now()
31 .duration_since(UNIX_EPOCH)
32 .map(|elapsed| elapsed.as_millis() as i64)
33 .unwrap_or(0)
34}
35
36pub fn run(_args: Vec<String>) {
37 crate::load_env_file();
38 let mut client = match MachineClient::from_env() {
39 Ok(client) => client,
40 Err(err) => {
41 eprintln!("open failed: {err}");
42 std::process::exit(1);
43 }
44 };
45 match client.listen_for_sends_from_env() {
46 Ok(path) => println!("send socket {}", path.display()),
47 Err(err) => eprintln!("send socket not opened: {err}"),
48 }
49 println!("open ok; waiting on the push socket");
50 let drive_secs = env_secs("M4A_DRIVE_SECS", 15);
51 let run_secs = env_secs("M4A_RUN_SECS", 0);
52 let started = Instant::now();
53 let mut seen: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
54 loop {
55 let report = client.tick(now_ms(), drive_secs);
56 for (nick, event_id) in &report.pushed {
57 println!("push {nick} event={event_id}");
58 }
59 for (nick, text) in &report.alerts {
60 println!("alert {nick}: {text}");
61 }
62 for (nick, room) in &report.joined {
63 println!("joined {nick} room={room}");
64 }
65 for (from, to, reply) in &report.sent {
66 match (&reply.event_id, &reply.error) {
67 (_, Some(err)) => println!("send {from} -> {to} failed: {err}"),
68 (Some(event_id), None) => println!("send {from} -> {to} event={event_id}"),
69 (None, None) => println!("send {from} -> {to} ok"),
70 }
71 }
72 for (nick, err) in &report.errors {
73 eprintln!("drive {nick}: {err}");
74 }
75 let log = client.wake_log();
77 let mut now_counts: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
78 for (nick, attempt) in &log {
79 let n = now_counts.entry(nick.clone()).or_insert(0);
80 *n += 1;
81 if *n > seen.get(nick).copied().unwrap_or(0) {
82 let status = attempt.status.map(|code| code.to_string()).unwrap_or_else(|| "none".to_string());
83 println!("wake {nick} event={} status={status}", attempt.event_id);
84 }
85 }
86 seen = now_counts;
87 if let Err(err) = client.poll_agent_directory() {
88 eprintln!("agent poll: {err}");
89 }
90 if run_secs > 0 && started.elapsed().as_secs() >= run_secs {
91 println!("run time reached; exiting");
92 break;
93 }
94 let streak = client.error_streak();
97 if streak == 1 || streak == HEAL_LOG_STREAK {
98 eprintln!("homeserver unreachable or failing; backing off and retrying on its own");
99 }
100 let pause_ms = if streak == 0 { 250 } else { (250u64 << streak.min(6)).min(10_000) };
101 std::thread::sleep(Duration::from_millis(pause_ms));
102 }
103}