mirror of
https://github.com/lov3b/ecb-rates.git
synced 2025-02-22 09:50:10 +01:00
Cahce read and write
This commit is contained in:
parent
1932ad1187
commit
0bd3c9004f
72
Cargo.lock
generated
72
Cargo.lock
generated
@ -17,6 +17,21 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
@ -138,6 +153,21 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.23"
|
||||
@ -226,6 +256,7 @@ name = "ecb-rates"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"colored",
|
||||
"quick-xml",
|
||||
@ -495,6 +526,29 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
@ -751,6 +805,15 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
@ -1443,6 +1506,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
|
@ -5,6 +5,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.95"
|
||||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
colored = "2.2.0"
|
||||
quick-xml = { version = "0.37.2", features = ["async-tokio", "tokio"] }
|
||||
|
92
src/cache.rs
Normal file
92
src/cache.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::fs;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use chrono::serde::ts_seconds;
|
||||
use chrono::{DateTime, Local, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::ExchangeRateResult;
|
||||
use crate::os::Os;
|
||||
|
||||
const FILE_NAME: &'static str = "cache.json";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Cache {
|
||||
#[serde(with = "ts_seconds")]
|
||||
date: DateTime<Utc>,
|
||||
|
||||
#[serde(rename = "camelCase")]
|
||||
pub exchange_rate_results: Vec<ExchangeRateResult>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn load() -> Option<Self> {
|
||||
let config_opt = Os::get_current()?.get_config_path();
|
||||
let mut config_path = match config_opt {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to locate config dir: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if let Err(e) = fs::create_dir_all(&config_path) {
|
||||
eprintln!("Failed to create config dir: {:?}", e);
|
||||
return None;
|
||||
}
|
||||
config_path.push(FILE_NAME);
|
||||
if !config_path.try_exists().unwrap_or_default() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match Self::read_config(&config_path) {
|
||||
Ok(k) => Some(k),
|
||||
Err(e) => {
|
||||
eprintln!("Config path is invalid, or cannot be created: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(exchange_rate_results: Vec<ExchangeRateResult>) -> Self {
|
||||
let date = Local::now().to_utc();
|
||||
Self {
|
||||
exchange_rate_results,
|
||||
date,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) -> anyhow::Result<()> {
|
||||
let mut config_path = Os::get_current()
|
||||
.context("Failed to get config home")?
|
||||
.get_config_path()?;
|
||||
fs::create_dir_all(&config_path)?;
|
||||
eprintln!("Config dir: {}", &config_path.display());
|
||||
|
||||
config_path.push(FILE_NAME);
|
||||
eprintln!("File to write: {}", &config_path.display());
|
||||
|
||||
let file = fs::File::options()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&config_path)?;
|
||||
|
||||
let writer = BufWriter::new(file);
|
||||
serde_json::to_writer(writer, self)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> bool {
|
||||
let today = Local::now().naive_local().date();
|
||||
let saved = self.date.naive_local().date();
|
||||
saved == today
|
||||
}
|
||||
|
||||
fn read_config(path: &Path) -> anyhow::Result<Self> {
|
||||
let file = fs::File::open(path)?;
|
||||
let reader = BufReader::new(file);
|
||||
Ok(serde_json::from_reader(reader)?)
|
||||
}
|
||||
}
|
@ -21,6 +21,10 @@ pub struct Cli {
|
||||
#[arg(long = "compact")]
|
||||
pub compact: bool,
|
||||
|
||||
/// Override the cache
|
||||
#[arg(long = "no-cache")]
|
||||
pub no_cache: bool,
|
||||
|
||||
/// Amount of data
|
||||
#[arg(value_enum, default_value_t = Resolution::TODAY, long="resolution", short='r')]
|
||||
pub resolution: Resolution,
|
||||
|
@ -3,6 +3,7 @@ pub mod models;
|
||||
pub mod os;
|
||||
pub mod parsing;
|
||||
pub mod table;
|
||||
pub mod cache;
|
||||
|
||||
const APP_NAME: &'static str = "ECB-rates";
|
||||
|
||||
|
28
src/main.rs
28
src/main.rs
@ -1,4 +1,5 @@
|
||||
use clap::Parser as _;
|
||||
use ecb_rates::cache::Cache;
|
||||
use reqwest::{Client, IntoUrl};
|
||||
use std::{borrow::BorrowMut, collections::HashMap, error::Error, process::ExitCode};
|
||||
|
||||
@ -30,12 +31,18 @@ fn filter_currencies(exchange_rate_results: &mut [ExchangeRateResult], currencie
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> ExitCode {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut parsed = match get_and_parse(cli.resolution.to_ecb_url()).await {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to get/parse data from ECB: {}", e);
|
||||
return ExitCode::FAILURE;
|
||||
let use_cache = !cli.no_cache;
|
||||
let cache = if use_cache { Cache::load() } else { None };
|
||||
let cache_ok = cache.as_ref().map_or_else(|| false, |c| c.validate());
|
||||
let mut parsed = if cache_ok {
|
||||
cache.as_ref().unwrap().exchange_rate_results.clone()
|
||||
} else {
|
||||
match get_and_parse(cli.resolution.to_ecb_url()).await {
|
||||
Ok(k) => k,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to get/parse data from ECB: {}", e);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -72,9 +79,9 @@ async fn main() -> ExitCode {
|
||||
.expect("Failed to parse content as JSON")
|
||||
}
|
||||
FormatOption::Plain => parsed
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let t: Table = x.into();
|
||||
let t: Table = x.clone().into();
|
||||
format!("{}", t)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@ -82,5 +89,10 @@ async fn main() -> ExitCode {
|
||||
};
|
||||
|
||||
println!("{}", &output);
|
||||
if !cache_ok {
|
||||
if let Err(e) = Cache::new(parsed).save() {
|
||||
eprintln!("Failed to save to cache with: {:?}", e);
|
||||
}
|
||||
}
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ExchangeRateResult {
|
||||
pub time: String,
|
||||
pub rates: HashMap<String, f64>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user