use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
use std::marker::PhantomData;
use std::sync::Mutex;
use anyhow::Context;
use anyhow::Result;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json::{Map, Value};
use tracing::error;
use crate::util::lium_paths::gen_path_in_lium_dir;
pub struct KvCache<T: Serialize + DeserializeOwned + Sized + Clone + Debug> {
name: &'static str,
map: Mutex<Option<HashMap<String, T>>>,
file: Mutex<Option<File>>,
_value_type: PhantomData<T>,
}
impl<T: Serialize + DeserializeOwned + Sized + Clone + Debug> KvCache<T> {
pub const fn new(name: &'static str) -> Self {
Self {
name,
map: Mutex::new(None),
file: Mutex::new(None),
_value_type: PhantomData::<T>,
}
}
pub fn clear(&self) -> Result<()> {
self.load_cache_file()?;
{
let mut map = self.map.lock().unwrap();
let map = map.as_mut().unwrap();
map.clear();
}
self.sync()
}
fn create_file(&self, remove: bool) -> Result<()> {
let path =
gen_path_in_lium_dir(self.name).context("Failed to generate a cache file path")?;
if remove {
std::fs::remove_file(&path).context("Failed to remove the file")?;
}
let mut f = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?;
if f.metadata()?.len() == 0 {
f.write_all(serde_json::to_string(&Map::<String, Value>::new())?.as_bytes())?;
f.sync_all()?;
}
let mut file = self.file.lock().expect("lock failed");
*file = Some(f);
Ok(())
}
fn load_cache_file(&self) -> Result<()> {
let file = self.file.lock().expect("lock failed");
let has_file = file.is_some();
drop(file);
if !has_file {
self.create_file(false)?;
}
let mut file_lock = self.file.lock().expect("lock failed");
let file = file_lock.as_mut().expect("File is not initialized yet");
file.rewind()?;
let mut json = String::new();
file.read_to_string(&mut json)?;
drop(file_lock);
match serde_json::from_str(&json) {
Ok(data) => {
*self.map.lock().unwrap() = data;
Ok(())
}
Err(e) => {
error!("Failed to parse the cache: {e:?}");
error!("Creating a cache file again...");
self.create_file(true)?;
*self.map.lock().unwrap() = Some(HashMap::new());
Ok(())
}
}
}
pub fn get(&self, key: &str) -> Result<Option<T>> {
self.load_cache_file()?;
let mut map = self.map.lock().unwrap();
let map = map.as_mut().unwrap();
Ok(map.get(key).cloned())
}
pub fn set(&self, key: &str, value: T) -> Result<()> {
self.load_cache_file()?;
{
let mut map = self.map.lock().unwrap();
let map = map.as_mut().unwrap();
map.insert(key.to_string(), value);
}
self.sync()?;
Ok(())
}
pub fn remove(&self, key: &str) -> Result<Option<T>> {
self.load_cache_file()?;
let old = {
let mut map = self.map.lock().unwrap();
let map = map.as_mut().unwrap();
map.remove(key)
};
self.sync()?;
Ok(old)
}
pub fn sync(&self) -> Result<()> {
let mut map = self.map.lock().unwrap();
let map = map.as_mut().unwrap();
let mut file = self.file.lock().expect("lock failed");
let file = file.as_mut().expect("File is not initialized yet");
file.set_len(0)?;
file.rewind()?;
file.write_all(serde_json::to_string(map)?.as_bytes())?;
file.sync_all().context("failed to sync backed file")
}
pub fn entries(&self) -> Result<HashMap<String, T>> {
self.load_cache_file()?;
let map = self.map.lock().unwrap();
Ok((*map).clone().unwrap())
}
pub fn get_or_else(&self, key: &str, f: &dyn Fn(&str) -> Result<T>) -> Result<T> {
if let Some(s) = self.get(key)? {
Ok(s)
} else {
let value = f(key);
if let Ok(value) = &value {
self.set(key, value.clone())?;
}
value
}
}
}