use std::{
collections::{hash_map::DefaultHasher, HashMap},
fs::{File, OpenOptions},
hash::{Hash, Hasher},
io::{Seek, Write},
marker::PhantomData,
};
use anyhow::Result;
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use serde::{de::DeserializeOwned, Serialize};
pub struct Cache<K, V> {
cache_file: File,
cache: HashMap<u64, V>,
dirty: bool,
_key: PhantomData<K>,
}
pub const CACHE_PATH: &str = ".aquascope-cache";
fn hash<K: Hash>(k: &K) -> u64 {
let mut hasher = DefaultHasher::new();
k.hash(&mut hasher);
hasher.finish()
}
impl<K: Hash, V: Serialize + DeserializeOwned> Cache<K, V> {
pub fn load() -> Result<Self> {
let cache_file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(CACHE_PATH)?;
let cache = if cache_file.metadata()?.len() == 0 {
HashMap::new()
} else {
let mut decoder = GzDecoder::new(&cache_file);
serde_json::from_reader(&mut decoder).unwrap_or_else(|e| {
eprintln!("Warning: failed to read Aquascope cache with error {e}");
HashMap::new()
})
};
Ok(Cache {
cache,
cache_file,
dirty: false,
_key: PhantomData,
})
}
pub fn get(&self, key: &K) -> Option<&V> {
self.cache.get(&hash(key))
}
pub fn set(&mut self, key: K, value: V) {
self.cache.insert(hash(&key), value);
self.dirty = true;
}
pub fn save(&mut self) -> Result<()> {
if self.dirty {
self.cache_file.set_len(0)?;
self.cache_file.rewind()?;
let mut encoder =
GzEncoder::new(&mut self.cache_file, Compression::best());
serde_json::to_writer(&mut encoder, &self.cache)?;
encoder.finish()?;
self.cache_file.flush()?;
}
Ok(())
}
}