From df31122c0eb3aff4132e4aab2c06e0133b1105b5 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 1 Jul 2026 13:15:36 +0200 Subject: [PATCH] feat: caching --- src/dwd.rs | 36 +++++++++++++++++++++++++++++++++--- src/main.rs | 8 ++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/dwd.rs b/src/dwd.rs index 46f6f80..b9c6cd9 100644 --- a/src/dwd.rs +++ b/src/dwd.rs @@ -1,10 +1,15 @@ -use std::collections::{BTreeMap, HashMap}; +use std::{ + collections::{BTreeMap, HashMap}, + time::Instant, +}; use reqwest::Url; use serde::Deserialize; #[derive(Default)] -pub struct DwdCache {} +pub struct DwdCache { + dwd_responses: HashMap, (Instant, Result)>, +} pub type Forecast = HashMap; #[derive(Deserialize)] @@ -168,7 +173,32 @@ impl ForecastData { } } -pub async fn forecast(ids: &[&str], cache: &mut DwdCache) -> Result { +pub async fn forecast<'a>( + ids: Vec, + cache: &'a mut DwdCache, +) -> Result<&'a Forecast, &'a String> { + let now = Instant::now(); + cache.dwd_responses.retain(|_, (time, cached)| { + now.saturating_duration_since(*time).as_secs() < if cached.is_ok() { 600 } else { 90 } + }); + let v = if cache.dwd_responses.contains_key(&ids) { + // cache is fresh, don't remove it, + // don't fetch new data, or_insert_with will not run. + None + } else { + // there is no cache (never was or it was outdated). + // fetch new data since or_insert_with will run. + Some(forecast_impl(&ids).await) + }; + cache + .dwd_responses + .entry(ids) + // closure will run exactly when `v` is `Some` + .or_insert_with(|| (now, v.unwrap())) + .1 + .as_ref() +} +async fn forecast_impl(ids: &[String]) -> Result { dbg!(ids); let mut url = Url::parse("https://app-prod-ws.warnwetter.de/v30/stationOverviewExtended").unwrap(); diff --git a/src/main.rs b/src/main.rs index 4dc4ce0..218d013 100644 --- a/src/main.rs +++ b/src/main.rs @@ -196,13 +196,13 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { let q = q.to_lowercase(); dwd_stations .range((q.clone(), 0)..=(q, usize::MAX)) - .map(|(_, station)| station.station.as_str()) + .map(|(_, station)| station.station.clone()) .take(WEATHER_MAX_STATIONS) .collect() } LocationString::Station(q) => q .iter() - .map(|s| s.as_str()) + .map(|s| s.clone()) .take(WEATHER_MAX_STATIONS) .collect(), LocationString::Coords(lat, lon) => vec![ @@ -211,7 +211,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { .min_by(|a, b| dist2(a, *lat, *lon).total_cmp(&dist2(b, *lat, *lon))) .unwrap() .station - .as_str(), + .clone(), ], }; if stations.is_empty() { @@ -220,7 +220,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { "could not find any such station".to_owned(), ))); } - match crate::dwd::forecast(&stations, dwd_cache).await { + match crate::dwd::forecast(stations, dwd_cache).await { Ok(forecast) => { fn forecasts( forecast: &HashMap,