diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000..05beb91 --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,92 @@ +use crate::errors::*; +use core::time::Duration; +use log::{debug, trace}; +use serde::Serialize; +use std::fs::File as FsFile; +use std::io::{BufReader, BufWriter}; +use std::path::PathBuf; +use std::time::SystemTime; + +pub trait Cache { + fn set(&self, key: &str, data: S) -> Result<()> + where S: Serialize; + fn get(&self, key: &str) -> Result> + where D: serde::de::DeserializeOwned; + fn exists(&self, key: &str) -> bool; +} + +/// A File cache +pub struct File { + /// The root path to the files + root: PathBuf, + /// Time to live for items + ttl: Duration, +} + +impl File { + /// Create a new File cache + /// + /// The cached objects will be stored in the provided root folder. + /// The folder will be created, if it does not yet exist. + /// + /// ## Errors + /// The function returns an `Err` if the creation of the folders fails + pub fn new(root: &PathBuf, ttl: Duration) -> Result { + if !root.exists() { + std::fs::create_dir_all(root) + .chain_err(|| "Error while creating dirs for file cache")?; + } + Ok(Self { root: root.clone(), ttl }) + } +} + +impl Cache for File { + fn exists(&self, key: &str) -> bool { + trace!("Check existence of {} in file cache", key); + let mut path = self.root.clone(); + path.push(key); + if !path.exists() { + return false; + } + + let modified: SystemTime = path + .metadata() + .map(|meta| meta.modified().unwrap_or(SystemTime::UNIX_EPOCH)) + .unwrap_or(SystemTime::UNIX_EPOCH); + + if modified.elapsed().unwrap_or(Duration::from_secs(u64::max_value())) > self.ttl { + debug!("Cache file is too older (> days), won't be used"); + return false; + } + + true + } + + fn set(&self, key: &str, data: S) -> Result<()> + where S: serde::Serialize { + let mut path: PathBuf = self.root.clone(); + path.push(key); + trace!("Serializing data to cache file {}", path.to_string_lossy()); + let cache = + FsFile::create(path).chain_err(|| "Error while creating file cache to write")?; + let writer = BufWriter::new(cache); + serde_json::to_writer(writer, &data) + .chain_err(|| "Error while serialzing templates to file cache")?; + debug!("Serialization of data completed"); + Ok(()) + } + + fn get(&self, key: &str) -> Result> + where D: serde::de::DeserializeOwned { + let mut path = self.root.clone(); + path.push(key); + debug!("Retrieving {} from file cache", key); + let f = FsFile::open(path) + .chain_err(|| format!("Error while opening cache file for key {}", key))?; + let reader = BufReader::new(f); + let obj: Vec = serde_json::from_reader(reader) + .chain_err(|| "Error while reading templates from file cache")?; + debug!("Deserialized {} templates from file cache", obj.len()); + Ok(obj) + } +} diff --git a/src/main.rs b/src/main.rs index daf8c83..d031d11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,7 @@ use structopt::{clap, StructOpt}; // Local imports mod app; +mod cache; mod gitignore; mod helpers; mod template; diff --git a/src/template.rs b/src/template.rs index d8b532d..1c7ebc4 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,5 +1,6 @@ //! This module contains structs for working with the github templates +use directories::ProjectDirs; use log::{debug, trace}; use reqwest::blocking::Client; use reqwest::header::{ACCEPT, CONTENT_TYPE, USER_AGENT}; @@ -115,8 +116,12 @@ impl GithubTemplates { /// Returns the path of the file cache fn cache_path() -> PathBuf { - let proj = directories::ProjectDirs::from("org", "webschneider", env!("CARGO_PKG_NAME")); - let mut cache: PathBuf = crate::helpers::cache_dir(); + let proj = ProjectDirs::from("org", "webschneider", env!("CARGO_PKG_NAME")); + let mut cache: PathBuf = match proj { + Some(p) => p.cache_dir().into(), + None => PathBuf::from("/tmp"), + }; + cache.push("templates.json"); debug!("Using cache path for templates: {}", cache.to_str().unwrap_or("err")); cache