use crate::MachineClient;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
const HEAL_LOG_STREAK: u32 = 10;
fn env_secs(key: &str, default: u64) -> u64 {
std::env::var(key)
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or(default)
}
fn now_ms() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|elapsed| elapsed.as_millis() as i64)
.unwrap_or(0)
}
pub fn run(_args: Vec<String>) {
crate::load_env_file();
let mut client = match MachineClient::from_env() {
Ok(client) => client,
Err(err) => {
eprintln!("open failed: {err}");
std::process::exit(1);
}
};
match client.listen_for_sends_from_env() {
Ok(path) => println!("send socket {}", path.display()),
Err(err) => eprintln!("send socket not opened: {err}"),
}
println!("open ok; waiting on the push socket");
let drive_secs = env_secs("M4A_DRIVE_SECS", 15);
let run_secs = env_secs("M4A_RUN_SECS", 0);
let started = Instant::now();
let mut seen: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
loop {
let report = client.tick(now_ms(), drive_secs);
for (nick, event_id) in &report.pushed {
println!("push {nick} event={event_id}");
}
for (nick, text) in &report.alerts {
println!("alert {nick}: {text}");
}
for (nick, room) in &report.joined {
println!("joined {nick} room={room}");
}
for (from, to, reply) in &report.sent {
match (&reply.event_id, &reply.error) {
(_, Some(err)) => println!("send {from} -> {to} failed: {err}"),
(Some(event_id), None) => println!("send {from} -> {to} event={event_id}"),
(None, None) => println!("send {from} -> {to} ok"),
}
}
for (nick, err) in &report.errors {
eprintln!("drive {nick}: {err}");
}
let log = client.wake_log();
let mut now_counts: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
for (nick, attempt) in &log {
let n = now_counts.entry(nick.clone()).or_insert(0);
*n += 1;
if *n > seen.get(nick).copied().unwrap_or(0) {
let status = attempt.status.map(|code| code.to_string()).unwrap_or_else(|| "none".to_string());
println!("wake {nick} event={} status={status}", attempt.event_id);
}
}
seen = now_counts;
if let Err(err) = client.poll_agent_directory() {
eprintln!("agent poll: {err}");
}
if run_secs > 0 && started.elapsed().as_secs() >= run_secs {
println!("run time reached; exiting");
break;
}
let streak = client.error_streak();
if streak == 1 || streak == HEAL_LOG_STREAK {
eprintln!("homeserver unreachable or failing; backing off and retrying on its own");
}
let pause_ms = if streak == 0 { 250 } else { (250u64 << streak.min(6)).min(10_000) };
std::thread::sleep(Duration::from_millis(pause_ms));
}
}