diff --git a/crates/owlen-tui/src/app/mod.rs b/crates/owlen-tui/src/app/mod.rs index b44b8dc..f01066f 100644 --- a/crates/owlen-tui/src/app/mod.rs +++ b/crates/owlen-tui/src/app/mod.rs @@ -1,4 +1,5 @@ mod generation; +mod worker; pub mod messages; @@ -51,6 +52,16 @@ impl App { active.abort(); } } + + /// Launch the background worker responsible for provider health checks. + pub fn spawn_background_worker(&self) -> JoinHandle<()> { + let manager = Arc::clone(&self.provider_manager); + let sender = self.message_tx.clone(); + + tokio::spawn(async move { + worker::background_worker(manager, sender).await; + }) + } } struct ActiveGeneration { diff --git a/crates/owlen-tui/src/app/worker.rs b/crates/owlen-tui/src/app/worker.rs new file mode 100644 index 0000000..22a0a3f --- /dev/null +++ b/crates/owlen-tui/src/app/worker.rs @@ -0,0 +1,52 @@ +use std::sync::Arc; +use std::time::Duration; + +use tokio::{sync::mpsc, time}; + +use owlen_core::provider::ProviderManager; + +use super::AppMessage; + +const HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(30); + +/// Periodically refresh provider health and emit status updates into the app's +/// message channel. Exits automatically once the receiver side of the channel +/// is dropped. +pub async fn background_worker( + provider_manager: Arc, + message_tx: mpsc::UnboundedSender, +) { + let mut interval = time::interval(HEALTH_CHECK_INTERVAL); + let mut last_statuses = provider_manager.provider_statuses().await; + + loop { + interval.tick().await; + + if message_tx.is_closed() { + break; + } + + let statuses = provider_manager.refresh_health().await; + + for (provider_id, status) in statuses { + let changed = match last_statuses.get(&provider_id) { + Some(previous) => previous != &status, + None => true, + }; + + last_statuses.insert(provider_id.clone(), status); + + if changed + && message_tx + .send(AppMessage::ProviderStatus { + provider_id, + status, + }) + .is_err() + { + // Receiver dropped; terminate worker. + return; + } + } + } +}