use crate::error::{Error, Result};
use parseit::reader;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[derive(Debug, Serialize, Deserialize)]
pub struct CacheData<Data> {
pub data: Data,
pub timestamp: u64,
}
impl<Data> CacheData<Data> {
pub fn new(data: Data, path: &Path) -> Result<Self> {
Ok(Self {
data,
timestamp: Self::get_timestamp(path)?,
})
}
pub fn get_timestamp(path: &Path) -> Result<u64> {
Ok(fs::metadata(&path)?
.modified()?
.duration_since(SystemTime::UNIX_EPOCH)?
.as_secs())
}
}
#[derive(Debug)]
pub struct Cache {
cache_dir: PathBuf,
}
impl Cache {
pub fn init() -> Result<Self> {
Ok(Self {
cache_dir: dirs_next::cache_dir().ok_or_else(|| {
Error::CacheError(String::from("cannot access the cache directory"))
})?,
})
}
fn get_cache_path(&self, label: &str) -> PathBuf {
self.cache_dir
.join(env!("CARGO_PKG_NAME"))
.join(label)
.with_extension("json")
}
pub fn exists(&self, label: &str) -> bool {
self.get_cache_path(label).exists()
}
pub fn read<T: DeserializeOwned>(&self, label: &str) -> Result<CacheData<T>> {
let raw_data = reader::read_to_string(self.get_cache_path(label))?;
Ok(serde_json::from_str(&raw_data)?)
}
pub fn write<T: Serialize>(&self, data: CacheData<T>, label: &str) -> Result<()> {
let cache_path = self.get_cache_path(label);
if !cache_path.exists() {
fs::create_dir_all(self.cache_dir.join(env!("CARGO_PKG_NAME")))?;
};
let mut file = File::create(&cache_path)?;
file.write_all(serde_json::to_string(&data)?.as_bytes())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_cache() -> Result<()> {
let cache = Cache::init()?;
let data = String::from("cache_test");
let cache_data = CacheData::new(
&data,
&Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"),
)?;
cache.write(cache_data, "data")?;
assert!(cache.exists("data"));
assert_eq!(data, cache.read::<String>("data")?.data);
Ok(())
}
}