mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-10 15:42:01 +03:00
batch historical rates IPC calls with a pooled http client
This commit is contained in:
parent
bba294c6d5
commit
55d8401a44
3 changed files with 119 additions and 16 deletions
|
|
@ -201,10 +201,12 @@ fn read_rates_cache(app: &AppHandle) -> Option<RatesCache> {
|
|||
}
|
||||
|
||||
async fn fetch_rates_online() -> Result<RatesPayload, String> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let client = HTTP_CLIENT.get_or_init(|| {
|
||||
reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.build()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let body: serde_json::Value = client
|
||||
.get("https://open.er-api.com/v6/latest/USD")
|
||||
|
|
@ -380,6 +382,8 @@ fn valid_date_str(date: &str) -> bool {
|
|||
&& parts.iter().all(|p| p.chars().all(|c| c.is_ascii_digit()))
|
||||
}
|
||||
|
||||
static HTTP_CLIENT: std::sync::OnceLock<reqwest::Client> = std::sync::OnceLock::new();
|
||||
|
||||
#[tauri::command]
|
||||
async fn fetch_historical_rates(
|
||||
app: AppHandle,
|
||||
|
|
@ -394,10 +398,12 @@ async fn fetch_historical_rates(
|
|||
return Ok(cached);
|
||||
}
|
||||
}
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let client = HTTP_CLIENT.get_or_init(|| {
|
||||
reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.build()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
// api.frankfurter.app 301-redirects here now, and the old domain is
|
||||
// flaky over reqwest/rustls-tls — go straight to the stable host
|
||||
let url = format!("https://api.frankfurter.dev/v1/{}?from=USD", date);
|
||||
|
|
@ -425,6 +431,86 @@ async fn fetch_historical_rates(
|
|||
Ok(rates)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn fetch_historical_rates_batch(
|
||||
app: AppHandle,
|
||||
dates: Vec<String>,
|
||||
) -> Result<std::collections::HashMap<String, std::collections::HashMap<String, f64>>, String> {
|
||||
let mut results = std::collections::HashMap::new();
|
||||
let mut missing_dates = Vec::new();
|
||||
|
||||
for date in &dates {
|
||||
if !valid_date_str(date) {
|
||||
continue;
|
||||
}
|
||||
let cache_path = data_dir(&app).join(format!("rates-{}.json", date));
|
||||
if let Ok(raw) = std::fs::read_to_string(&cache_path) {
|
||||
if let Ok(cached) = serde_json::from_str::<std::collections::HashMap<String, f64>>(&raw)
|
||||
{
|
||||
results.insert(date.clone(), cached);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
missing_dates.push(date.clone());
|
||||
}
|
||||
|
||||
if missing_dates.is_empty() {
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
let client = HTTP_CLIENT.get_or_init(|| {
|
||||
reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(15))
|
||||
.build()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let mut futures = Vec::new();
|
||||
|
||||
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();
|
||||
|
||||
futures.push(async move {
|
||||
let mut rate_map = None;
|
||||
if let Ok(resp) = client_clone.get(&url).send().await {
|
||||
if let Ok(body) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(rates_obj) = body["rates"].as_object() {
|
||||
let mut daily_rates = std::collections::HashMap::new();
|
||||
daily_rates.insert("USD".to_string(), 1.0_f64);
|
||||
for (code, val) in rates_obj {
|
||||
if let Some(v) = val.as_f64() {
|
||||
if v > 0.0 {
|
||||
daily_rates.insert(code.to_uppercase(), v);
|
||||
}
|
||||
}
|
||||
}
|
||||
let cache_path = data_dir(&app_clone).join(format!("rates-{}.json", date));
|
||||
if let Ok(serialized) = serde_json::to_string(&daily_rates) {
|
||||
let _ = crate::write_atomic(&cache_path, &serialized);
|
||||
}
|
||||
rate_map = Some((date, daily_rates));
|
||||
}
|
||||
}
|
||||
}
|
||||
rate_map
|
||||
});
|
||||
}
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
let fetched_results = futures::stream::iter(futures)
|
||||
.buffer_unordered(5)
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
for (date, rates) in fetched_results.into_iter().flatten() {
|
||||
results.insert(date, rates);
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
// ---------- documents in the (possibly custom) data folder
|
||||
|
||||
#[tauri::command]
|
||||
|
|
@ -747,6 +833,7 @@ fn main() {
|
|||
migrate_data_dir,
|
||||
fetch_rates,
|
||||
fetch_historical_rates,
|
||||
fetch_historical_rates_batch,
|
||||
fetch_market_data,
|
||||
load_extensions,
|
||||
open_extensions_folder,
|
||||
|
|
|
|||
12
src/main.ts
12
src/main.ts
|
|
@ -5,7 +5,7 @@ import { SumEditor } from "./ui/editor";
|
|||
import {
|
||||
AppData, DocMeta, SettingsData, defaultSettingsData,
|
||||
loadAppData, saveAppData, flushAppData, onAppQuit, loadSettings, saveSettings,
|
||||
fetchRates, fetchMarketData, fetchHistoricalRates, loadExtensionScripts, openExtensionsFolder, isTauri,
|
||||
fetchRates, fetchMarketData, fetchHistoricalRatesBatch, loadExtensionScripts, openExtensionsFolder, isTauri,
|
||||
getLaunchFile, onOpenFile, onFileDrop,
|
||||
setDataDir, runBackups, backupDeletedSheet, openBackupsFolder,
|
||||
chooseFolder, dataDirHasDocuments, migrateDataDir,
|
||||
|
|
@ -605,13 +605,9 @@ async function fetchNeededHistoricalRates(text: string): Promise<void> {
|
|||
const datesToFetch = Array.from(dates).filter((date) => !engine.hasHistoricalRates(date));
|
||||
let fetched = false;
|
||||
if (datesToFetch.length > 0) {
|
||||
const results = await Promise.all(
|
||||
datesToFetch.map(async (date) => {
|
||||
const rates = await fetchHistoricalRates(date);
|
||||
return { date, rates };
|
||||
})
|
||||
);
|
||||
for (const { date, rates } of results) {
|
||||
const results = await fetchHistoricalRatesBatch(datesToFetch);
|
||||
for (const date of datesToFetch) {
|
||||
const rates = results[date];
|
||||
if (rates) {
|
||||
engine.setHistoricalRates(date, rates);
|
||||
fetched = true;
|
||||
|
|
|
|||
|
|
@ -213,6 +213,26 @@ export async function fetchRates(force = false): Promise<RatesPayload | null> {
|
|||
|
||||
// ---------- historical rates
|
||||
|
||||
export async function fetchHistoricalRatesBatch(dates: string[]): Promise<Record<string, Record<string, number>>> {
|
||||
if (dates.length === 0) return {};
|
||||
try {
|
||||
if (isTauri()) {
|
||||
return await invoke<Record<string, Record<string, number>>>("fetch_historical_rates_batch", { dates });
|
||||
}
|
||||
const results: Record<string, Record<string, number>> = {};
|
||||
await Promise.all(
|
||||
dates.map(async (date) => {
|
||||
const rates = await fetchHistoricalRates(date);
|
||||
if (rates) results[date] = rates;
|
||||
})
|
||||
);
|
||||
return results;
|
||||
} catch (e) {
|
||||
console.warn("fetchHistoricalRatesBatch failed", e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchHistoricalRates(date: string): Promise<Record<string, number> | null> {
|
||||
try {
|
||||
if (isTauri()) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue