Browse Source

implement kind of cache

master
Schneider 5 years ago
parent
commit
512d0c2ace
Signed by: schneider GPG Key ID: 3F50B02A50039F3B
  1. 20
      src/app.rs
  2. 31
      src/cache.rs
  3. 76
      src/template.rs

20
src/app.rs

@ -5,11 +5,13 @@
use std::path::PathBuf; use std::path::PathBuf;
// 3rd-party crate imports // 3rd-party crate imports
use directories::ProjectDirs;
use structopt::StructOpt; use structopt::StructOpt;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
// Local Imports // Local Imports
use crate::cache::{Cache, File as FileCache};
use crate::errors::*; use crate::errors::*;
use crate::gitignore::Gitignore; use crate::gitignore::Gitignore;
use crate::helpers::{git_dir, BoilerplateOpts, HELP_TEMPLATE}; use crate::helpers::{git_dir, BoilerplateOpts, HELP_TEMPLATE};
@ -98,7 +100,23 @@ fn run_get(lang: &str) -> Result<()> {
/// Runs the command `list-templates` /// Runs the command `list-templates`
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
fn run_list_templates() -> Result<()> { fn run_list_templates() -> Result<()> {
let tmpl = GithubTemplates::new().chain_err(|| "Error while getting Templates")?;
let cache_root: PathBuf = match ProjectDirs::from("org", "webschneider", env!("CARGO_PKG_NAME"))
{
Some(dirs) => PathBuf::from(dirs.cache_dir()),
None => "/tmp".into(),
};
let cache: FileCache = FileCache::new(&cache_root, std::time::Duration::from_secs(60 * 24 * 2))
.chain_err(|| "Error while creating FileCache")?;
let tmpl = if cache.exists("templates.json") {
cache.get("templates.json")?
} else {
let tmpls = GithubTemplates::new().chain_err(|| "Error while getting Templates")?;
cache
.set("templates.json", &tmpls)
.chain_err(|| "Error while writing templates to cache")?;
tmpls
};
let names = tmpl.list_names(); let names = tmpl.list_names();
println!("{}", names.join("\n")); println!("{}", names.join("\n"));
Ok(()) Ok(())

31
src/cache.rs

@ -1,4 +1,5 @@
use crate::errors::*; use crate::errors::*;
use crate::template::GithubTemplates;
use core::time::Duration; use core::time::Duration;
use log::{debug, trace}; use log::{debug, trace};
use serde::Serialize; use serde::Serialize;
@ -7,15 +8,18 @@ use std::io::{BufReader, BufWriter};
use std::path::PathBuf; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
pub trait Cache {
fn set<S>(&self, key: &str, data: S) -> Result<()>
where S: Serialize;
fn get<D>(&self, key: &str) -> Result<Vec<D>>
where D: serde::de::DeserializeOwned;
pub trait Cache<T>
where T: Serialize + serde::de::DeserializeOwned {
/// Stores the `data` under the given `key`
fn set(&self, key: &str, data: &T) -> Result<()>;
/// Retrieves the data under the given `key`
fn get(&self, key: &str) -> Result<T>;
/// Checks whether the given key exists
fn exists(&self, key: &str) -> bool; fn exists(&self, key: &str) -> bool;
} }
/// A File cache /// A File cache
#[derive(Debug)]
pub struct File { pub struct File {
/// The root path to the files /// The root path to the files
root: PathBuf, root: PathBuf,
@ -38,10 +42,9 @@ impl File {
} }
Ok(Self { root: root.clone(), ttl }) Ok(Self { root: root.clone(), ttl })
} }
}
impl Cache for File {
fn exists(&self, key: &str) -> bool {
/// Checks whether an element for the given key exists
pub fn exists(&self, key: &str) -> bool {
trace!("Check existence of {} in file cache", key); trace!("Check existence of {} in file cache", key);
let mut path = self.root.clone(); let mut path = self.root.clone();
path.push(key); path.push(key);
@ -62,8 +65,8 @@ impl Cache for File {
true true
} }
fn set<S>(&self, key: &str, data: S) -> Result<()>
where S: serde::Serialize {
/// Stores a `data` under the specified `key`
pub fn set(&self, key: &str, data: &GithubTemplates) -> Result<()> {
let mut path: PathBuf = self.root.clone(); let mut path: PathBuf = self.root.clone();
path.push(key); path.push(key);
trace!("Serializing data to cache file {}", path.to_string_lossy()); trace!("Serializing data to cache file {}", path.to_string_lossy());
@ -76,17 +79,17 @@ impl Cache for File {
Ok(()) Ok(())
} }
fn get<D>(&self, key: &str) -> Result<Vec<D>>
where D: serde::de::DeserializeOwned {
/// Retrieves data for `key`
pub fn get(&self, key: &str) -> Result<GithubTemplates> {
let mut path = self.root.clone(); let mut path = self.root.clone();
path.push(key); path.push(key);
debug!("Retrieving {} from file cache", key); debug!("Retrieving {} from file cache", key);
let f = FsFile::open(path) let f = FsFile::open(path)
.chain_err(|| format!("Error while opening cache file for key {}", key))?; .chain_err(|| format!("Error while opening cache file for key {}", key))?;
let reader = BufReader::new(f); let reader = BufReader::new(f);
let obj: Vec<D> = serde_json::from_reader(reader)
let obj: GithubTemplates = serde_json::from_reader(reader)
.chain_err(|| "Error while reading templates from file cache")?; .chain_err(|| "Error while reading templates from file cache")?;
debug!("Deserialized {} templates from file cache", obj.len());
debug!("Deserialized {} templates from file cache", "some");
Ok(obj) Ok(obj)
} }
} }

76
src/template.rs

@ -1,20 +1,20 @@
//! This module contains structs for working with the github templates //! This module contains structs for working with the github templates
use directories::ProjectDirs;
use crate::cache::Cache;
use log::{debug, trace}; use log::{debug, trace};
use reqwest::blocking::Client; use reqwest::blocking::Client;
use reqwest::header::{ACCEPT, CONTENT_TYPE, USER_AGENT}; use reqwest::header::{ACCEPT, CONTENT_TYPE, USER_AGENT};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::prelude::*;
use std::io::{BufReader, BufWriter};
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use crate::errors::*; use crate::errors::*;
type CacheContents = Vec<Template>;
type CacheType = dyn Cache<CacheContents>;
/// Default user agent string used for api requests /// Default user agent string used for api requests
pub const DEFAULT_USER_AGENT: &str = "gitig"; pub const DEFAULT_USER_AGENT: &str = "gitig";
/// Default key used for cache
const CACHE_KEY: &str = "templates.json";
/// Response objects from github api /// Response objects from github api
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -65,9 +65,10 @@ impl Template {
} }
/// This struct holds the information about the templates available at github /// This struct holds the information about the templates available at github
#[derive(Serialize, Deserialize)]
pub struct GithubTemplates { pub struct GithubTemplates {
/// The templates /// The templates
templates: Vec<Template>,
templates: CacheContents,
} }
impl GithubTemplates { impl GithubTemplates {
@ -82,70 +83,15 @@ impl GithubTemplates {
.header(USER_AGENT, format!("{} {}", DEFAULT_USER_AGENT, env!("CARGO_PKG_VERSION"))) .header(USER_AGENT, format!("{} {}", DEFAULT_USER_AGENT, env!("CARGO_PKG_VERSION")))
.send() .send()
.chain_err(|| "Error while sending request to query all templates")?; .chain_err(|| "Error while sending request to query all templates")?;
let body: Vec<Template> = res.json().chain_err(|| "json")?; let body: Vec<Template> = res.json().chain_err(|| "json")?;
debug!("Received and deserialized {} templates", body.len()); debug!("Received and deserialized {} templates", body.len());
trace!("Serializing templates to file cache");
let cache = File::create(Self::cache_path())
.chain_err(|| "Error while creating file cache to write")?;
let writer = BufWriter::new(cache);
serde_json::to_writer(writer, &body)
.chain_err(|| "Error while serialzing templates to file cache")?;
debug!("Serialization of templates completed");
Ok(GithubTemplates { templates: body })
}
/// Returns whether there is an cached json version
fn is_cached() -> bool {
let path = Self::cache_path();
if !path.exists() {
return false;
}
let modified: SystemTime = path
.metadata()
.map(|meta| meta.modified().unwrap_or(SystemTime::UNIX_EPOCH))
.unwrap_or(SystemTime::UNIX_EPOCH);
let max_age = Duration::from_secs(60 * 24 * 2 /* two days */);
if modified.elapsed().unwrap_or(Duration::from_secs(u64::max_value())) > max_age {
debug!("Cache file is too older (> days), won't be used");
return false;
}
true
}
/// Returns the path of the file cache
fn cache_path() -> PathBuf {
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
}
/// Reads the templates from the file cache
fn from_cache() -> Result<GithubTemplates> {
trace!("Reading templates from file cache");
if !Self::is_cached() {
debug!("Templates response not yet cached");
return Err("Results are not yet cached".into());
}
let f = File::open(Self::cache_path())
.chain_err(|| "Error while opening cache file for templates")?;
let reader = BufReader::new(f);
let tpls: Vec<Template> = serde_json::from_reader(reader)
.chain_err(|| "Error while reading templates from file cache")?;
debug!("Deserialized {} templates from file cache", tpls.len());
Ok(GithubTemplates { templates: tpls })
Ok(GithubTemplates { templates: body })
} }
/// Creates a new struct with templates /// Creates a new struct with templates
pub fn new() -> Result<GithubTemplates> { Self::from_cache().or_else(|_| Self::from_server()) }
pub fn new() -> Result<GithubTemplates> { Self::from_server() }
/// Returns a list of the template names /// Returns a list of the template names
pub fn list_names(&self) -> Vec<&str> { pub fn list_names(&self) -> Vec<&str> {

Loading…
Cancel
Save