gitig 0.3.1

A cli utility to manage gitignore files easily
//! Module to implement a simple cache
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;

/// Trait for a simple cache
///
/// Each struct implementing this trais has to offer functions to `get`, `set` an object and check
/// if an value `exists`.
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;
}

/// A File cache
#[derive(Debug)]
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<Self> {
        if !root.exists() {
            std::fs::create_dir_all(root)
                .chain_err(|| "Error while creating dirs for file cache")?;
        }
        Ok(Self { root: root.clone(), ttl })
    }

    /// Checks whether an element for the given key exists
    pub 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);
        let age = modified.elapsed().unwrap_or(Duration::from_secs(u64::max_value()));
        debug!("Age of cache file: {} s", age.as_secs());

        if age > self.ttl {
            debug!("Cache file is too old (> {} seconds), won't be used", self.ttl.as_secs());
            return false;
        }

        true
    }

    /// Stores a `data` under the specified `key`
    pub fn set<T>(&self, key: &str, data: &T) -> Result<()>
    where T: 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(())
    }

    /// Retrieves data for `key`
    pub fn get<T>(&self, key: &str) -> Result<T>
    where T: 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: T = serde_json::from_reader(reader)
            .chain_err(|| "Error while reading templates from file cache")?;
        debug!("Deserialized {} templates from file cache", "some");
        Ok(obj)
    }
}