use chrono; use chrono::Duration; use serde::{Deserialize, Serialize}; use serde_json; use std::collections::HashMap; use std::env; use std::fmt; use std::fs; use std::io; use std::io::prelude::*; use std::path::PathBuf; #[derive(Serialize, Deserialize, Debug)] pub struct CacheEntry { uuid: String, changed: chrono::DateTime, } impl CacheEntry { pub fn changed(&self) -> chrono::DateTime { self.changed } pub fn expired(&self, ttl: Duration) -> bool { let now = chrono::Utc::now(); now > self.changed + ttl } pub fn uuid(&self) -> &String { return &self.uuid; } } #[derive(Serialize, Deserialize, Debug)] pub struct Cache { paths: HashMap, device_id: Option, } #[derive(Debug)] pub enum Error { Io(io::Error), Json(serde_json::Error), } impl Error { pub fn message(&self) -> String { match *self { Self::Io(ref e) => format!("{}", e), Self::Json(ref e) => format!("{}", e), } } } impl From for Error { fn from(error: io::Error) -> Self { Self::Io(error) } } impl From for Error { fn from(error: serde_json::Error) -> Self { Self::Json(error) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.message()) } } impl Cache { pub fn new() -> Self { Self { paths: HashMap::new(), device_id: None, } } pub fn load(path: &str) -> Result { if let Ok(mut file) = fs::File::open(path) { let mut contents = String::new(); file.read_to_string(&mut contents)?; let cache = serde_json::from_str(&contents); match cache { Ok(c) => Ok(c), Err(e) => Err(e.into()), } } else { Ok(Self::new()) } } pub fn get(&self, path: &str) -> Option<&CacheEntry> { if let Some(entry) = self.paths.get(path) { return Some(&entry); } None } pub fn update(&mut self, path: &str, uuid: &str) { let entry = CacheEntry { uuid: uuid.into(), changed: chrono::Utc::now(), }; self.paths.insert(path.into(), entry); } pub fn save(&self, path: &str) -> Result<(), Error> { let path = PathBuf::from(path); match path.parent() { Some(p) => { if !p.is_dir() { fs::create_dir_all(p)?; } }, None => {}, }; let mut file = fs::File::create(path)?; let contents = serde_json::to_string(&self)?; file.write_all(&contents.as_bytes())?; Ok(()) } pub fn device_id(&self) -> Option<&str> { match &self.device_id { Some(p) => Some(p), None => None, } } pub fn set_device_id(&mut self, device_id: &str) { self.device_id = Some(device_id.into()); } } pub fn get_cache_dir() -> PathBuf { let mut dir = match env::var("XDG_CACHE_HOME") { Ok(v) => PathBuf::from(&v), Err(_) => match env::var("HOME") { Ok(v) => [v, ".cache".into()].iter().collect(), Err(_) => ".".into(), }, }; dir.push(env!("CARGO_PKG_NAME")); dir }