From f862be3916e720eceaa79efb6710b05280220764 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 1 Jul 2026 13:19:55 +0200 Subject: [PATCH] feat: caching --- src/dwd.rs | 36 +++++++++++++++++++++++++++++++++--- src/main.rs | 24 ++++++++++-------------- 2 files changed, 43 insertions(+), 17 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..9b380dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -196,22 +196,18 @@ 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()) - .take(WEATHER_MAX_STATIONS) - .collect(), + LocationString::Station(q) => q.iter().take(WEATHER_MAX_STATIONS).cloned().collect(), LocationString::Coords(lat, lon) => vec![ dwd_stations .values() .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 +216,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, @@ -230,7 +226,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { .flat_map(|(_, forecast)| forecast.forecasts()) } let mut timer = TimeStepper::invalid(); - for forecast in forecasts(&forecast) { + for forecast in forecasts(forecast) { timer.add(forecast.time_start(), forecast.time_step()); } if timer.count() == 0 { @@ -274,7 +270,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { (35.0, "#FF0"), ], |time| { - avg(forecasts(&forecast) + avg(forecasts(forecast) .flat_map(move |forecast| forecast.temperature(time))) }, additional, @@ -286,7 +282,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { (i(), Some(0.0), None), &[(0.0, "#333"), (1.0, "#446"), (5.0, "#22A"), (20.0, "#00F")], |time| { - avg(forecasts(&forecast) + avg(forecasts(forecast) .flat_map(move |forecast| forecast.precipitation(time))) }, additional, @@ -298,7 +294,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { (i(), Some(0.0), None), &[(0.0, "#333"), (60.0, "#DD0"), (360.0, "#F90")], |time| { - avg(forecasts(&forecast) + avg(forecasts(forecast) .flat_map(move |forecast| forecast.sunshine(time))) }, additional, @@ -310,7 +306,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { (i(), Some(0.0), Some(100.0)), &[(0.0, "#884"), (40.0, "#333"), (100.0, "#A0A")], |time| { - avg(forecasts(&forecast) + avg(forecasts(forecast) .flat_map(move |forecast| forecast.humidity(time))) }, additional, @@ -329,7 +325,7 @@ async fn weather_overview(q: &str, data: &State, accept: &Accept) -> Page { } } } - Err(e) => Err(Err((Status::InternalServerError, e))), + Err(e) => Err(Err((Status::InternalServerError, e.clone()))), } } else { Err(Err((