#![warn(missing_docs)]
use serde::de::DeserializeOwned;
pub use serde::{Deserialize, Serialize};
use std::{
collections::hash_map::{HashMap, Iter, Keys, Values, ValuesMut},
ffi::OsStr,
fmt::Debug,
fs::{self, File},
io::{prelude::*, SeekFrom},
ops::{Index, IndexMut},
path::{Path, PathBuf},
};
mod table_error;
pub use table_error::{TableBuilderError, TableError};
mod aux;
pub use aux::{ContentPolicy, ExtensionPolicy, RWPolicy, TableBuilder, TableMetadata, WriteType};
#[derive(Debug)]
pub struct TableElement<T> {
file: File,
pub info: T,
}
#[derive(Debug)]
pub struct Table<T>
where
T: Serialize + DeserializeOwned,
{
dir: PathBuf,
content: HashMap<String, TableElement<T>>,
metadata: TableMetadata,
is_modified: bool,
}
impl<T> Table<T>
where
T: Serialize + DeserializeOwned,
{
pub fn new<Q: AsRef<Path>>(dir: Q, metadata: TableMetadata) -> Result<Self, TableBuilderError> {
if metadata.rw_policy == RWPolicy::ReadOnly {
return Err(TableBuilderError::CreateWithoutWriteError);
}
match fs::metadata(&dir) {
Err(e) => match e.kind() {
std::io::ErrorKind::NotFound => {}
_ => return Err(e.into()),
},
Ok(_) => return Err(TableBuilderError::TableAlreadyExistsError),
};
fs::create_dir_all(&dir)?;
Ok(Table {
dir: dir.as_ref().to_path_buf(),
content: HashMap::new(),
metadata,
is_modified: false,
})
}
pub fn builder<Q: AsRef<Path>>(dir: Q) -> TableBuilder<T> {
TableBuilder::new(dir)
}
pub fn load<Q: AsRef<Path>>(
dir: Q,
metadata: Option<TableMetadata>,
) -> Result<Self, TableError> {
let metadata = metadata.unwrap_or_default();
let mut content = HashMap::<String, TableElement<T>>::new();
fs::read_dir(&dir)?.try_for_each(|dir_entry| {
let path = dir_entry?.path();
let jstr = OsStr::new("json");
if path.is_file() && Some(jstr) == path.extension() {
let name = path.file_name().unwrap().to_str().unwrap();
let (name, _) = name.rsplit_once('.').unwrap();
let file = match metadata.rw_policy {
RWPolicy::ReadOnly => File::open(&path),
RWPolicy::Write(_) => File::options().read(true).write(true).open(&path),
};
match file {
Ok(fi) => match serde_json::from_reader(&fi) {
Ok(info) => {
content.insert(name.to_string(), TableElement { file: fi, info });
Ok(())
}
Err(serde_error) => match metadata.content_policy {
ContentPolicy::IgnoreSerdeErrors => Ok(()),
ContentPolicy::PromoteSerdeErrors => Err(serde_error.into()),
},
},
Err(e) => Err(TableError::FileOpError(e)),
}
} else {
match metadata.extension_policy {
ExtensionPolicy::OnlyJsonFiles => Err(TableError::JsonError),
ExtensionPolicy::IgnoreNonJson => Ok(()),
}
}
})?;
Ok(Table {
metadata,
dir: dir.as_ref().to_path_buf(),
content,
is_modified: false,
})
}
pub fn push(&mut self, fname: &str, info_elem: T) -> Result<(), TableError> {
self.mod_permissions()?;
let mut f_elem_name = self.dir.clone();
f_elem_name.push(format!("{}.json", fname));
let f_elem = File::options()
.read(true)
.write(true)
.create_new(true)
.open(&f_elem_name)?;
let element = TableElement {
file: f_elem,
info: info_elem,
};
if let Some(e) = self.content.insert(fname.into(), element) {
drop(e.file);
fs::remove_file(f_elem_name)?;
return Err(TableError::PushError(fname.into()));
}
self.is_modified = true;
Ok(())
}
pub fn pop(&mut self, fname: &str) -> Result<(), TableError> {
self.mod_permissions()?;
self.is_modified = true;
match self.content.remove(fname) {
Some(_) => {
let mut f_elem = self.dir.clone();
f_elem.push(format!("{}.json", fname));
fs::remove_file(f_elem).map_err(|err| err.into())
}
None => Err(TableError::PopError(fname.to_string())),
}
}
pub fn soft_pop(&mut self, fname: &str, alt_name: Option<&str>) -> Result<(), TableError> {
self.mod_permissions()?;
match self.content.get(fname) {
Some(content) => {
let mut f_elem = self.dir.clone();
f_elem.push(format!("{}.json_soft_delete", alt_name.unwrap_or(fname)));
let file = File::options().write(true).create_new(true).open(f_elem)?;
serde_json::to_writer_pretty(file, &content.info)?;
self.pop(fname)?;
Ok(())
}
None => {
Err(TableError::PopError(fname.to_string()))
}
}
}
pub fn is_modified(&self) -> bool {
self.is_modified
}
pub fn get_table_keys(&self) -> Keys<String, TableElement<T>> {
self.content.keys()
}
pub fn iter(&self) -> Iter<String, TableElement<T>> {
self.content.iter()
}
pub fn get_table_content(&self) -> Values<String, TableElement<T>> {
self.content.values()
}
pub fn get_mut_table_content(&mut self) -> ValuesMut<String, TableElement<T>> {
self.is_modified = true;
self.content.values_mut()
}
pub fn get_element(&self, entry_name: &str) -> Option<&TableElement<T>> {
self.content.get(entry_name)
}
pub fn get_mut_element(&mut self, entry_name: &str) -> Option<&mut TableElement<T>> {
self.is_modified = true;
self.content.get_mut(entry_name)
}
pub fn write_back(&mut self) -> Result<(), TableError> {
self.mod_permissions()?;
if self.is_modified() {
self.is_modified = false;
for table_element in self.content.values_mut() {
let file = &mut table_element.file;
file.set_len(0)?;
file.seek(SeekFrom::Start(0))?;
serde_json::to_writer_pretty(file, &table_element.info)?;
}
}
Ok(())
}
pub fn len(&self) -> usize {
self.content.len()
}
pub fn is_empty(&self) -> bool {
self.content.is_empty()
}
fn mod_permissions(&self) -> Result<(), TableError> {
match self.metadata.rw_policy {
RWPolicy::Write(_) => Ok(()),
RWPolicy::ReadOnly => Err(TableError::NoWritePolicyError),
}
}
pub fn has_mod_permissions(&self) -> bool {
self.mod_permissions().is_ok()
}
}
impl<T> Table<T>
where
T: Serialize + DeserializeOwned + Clone,
{
pub fn append_clone<Q: AsRef<str>>(
&mut self,
fnames: &[Q],
elements: &[T],
) -> Result<(), TableError> {
if elements.len() != fnames.len() {
return Err(TableError::AppendLengthError);
}
for (element, fname) in elements.iter().zip(fnames) {
self.push(fname.as_ref(), element.clone())?;
}
Ok(())
}
pub fn rename(&mut self, old_name: &str, new_name: &str) -> Result<(), TableError> {
self.mod_permissions()?;
self.is_modified = true;
let name_string = old_name.to_string();
let info = self
.get_element(old_name)
.ok_or(TableError::PopError(name_string))?
.info
.clone();
self.pop(old_name)?;
self.push(new_name, info)?;
Ok(())
}
}
impl<T> Table<T>
where
T: Serialize + DeserializeOwned + Copy,
{
pub fn append<Q: AsRef<str>>(
&mut self,
fnames: &[Q],
elements: &[T],
) -> Result<(), TableError> {
if elements.len() != fnames.len() {
return Err(TableError::AppendLengthError);
}
for (&element, fname) in elements.iter().zip(fnames) {
self.push(fname.as_ref(), element)?;
}
Ok(())
}
}
impl<T> Index<&str> for Table<T>
where
T: Serialize + DeserializeOwned,
{
type Output = TableElement<T>;
fn index(&self, index: &str) -> &Self::Output {
&self.content[index]
}
}
impl<T> IndexMut<&str> for Table<T>
where
T: Serialize + DeserializeOwned,
{
fn index_mut(&mut self, index: &str) -> &mut Self::Output {
self.is_modified = true;
self.content.get_mut(index).unwrap()
}
}
impl<T> Drop for Table<T>
where
T: Serialize + DeserializeOwned,
{
fn drop(&mut self) {
if RWPolicy::Write(WriteType::Automatic) == self.metadata.rw_policy {
self.write_back().unwrap();
}
}
}