mirror of
				https://github.com/lov3b/ecb-rates.git
				synced 2025-11-04 07:10:18 +01:00 
			
		
		
		
	Cahce read and write
This commit is contained in:
		
							
								
								
									
										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"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
 | 
					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]]
 | 
					[[package]]
 | 
				
			||||||
name = "anstream"
 | 
					name = "anstream"
 | 
				
			||||||
version = "0.6.18"
 | 
					version = "0.6.18"
 | 
				
			||||||
@@ -138,6 +153,21 @@ version = "1.0.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 | 
					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]]
 | 
					[[package]]
 | 
				
			||||||
name = "clap"
 | 
					name = "clap"
 | 
				
			||||||
version = "4.5.23"
 | 
					version = "4.5.23"
 | 
				
			||||||
@@ -226,6 +256,7 @@ name = "ecb-rates"
 | 
				
			|||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "anyhow",
 | 
					 "anyhow",
 | 
				
			||||||
 | 
					 "chrono",
 | 
				
			||||||
 "clap",
 | 
					 "clap",
 | 
				
			||||||
 "colored",
 | 
					 "colored",
 | 
				
			||||||
 "quick-xml",
 | 
					 "quick-xml",
 | 
				
			||||||
@@ -495,6 +526,29 @@ dependencies = [
 | 
				
			|||||||
 "tracing",
 | 
					 "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]]
 | 
					[[package]]
 | 
				
			||||||
name = "icu_collections"
 | 
					name = "icu_collections"
 | 
				
			||||||
version = "1.5.0"
 | 
					version = "1.5.0"
 | 
				
			||||||
@@ -751,6 +805,15 @@ dependencies = [
 | 
				
			|||||||
 "tempfile",
 | 
					 "tempfile",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "num-traits"
 | 
				
			||||||
 | 
					version = "0.2.19"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "autocfg",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "object"
 | 
					name = "object"
 | 
				
			||||||
version = "0.36.7"
 | 
					version = "0.36.7"
 | 
				
			||||||
@@ -1443,6 +1506,15 @@ dependencies = [
 | 
				
			|||||||
 "wasm-bindgen",
 | 
					 "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]]
 | 
					[[package]]
 | 
				
			||||||
name = "windows-registry"
 | 
					name = "windows-registry"
 | 
				
			||||||
version = "0.2.0"
 | 
					version = "0.2.0"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ edition = "2021"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
anyhow = "1.0.95"
 | 
					anyhow = "1.0.95"
 | 
				
			||||||
 | 
					chrono = { version = "0.4.39", features = ["serde"] }
 | 
				
			||||||
clap = { version = "4.5.23", features = ["derive"] }
 | 
					clap = { version = "4.5.23", features = ["derive"] }
 | 
				
			||||||
colored = "2.2.0"
 | 
					colored = "2.2.0"
 | 
				
			||||||
quick-xml = { version = "0.37.2", features = ["async-tokio", "tokio"] }
 | 
					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")]
 | 
					    #[arg(long = "compact")]
 | 
				
			||||||
    pub compact: bool,
 | 
					    pub compact: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Override the cache
 | 
				
			||||||
 | 
					    #[arg(long = "no-cache")]
 | 
				
			||||||
 | 
					    pub no_cache: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Amount of data
 | 
					    /// Amount of data
 | 
				
			||||||
    #[arg(value_enum, default_value_t = Resolution::TODAY, long="resolution", short='r')]
 | 
					    #[arg(value_enum, default_value_t = Resolution::TODAY, long="resolution", short='r')]
 | 
				
			||||||
    pub resolution: Resolution,
 | 
					    pub resolution: Resolution,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ pub mod models;
 | 
				
			|||||||
pub mod os;
 | 
					pub mod os;
 | 
				
			||||||
pub mod parsing;
 | 
					pub mod parsing;
 | 
				
			||||||
pub mod table;
 | 
					pub mod table;
 | 
				
			||||||
 | 
					pub mod cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const APP_NAME: &'static str = "ECB-rates";
 | 
					const APP_NAME: &'static str = "ECB-rates";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,4 +1,5 @@
 | 
				
			|||||||
use clap::Parser as _;
 | 
					use clap::Parser as _;
 | 
				
			||||||
 | 
					use ecb_rates::cache::Cache;
 | 
				
			||||||
use reqwest::{Client, IntoUrl};
 | 
					use reqwest::{Client, IntoUrl};
 | 
				
			||||||
use std::{borrow::BorrowMut, collections::HashMap, error::Error, process::ExitCode};
 | 
					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")]
 | 
					#[tokio::main(flavor = "current_thread")]
 | 
				
			||||||
async fn main() -> ExitCode {
 | 
					async fn main() -> ExitCode {
 | 
				
			||||||
    let cli = Cli::parse();
 | 
					    let cli = Cli::parse();
 | 
				
			||||||
 | 
					    let use_cache = !cli.no_cache;
 | 
				
			||||||
    let mut parsed = match get_and_parse(cli.resolution.to_ecb_url()).await {
 | 
					    let cache = if use_cache { Cache::load() } else { None };
 | 
				
			||||||
        Ok(k) => k,
 | 
					    let cache_ok = cache.as_ref().map_or_else(|| false, |c| c.validate());
 | 
				
			||||||
        Err(e) => {
 | 
					    let mut parsed = if cache_ok {
 | 
				
			||||||
            eprintln!("Failed to get/parse data from ECB: {}", e);
 | 
					        cache.as_ref().unwrap().exchange_rate_results.clone()
 | 
				
			||||||
            return ExitCode::FAILURE;
 | 
					    } 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")
 | 
					            .expect("Failed to parse content as JSON")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        FormatOption::Plain => parsed
 | 
					        FormatOption::Plain => parsed
 | 
				
			||||||
            .into_iter()
 | 
					            .iter()
 | 
				
			||||||
            .map(|x| {
 | 
					            .map(|x| {
 | 
				
			||||||
                let t: Table = x.into();
 | 
					                let t: Table = x.clone().into();
 | 
				
			||||||
                format!("{}", t)
 | 
					                format!("{}", t)
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .collect::<Vec<_>>()
 | 
					            .collect::<Vec<_>>()
 | 
				
			||||||
@@ -82,5 +89,10 @@ async fn main() -> ExitCode {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    println!("{}", &output);
 | 
					    println!("{}", &output);
 | 
				
			||||||
 | 
					    if !cache_ok {
 | 
				
			||||||
 | 
					        if let Err(e) = Cache::new(parsed).save() {
 | 
				
			||||||
 | 
					            eprintln!("Failed to save to cache with: {:?}", e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    ExitCode::SUCCESS
 | 
					    ExitCode::SUCCESS
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize, Deserialize)]
 | 
					#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
				
			||||||
pub struct ExchangeRateResult {
 | 
					pub struct ExchangeRateResult {
 | 
				
			||||||
    pub time: String,
 | 
					    pub time: String,
 | 
				
			||||||
    pub rates: HashMap<String, f64>,
 | 
					    pub rates: HashMap<String, f64>,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user