use crate::errors::*;
use hex::ToHex;
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use sha2::Digest;
use std::io::Seek;
use std::path::Path;
use std::time::{Duration, Instant};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
struct CacheEntry<T> {
what: String,
val: T,
}
pub struct CachedValue<T> {
pub fetch: Option<Duration>,
pub update: Option<Duration>,
pub creation: Option<Duration>,
pub total: Duration,
pub value: T,
}
pub struct Cached<T> {
cache_file: fd_lock::RwLock<fs_err::File>,
what: String,
_phantom: std::marker::PhantomData<T>,
}
impl<'a, T> Cached<T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(what: impl AsRef<str>, cache_dir: impl AsRef<Path>) -> Result<Self> {
let what = what.as_ref();
let what_digest = sha2::Sha256::digest(what.as_bytes());
let cache_dir = cache_dir.as_ref();
fs_err::create_dir_all(cache_dir)?;
let cache_file = cache_dir.join(what_digest.as_slice().encode_hex::<String>());
let cache_file = fs_err::OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(cache_file)?;
Ok(Self {
cache_file: fd_lock::RwLock::new(cache_file),
what: what.to_owned(),
_phantom: std::marker::PhantomData,
})
}
pub fn fetch_or_update(
&mut self,
create: impl FnOnce(&str) -> Result<T>,
) -> Result<CachedValue<T>> {
let total_start = Instant::now();
match self.fetch() {
Ok(Some(value)) => {
let elapsed = total_start.elapsed();
Ok(CachedValue {
value,
fetch: Some(elapsed),
update: None,
creation: None,
total: elapsed,
})
}
Ok(None) => {
let fetch = Some(total_start.elapsed());
let creation_start = Instant::now();
let value = create(self.what.as_str())?;
let creation = Some(creation_start.elapsed());
let update_start = Instant::now();
if let Err(err) = self.update(&value) {
log::warn!("Failed to write value to cached: {err:?}");
}
let update = Some(update_start.elapsed());
let total = total_start.elapsed();
Ok(CachedValue {
value,
fetch,
update,
creation,
total,
})
}
Err(err) => {
log::warn!("Overriding existing value that failed to load: {err:?}");
let fetch = Some(total_start.elapsed());
let creation_start = Instant::now();
let value = create(self.what.as_str())?;
let creation = Some(creation_start.elapsed());
let update_start = Instant::now();
if let Err(err) = self.update(&value) {
log::warn!("Failed to update cached: {err:?}");
}
let update = Some(update_start.elapsed());
let total = total_start.elapsed();
Ok(CachedValue {
value,
fetch,
update,
creation,
total,
})
}
}
}
pub fn fetch(&mut self) -> Result<Option<T>> {
let guard = self.cache_file.read()?;
let buf = std::io::BufReader::new(guard.file());
match bincode::deserialize_from(buf) {
Ok(CacheEntry { what, val }) => {
if what == self.what {
log::debug!("Cached value with matching what \"{what}\"");
Ok(Some(val))
} else {
log::warn!(
"Cached value what \"{}\" does not match expect what \"{}\", removing",
what,
self.what
);
Ok(None)
}
}
Err(e) => {
log::warn!("Failed to load cached value: {e:?}");
Ok(None)
}
}
}
pub fn update(&mut self, val: &T) -> Result<()> {
let mut write_guard = self.cache_file.write()?;
let entry = CacheEntry {
what: self.what.clone(),
val,
};
let encoded: Vec<u8> = bincode::serialize(&entry).unwrap();
let mut encoded = &encoded[..];
let file = write_guard.file_mut();
file.rewind()?;
std::io::copy(&mut encoded, file)?;
let loco = file.stream_position()?;
file.set_len(loco)?;
Ok(())
}
}