Skip to content

Commit

Permalink
simple refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrlok committed Jun 28, 2022
1 parent 1d7a9f8 commit 34bc702
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 228 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name = "shoporusni"
version = "0.1.0"
edition = "2021"



# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
Expand Down
82 changes: 82 additions & 0 deletions src/lib/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use anyhow::Result;
use reqwest::blocking::get;
use url::Url;

pub fn get_data(url: Url) -> Result<String> {
get(url)?.text().map_err(|e| e.into())
}

#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Statistics {
pub message: String,
pub data: Data,
}

#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Data {
pub date: String,
pub day: i64,
pub resource: String,
pub stats: Stats,
pub increase: Increase,
}

#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Stats {
#[serde(rename = "personnel_units")]
pub personnel_units: i64,
pub tanks: i64,
#[serde(rename = "armoured_fighting_vehicles")]
pub armoured_fighting_vehicles: i64,
#[serde(rename = "artillery_systems")]
pub artillery_systems: i64,
pub mlrs: i64,
#[serde(rename = "aa_warfare_systems")]
pub aa_warfare_systems: i64,
pub planes: i64,
pub helicopters: i64,
#[serde(rename = "vehicles_fuel_tanks")]
pub vehicles_fuel_tanks: i64,
#[serde(rename = "warships_cutters")]
pub warships_cutters: i64,
#[serde(rename = "cruise_missiles")]
pub cruise_missiles: i64,
#[serde(rename = "uav_systems")]
pub uav_systems: i64,
#[serde(rename = "special_military_equip")]
pub special_military_equip: i64,
#[serde(rename = "atgm_srbm_systems")]
pub atgm_srbm_systems: i64,
}

#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Increase {
#[serde(rename = "personnel_units")]
pub personnel_units: i64,
pub tanks: i64,
#[serde(rename = "armoured_fighting_vehicles")]
pub armoured_fighting_vehicles: i64,
#[serde(rename = "artillery_systems")]
pub artillery_systems: i64,
pub mlrs: i64,
#[serde(rename = "aa_warfare_systems")]
pub aa_warfare_systems: i64,
pub planes: i64,
pub helicopters: i64,
#[serde(rename = "vehicles_fuel_tanks")]
pub vehicles_fuel_tanks: i64,
#[serde(rename = "warships_cutters")]
pub warships_cutters: i64,
#[serde(rename = "cruise_missiles")]
pub cruise_missiles: i64,
#[serde(rename = "uav_systems")]
pub uav_systems: i64,
#[serde(rename = "special_military_equip")]
pub special_military_equip: i64,
#[serde(rename = "atgm_srbm_systems")]
pub atgm_srbm_systems: i64,
}
162 changes: 162 additions & 0 deletions src/lib/cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use std::fs::File;
use std::io::{ErrorKind, Read, Write};
use std::path::{PathBuf};
use anyhow::{anyhow, Result};
use humantime::Duration;
use std::time::SystemTime;
use log::{debug, info, warn};

// Create a new instance of Cache and read the cache from the file
pub fn read(cache_dir: PathBuf, ttl: humantime::Duration) -> Result<Cache> {
let mut cache = Cache::new(cache_dir, ttl);
debug!("Init cache: {:?}", &cache);
info!("Reading cache from fle: {:?}", cache.file.as_path());
cache.read()?;
debug!("Cache read: {:?}", &cache);
Ok(cache)
}

#[derive(Debug)]
pub struct Cache {
pub file: PathBuf,
pub ttl: Duration,
pub data: Data,
}

impl Cache {
pub fn new(cache_dir: PathBuf, ttl: Duration) -> Cache {
Cache {
file: cache_dir.join("cache.json"),
data: Data::NotChecked,
ttl,
}
}

pub fn write(&mut self, data: &str) -> Result<()> {
let mut file = File::create(&self.file)?;
file.write_all(data.as_bytes())?;
Ok(())
}

pub fn read(&mut self) -> Result<&Self> {
match File::open(&self.file) {
Ok(mut f) => {
info!("Cache file exists: {:?}", &self.file);
info!("Reading cache file...");
let mut s = String::new();
f.read_to_string(&mut s)?;
debug!("Cache file read: {:?}", &s);

info!("Checking cache file age...");
let mills_since_modified = Duration::from(SystemTime::now().duration_since(f.metadata()?.modified()?)?);
info!("Cache file age: {}", mills_since_modified);
self.data = if mills_since_modified.as_micros() > self.ttl.as_micros() {
info!("Cache file is outdated");
Data::Outdated(s)
} else {
info!("Cache file is not outdated");
Data::Ready(s)
}
}
Err(e) => {
warn!("Error opening cache file: {:?}", e);
if e.kind() == ErrorKind::NotFound {
info!("Cache file does not exist: {:?}", &self.file);
info!("Creating cache file...");
File::create(&self.file)?;
self.data = Data::None
} else {
return Err(anyhow!("Error opening cache file: {}", e));
}
}
}
Ok(self)
}
}


#[derive(Debug, PartialEq, Clone)]
pub enum Data {
Ready(String),
Outdated(String),
None,
NotChecked,
}

#[cfg(test)]
mod tests {
use std::io::Write;
use std::str::FromStr;
use tempfile;
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;

// Test Cache.get_cache() fail
#[test]
fn get_cache_fail() {
let mut cache = Cache::new(PathBuf::from("/nonexist/"), Duration::from_str("1s").unwrap());
assert!(cache.read().is_err());
}

// Test Cache.get_cache() not found
#[test]
fn get_cache_none() {
let dir = tempfile::tempdir().expect("Can't create a temp dir").into_path();
let mut cache = Cache::new(dir.clone(), Duration::from_str("1s").unwrap());
assert!(matches!(cache.read(), Ok(_)));
assert!(matches!(cache.data, Data::None));
// Check file is created
assert!(
!File::open(format!("{}/cache.json", dir.to_str().unwrap())).is_err()
)
}

// Test Cache.get_cache() outdated
#[test]
fn get_cache_outdated() {
let dir = tempfile::tempdir().expect("Can't create a temp dir").into_path();
let mut cache = Cache::new(dir.clone(), Duration::from_str("0 ns").unwrap());
let mut file = File::create(dir.clone().join("cache.json")).unwrap();
write!(file, "data").expect("Fila should contain info");
assert!(matches!(cache.read(), Ok(_)));
assert_eq!(cache.data, Data::Outdated("data".to_string()));
// Check file is created
assert!(
!File::open(format!("{}/cache.json", dir.to_str().unwrap())).is_err()
)
}

// Test Cache.get_cache() ready
#[test]
fn get_cache_ready() {
let dir = tempfile::tempdir().expect("Can't create a temp dir").into_path();
let mut cache = Cache::new(dir.clone(), Duration::from_str("1 s").unwrap());
let mut file = File::create(dir.clone().join("cache.json")).unwrap();
write!(file, "data").expect("Fila should contain info");
assert!(matches!(cache.read(), Ok(_)));
assert_eq!(cache.data, Data::Ready("data".to_string()));
// Check file is created
assert!(
!File::open(format!("{}/cache.json", dir.to_str().unwrap())).is_err()
)
}

// Test Cache.write() fail
#[test]
fn write_fail() {
let dir = PathBuf::from_str("/nonexist/").unwrap();
let mut cache = Cache::new(dir.clone(), Duration::from_str("1s").unwrap());
assert!(cache.write("data").is_err());
}

// Test Cache.write() success
#[test]
fn write_success() {
let dir = tempfile::tempdir().expect("Can't create a temp dir").into_path();
let mut cache = Cache::new(dir.clone(), Duration::from_str("1s").unwrap());
assert!(matches!(cache.write("data"), Ok(_)));
assert!(
!File::open(format!("{}/cache.json", dir.to_str().unwrap())).is_err()
)
}
}
17 changes: 17 additions & 0 deletions src/lib/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::fs::DirBuilder;
use std::path::PathBuf;
use anyhow::{Result, Context};
use log::debug;

pub fn config_dir() -> Result<PathBuf> {
let cfg_dir = dirs::config_dir()
.unwrap_or(PathBuf::new())
.join("shoporusni");
debug!("Config dir: {:?}", cfg_dir);
// Create a new config dir if it doesn't exist
DirBuilder::new()
.recursive(true)
.create(&cfg_dir)
.context("Creating a new config directory")?;
Ok(cfg_dir)
}
52 changes: 52 additions & 0 deletions src/lib/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use anyhow::{anyhow, Context, Result};
use log::{debug, info};
use colored::Colorize;

pub mod api;
pub mod cache;
pub mod config;


pub fn get_data(url: url::Url, mut c: cache::Cache) -> Result<api::Statistics> {
info!("Trying to decide — get data from API or from cache");
let data = c.data.clone();
match data {
cache::Data::Ready(ref s) => {
info!("Cache is ready, no need to get API");
serde_json::from_str(s).context("Error parsing cache data")
},
cache::Data::Outdated(ref s) => {
info!("Cache is outdated");
api::get_data(url)
.and_then(|d| serde_json::from_str(&d).map_err(|e| e.into()))
.and_then(|d: api::Statistics| {
info!("Got data from API, writing cache");
info!("Cache data: {:?}", &d);
c.write(s)?;
info!("Cache written");
Ok(d)
})
.or_else(|_| serde_json::from_str(s).map_err(|e| e.into()))
},
cache::Data::None => {
info!("Cache is empty");
info!("Getting data from API");
let data = api::get_data(url)?;
debug!("Got data from API: {:?}", &data);
info!("Got data from API, trying to parse it");
let result: api::Statistics = serde_json::from_str(&data).context("Error serializing data")?;
debug!("Parsed data: {:?}", &result);
info!("Parsed data, writing cache");
c.write(&data)?;
info!("Cache written");
Ok(result)
}
_ => Err(anyhow!("Cache in wired state...")),
}
}

pub fn print_result(data: &api::Statistics) {
let mil = data.data.stats.personnel_units.to_string().red();
let mil_inc = data.data.increase.personnel_units.to_string().green();
print!("{}↑{}", mil, mil_inc)
}
Loading

0 comments on commit 34bc702

Please sign in to comment.