From 0bd3c9004f6d01a1b7605ee01a2d957f1f1f3c87 Mon Sep 17 00:00:00 2001 From: Love Billenius Date: Tue, 7 Jan 2025 15:52:01 +0100 Subject: [PATCH] Cahce read and write --- Cargo.lock | 72 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/cache.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/cli.rs | 4 +++ src/lib.rs | 1 + src/main.rs | 28 +++++++++++----- src/models.rs | 2 +- 7 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 src/cache.rs diff --git a/Cargo.lock b/Cargo.lock index dc6a59c..e2db372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 13b17e6..7894363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000..823a033 --- /dev/null +++ b/src/cache.rs @@ -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, + + #[serde(rename = "camelCase")] + pub exchange_rate_results: Vec, +} + +impl Cache { + pub fn load() -> Option { + 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) -> 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 { + let file = fs::File::open(path)?; + let reader = BufReader::new(file); + Ok(serde_json::from_reader(reader)?) + } +} diff --git a/src/cli.rs b/src/cli.rs index 6147b6e..5cd8fa1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -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, diff --git a/src/lib.rs b/src/lib.rs index 8d4ef6a..8fdb996 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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"; diff --git a/src/main.rs b/src/main.rs index ce137b2..de46799 100644 --- a/src/main.rs +++ b/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::>() @@ -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 } diff --git a/src/models.rs b/src/models.rs index 98abe83..3caacf1 100644 --- a/src/models.rs +++ b/src/models.rs @@ -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,