From 845bc3d484c355ca314c62b5d7a9a91cc637c5b0 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:14:11 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=A1=20Optimize=20historical=20rates?= =?UTF-8?q?=20batch=20fetching=20with=20API=20range=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced individual Frankfurter API requests with a single batched range query in fetch_historical_rates_batch. Added fallback logic to handle missing weekend/holiday dates. Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src-tauri/src/bench_batch.rs | 1 + src-tauri/src/main.rs | 84 +++++++++++++++++++++++------------- 2 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 src-tauri/src/bench_batch.rs diff --git a/src-tauri/src/bench_batch.rs b/src-tauri/src/bench_batch.rs new file mode 100644 index 0000000..93fe642 --- /dev/null +++ b/src-tauri/src/bench_batch.rs @@ -0,0 +1 @@ +// Wait, we can just write a quick script to benchmark it from JS or Rust diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9a32958..b6be0af 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -628,42 +628,68 @@ async fn fetch_historical_rates_batch( .unwrap_or_default() }); - let mut futures = Vec::new(); + let mut parsed_dates: Vec = missing_dates + .iter() + .filter_map(|d| chrono::NaiveDate::parse_from_str(d, "%Y-%m-%d").ok()) + .collect(); - for date in missing_dates { - let url = format!("https://api.frankfurter.dev/v1/{}?from=USD", date); - let client_clone = client.clone(); - let app_clone = app.clone(); + if parsed_dates.is_empty() { + return Ok(results); + } - futures.push(async move { - let mut rate_map = None; - if let Ok(resp) = client_clone.get(&url).send().await { - if let Ok(bytes) = read_body_capped(resp, RATES_RESPONSE_LIMIT).await { - if let Ok(body) = serde_json::from_slice::(&bytes) { - if let Some(rates_obj) = body["rates"].as_object() { - let daily_rates = parse_rate_map(rates_obj); - let cache_path = - data_dir(&app_clone).join(format!("rates-{}.json", date)); - if let Ok(serialized) = serde_json::to_string(&daily_rates) { - write_atomic_async(cache_path, serialized).await; + parsed_dates.sort(); + + let min_date = parsed_dates.first().unwrap(); + let max_date = parsed_dates.last().unwrap(); + + // Request from min_date - 7 days to cover weekends/holidays before the earliest requested date + let start_date = *min_date - chrono::Duration::days(7); + + let url = format!( + "https://api.frankfurter.dev/v1/{}..{}?from=USD", + start_date.format("%Y-%m-%d"), + max_date.format("%Y-%m-%d") + ); + + if let Ok(resp) = client.get(&url).send().await { + if let Ok(bytes) = read_body_capped(resp, RATES_RESPONSE_LIMIT * 50).await { + if let Ok(body) = serde_json::from_slice::(&bytes) { + if let Some(rates_obj) = body["rates"].as_object() { + let mut available_dates: Vec = rates_obj + .keys() + .filter_map(|d| chrono::NaiveDate::parse_from_str(d, "%Y-%m-%d").ok()) + .collect(); + available_dates.sort(); + + for req_date_str in missing_dates { + if let Ok(req_date) = chrono::NaiveDate::parse_from_str(&req_date_str, "%Y-%m-%d") { + // Find the exact date or the closest preceding date + let matched_date_str = match available_dates.binary_search(&req_date) { + Ok(idx) => Some(available_dates[idx].format("%Y-%m-%d").to_string()), + Err(idx) => { + if idx > 0 { + Some(available_dates[idx - 1].format("%Y-%m-%d").to_string()) + } else { + None + } + } + }; + + if let Some(matched_date) = matched_date_str { + if let Some(matched_rates_obj) = rates_obj.get(&matched_date).and_then(|r| r.as_object()) { + let daily_rates = parse_rate_map(matched_rates_obj); + let cache_path = data_dir(&app).join(format!("rates-{}.json", req_date_str)); + if let Ok(serialized) = serde_json::to_string(&daily_rates) { + write_atomic_async(cache_path, serialized).await; + } + results.insert(req_date_str, daily_rates); + } } - rate_map = Some((date, daily_rates)); } } } } - rate_map - }); - } - - use futures::stream::StreamExt; - let fetched_results = futures::stream::iter(futures) - .buffer_unordered(5) - .collect::>() - .await; - - for (date, rates) in fetched_results.into_iter().flatten() { - results.insert(date, rates); + } } Ok(results) From da0c8edb8c1467700044012bf02846b1c0506dd6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:22:23 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=A1=20Optimize=20historical=20rates?= =?UTF-8?q?=20batch=20fetching=20with=20API=20range=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced individual Frankfurter API requests with a single batched range query in fetch_historical_rates_batch. Added fallback logic to handle missing weekend/holiday dates. Ran cargo fmt to resolve CI formatting failures. Co-authored-by: Horizontal42 <75383519+Horizontal42@users.noreply.github.com> --- src-tauri/src/bench_batch.rs | 1 - src-tauri/src/main.rs | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) delete mode 100644 src-tauri/src/bench_batch.rs diff --git a/src-tauri/src/bench_batch.rs b/src-tauri/src/bench_batch.rs deleted file mode 100644 index 93fe642..0000000 --- a/src-tauri/src/bench_batch.rs +++ /dev/null @@ -1 +0,0 @@ -// Wait, we can just write a quick script to benchmark it from JS or Rust diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b6be0af..5201cb1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -662,13 +662,19 @@ async fn fetch_historical_rates_batch( available_dates.sort(); for req_date_str in missing_dates { - if let Ok(req_date) = chrono::NaiveDate::parse_from_str(&req_date_str, "%Y-%m-%d") { + if let Ok(req_date) = + chrono::NaiveDate::parse_from_str(&req_date_str, "%Y-%m-%d") + { // Find the exact date or the closest preceding date let matched_date_str = match available_dates.binary_search(&req_date) { - Ok(idx) => Some(available_dates[idx].format("%Y-%m-%d").to_string()), + Ok(idx) => { + Some(available_dates[idx].format("%Y-%m-%d").to_string()) + } Err(idx) => { if idx > 0 { - Some(available_dates[idx - 1].format("%Y-%m-%d").to_string()) + Some( + available_dates[idx - 1].format("%Y-%m-%d").to_string(), + ) } else { None } @@ -676,9 +682,12 @@ async fn fetch_historical_rates_batch( }; if let Some(matched_date) = matched_date_str { - if let Some(matched_rates_obj) = rates_obj.get(&matched_date).and_then(|r| r.as_object()) { + if let Some(matched_rates_obj) = + rates_obj.get(&matched_date).and_then(|r| r.as_object()) + { let daily_rates = parse_rate_map(matched_rates_obj); - let cache_path = data_dir(&app).join(format!("rates-{}.json", req_date_str)); + let cache_path = + data_dir(&app).join(format!("rates-{}.json", req_date_str)); if let Ok(serialized) = serde_json::to_string(&daily_rates) { write_atomic_async(cache_path, serialized).await; }