From dc0fee2ee3f95b9da30364e9dbd418bf5b1488bd Mon Sep 17 00:00:00 2001 From: vikingowl Date: Thu, 16 Oct 2025 21:01:08 +0200 Subject: [PATCH] feat(app): add background worker for provider health checks Introduce a `worker` module with `background_worker` that periodically refreshes provider health and emits status updates via the app's message channel. Add `spawn_background_worker` method to `App` for launching the worker as a Tokio task. --- crates/owlen-tui/src/app/mod.rs | 11 +++++++ crates/owlen-tui/src/app/worker.rs | 52 ++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 crates/owlen-tui/src/app/worker.rs 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; + } + } + } +}