use std::{
collections::HashMap,
fs::File,
io::BufReader,
path::{Path, PathBuf},
};
use anyhow::Context;
use nitro_shared::{pkg::PackageSearchParameters, util::utc_timestamp};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
pub struct PackageSearchCache {
max_age: u64,
contents: CacheContents,
path: PathBuf,
}
impl PackageSearchCache {
pub fn open(path: impl AsRef<Path>, max_age: u64) -> anyhow::Result<Self> {
let contents = if path.as_ref().exists() {
serde_json::from_reader(BufReader::new(File::open(path.as_ref())?)).unwrap_or_default()
} else {
let default = CacheContents::default();
let _ = serde_json::to_writer(File::create(path.as_ref())?, &default);
default
};
Ok(Self {
max_age,
contents,
path: path.as_ref().to_path_buf(),
})
}
pub fn check<D: DeserializeOwned>(&self, search: &PackageSearchParameters) -> Option<D> {
if search.search.is_some() {
return None;
}
let search = serde_json::to_string(search).ok()?;
let entry = self.contents.entries.get(&search)?;
let timestamp = utc_timestamp().ok()?;
if timestamp - entry.timestamp > self.max_age {
None
} else {
let results = serde_json::from_value(entry.results.clone()).ok()?;
Some(results)
}
}
pub fn write<S: Serialize>(
&mut self,
search: &PackageSearchParameters,
results: S,
) -> anyhow::Result<()> {
let search = serde_json::to_string(search).context("Failed to stringify search")?;
let results = serde_json::to_value(results).context("Failed to serialize results")?;
let timestamp = utc_timestamp().context("Failed to get UTC timestamp")?;
self.contents
.entries
.insert(search, CacheEntry { timestamp, results });
serde_json::to_writer(File::create(&self.path)?, &self.contents)
.context("Failed to write to cache")
}
}
#[derive(Serialize, Deserialize, Default)]
struct CacheContents {
entries: HashMap<String, CacheEntry>,
}
#[derive(Serialize, Deserialize)]
struct CacheEntry {
timestamp: u64,
results: serde_json::Value,
}