pub mod catalog_refresh; pub mod nightly; pub mod weather_poll; use sqlx::SqlitePool; use tokio::time::sleep; use self::catalog_refresh::run_catalog_refresh; pub use self::nightly::precompute_tonight; use self::weather_poll::start_weather_scheduler; use crate::astronomy::astro_twilight; use crate::config::{LAT, LON}; pub fn start_all_jobs(pool: SqlitePool) { // Catalog refresh on startup (respects TTL) let pool_cat = pool.clone(); tokio::spawn(async move { run_catalog_refresh(pool_cat).await; }); // Initial weather poll let pool_wx = pool.clone(); tokio::spawn(async move { if let Err(e) = crate::weather::poll_weather(&pool_wx).await { tracing::error!("Initial weather poll failed: {}", e); } }); // Weather scheduler start_weather_scheduler(pool.clone()); // Nightly precompute: run at dusk each day let pool_night = pool.clone(); tokio::spawn(async move { loop { // Run once immediately on startup if let Err(e) = precompute_tonight(&pool_night).await { tracing::error!("Nightly precompute failed: {}", e); } // Sleep until next dusk sleep_until_next_dusk().await; } }); } async fn sleep_until_next_dusk() { // Compute tonight's dusk and sleep until then let today = chrono::Utc::now().naive_utc().date(); let tomorrow = today + chrono::Duration::days(1); let dusk = astro_twilight(tomorrow, LAT, LON) .map(|(d, _)| d) .unwrap_or_else(|_| chrono::Utc::now() + chrono::Duration::hours(24)); let now = chrono::Utc::now(); let wait = if dusk > now { (dusk - now).to_std().unwrap_or(std::time::Duration::from_secs(3600)) } else { std::time::Duration::from_secs(3600) }; tracing::info!("Next nightly precompute scheduled in {:.0}h", wait.as_secs_f32() / 3600.0); let tokio_dur = tokio::time::Duration::from_secs(wait.as_secs()); sleep(tokio_dur).await; }