use crate::api::peers;
use crate::sidecar::SidecarHandle;
use parking_lot::Mutex;
use std::sync::Arc;
use std::time::Duration;
use tauri::{AppHandle, Emitter};
use tokio::task::JoinHandle;
use tracing::warn;
const PEERS_INTERVAL: Duration = Duration::from_secs(3);
const ROUTES_INTERVAL: Duration = Duration::from_secs(5);
pub struct Poller {
peers_handle: Mutex>>,
routes_handle: Mutex >>,
}
impl Poller {
pub fn new() -> Arc {
Arc::new(Self {
peers_handle: Mutex::new(None),
routes_handle: Mutex::new(None),
})
}
/// Spawn the two background loops. Cancels any previously-running tasks
/// so consecutive `start_daemon` calls don't leak handles.
pub fn start(self: &Arc, app: AppHandle, sidecar: Arc) {
self.stop();
*self.peers_handle.lock() = Some(spawn_peers_loop(app.clone(), Arc::clone(&sidecar)));
*self.routes_handle.lock() = Some(spawn_routes_loop(app, sidecar));
}
pub fn stop(&self) {
if let Some(h) = self.peers_handle.lock().take() {
h.abort();
}
if let Some(h) = self.routes_handle.lock().take() {
h.abort();
}
}
}
fn spawn_peers_loop(app: AppHandle, sidecar: Arc) -> JoinHandle<()> {
tokio::spawn(async move {
// Tick once immediately so the UI doesn't wait the full interval.
let mut first = true;
loop {
if !first {
tokio::time::sleep(PEERS_INTERVAL).await;
}
first = false;
let Some(client) = sidecar.client() else {
break;
};
match client.list_peers().await {
Ok(list) => {
let stats = peers::aggregate(&list);
let _ = app.emit("peers://updated", &list);
let _ = app.emit("stats://updated", &stats);
}
Err(e) => warn!(error = %e, "poller: list_peers failed"),
}
}
})
}
fn spawn_routes_loop(app: AppHandle, sidecar: Arc) -> JoinHandle<()> {
tokio::spawn(async move {
let mut first = true;
loop {
if !first {
tokio::time::sleep(ROUTES_INTERVAL).await;
}
first = false;
let Some(client) = sidecar.client() else {
break;
};
match client.routes_snapshot().await {
Ok(snap) => {
let _ = app.emit("routes://updated", &snap);
}
Err(e) => warn!(error = %e, "poller: routes_snapshot failed"),
}
}
})
}