use std::collections::BTreeSet;
use std::io;
use std::path::PathBuf;
use convert_case::Case;
use convert_case::Casing;
use gen_file::folder;
use serde::Deserialize;
use serde::Serialize;
use uuid::Uuid;
use crate::index;
use crate::temporary;
pub fn init_db_folder(target_folder: &TargetFolder) -> io::Result<()> {
let path: PathBuf = target_folder.path.join(temporary::FOLDER_NAME);
folder::remove_if_exists(&path)?;
folder::create(&path)?;
Ok(())
}
pub fn delete_db_folder(target_folder: &TargetFolder) -> io::Result<()> {
folder::remove_if_exists(&target_folder.path)?;
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TargetFolder {
pub path: PathBuf
}
impl TargetFolder {
pub fn from(path: &PathBuf) -> TargetFolder {
TargetFolder {
path: path.to_path_buf()
}
}
}
#[derive(Debug, Clone, PartialEq, Hash, Eq, Serialize, Deserialize)]
pub struct Capsule<T> {
pub inner: T,
pub id: Uuid,
pub key: String,
pub folder: TargetFolder,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Persist<T> {
pub inner: T,
}
pub trait Indexable {
fn index_keys(&self) -> std::collections::BTreeSet<String>;
}
pub trait FileDbKey {
fn file_db_key() -> String;
}
pub fn save_capsule<T: Serialize + Clone + for<'a> Deserialize<'a> + FileDbKey>(capsule: Capsule<T>, index_keys: BTreeSet<String>) -> io::Result<Capsule<T>> {
if let Some(id) = if !index_keys.is_empty()
{ index::index(index_keys, &capsule.key, &capsule.id, &capsule.folder)? } else { None } {
Capsule::<T>::get_by_id(&id, &capsule.folder)
} else {
temporary::save(capsule)
}
}
pub fn upsert_capsule<T: Serialize + Clone + for<'a> Deserialize<'a> + FileDbKey>(capsule: Capsule<T>, index_keys: BTreeSet<String>) -> io::Result<Capsule<T>> {
if let Some(id) = if !index_keys.is_empty()
{ index::index(index_keys, &capsule.key, &capsule.id, &capsule.folder)? } else { None } {
let mut found = Capsule::<T>::get_by_id(&id, &capsule.folder)?;
found.inner = capsule.inner;
temporary::save(found)
} else {
temporary::save(capsule)
}
}
pub fn save_object<T: Serialize + Clone + FileDbKey>(data: &T, target_folder: &TargetFolder) -> io::Result<T> {
temporary::create(target_folder, &T::file_db_key().to_case(Case::Snake), data)?;
Ok(data.clone())
}
impl <T: Serialize + for<'a> Deserialize<'a> + Clone + FileDbKey> Capsule<T> {
pub fn update(&self) -> io::Result<Capsule<T>>
where
T: Indexable,
{
let capsule = self.clone();
if !self.inner.index_keys().is_empty() {
let _ = index::update(self.inner.index_keys(), &capsule.key, &capsule.id, &capsule.folder);
}
temporary::save(capsule)
}
pub fn get_by_id(id: &Uuid, target_folder: &TargetFolder) -> io::Result<Capsule<T>> {
temporary::select::<Capsule<T>>(target_folder, &format!("{}_{}", T::file_db_key().to_case(Case::Snake), id))
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("cannot find {} {} in database", T::file_db_key(), id)))
}
pub fn get_all(target_folder: &TargetFolder) -> io::Result<Vec<Capsule<T>>> {
let id_vec = index::get_all_by_type(&T::file_db_key().to_case(Case::Snake), target_folder)?;
Ok(id_vec
.into_iter()
.flat_map(|id| Capsule::<T>::get_by_id(&id, target_folder))
.collect())
}
pub fn find_by_index(keys: &[&str], target_folder: &TargetFolder) -> io::Result<Vec<Capsule<T>>> {
let id_vec = index::find_by_type(&T::file_db_key().to_case(Case::Snake), keys, target_folder)?;
Ok(id_vec
.into_iter()
.flat_map(|id| Capsule::<T>::get_by_id(&id, target_folder))
.collect())
}
}
impl <T: Serialize + for<'a> Deserialize<'a> + Clone + FileDbKey> Persist<T> {
pub fn get(target_folder: &TargetFolder) -> io::Result<T> {
temporary::select::<T>(target_folder, &T::file_db_key().to_case(Case::Snake))
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("cannot find {} in database", T::file_db_key())))
}
}
#[cfg(test)]
#[path = "../tests/unit_tests/persistence.rs"]
pub mod test;