#![cfg_attr(docsrs, feature(doc_cfg))]
use async_std::sync::{Mutex, RwLock};
use std::future::Future;
use std::marker::PhantomData;
mod error;
pub use error::KoitError;
pub mod backend;
pub use backend::Backend;
pub mod format;
pub use format::Format;
#[derive(Debug)]
pub struct Database<D, B, F> {
data: RwLock<D>,
backend: Mutex<B>,
_format: PhantomData<F>,
}
impl<D, B, F> Database<D, B, F>
where
B: Backend,
F: Format<D>,
{
pub fn from_parts(data: D, backend: B) -> Self {
Self {
data: RwLock::new(data),
backend: Mutex::new(backend),
_format: PhantomData,
}
}
pub async fn write<T, R>(&self, task: T) -> R
where
T: FnOnce(&mut D) -> R,
{
let mut data = self.data.write().await;
task(&mut data)
}
pub async fn write_and_then<T, Fut, R>(&self, task: T) -> R
where
T: FnOnce(&mut D) -> Fut,
Fut: Future<Output = R>,
{
let mut data = self.data.write().await;
task(&mut data).await
}
pub async fn read<T, R>(&self, task: T) -> R
where
T: FnOnce(&D) -> R,
{
let data = self.data.read().await;
task(&data)
}
pub async fn read_and_then<T, Fut, R>(&self, task: T) -> R
where
T: FnOnce(&D) -> Fut,
Fut: Future<Output = R>,
{
let data = self.data.read().await;
task(&data).await
}
pub async fn replace(&self, data: D) -> D {
self
.write(|actual_data| std::mem::replace(actual_data, data))
.await
}
pub fn get_data_lock(&self) -> &RwLock<D> {
&self.data
}
pub fn get_data_mut(&mut self) -> &mut D {
self.data.get_mut()
}
pub async fn save(&self) -> Result<(), KoitError> {
let mut backend = self.backend.lock().await;
let data = self.data.read().await;
backend
.write(F::to_bytes(&data).map_err(|err| KoitError::ToFormat(err.into()))?)
.await
.map_err(|err| KoitError::BackendWrite(err.into()))?;
Ok(())
}
async fn load_from_backend(&self) -> Result<D, KoitError> {
let mut backend = self.backend.lock().await;
let bytes = backend
.read()
.await
.map_err(|err| KoitError::BackendRead(err.into()))?;
Ok(F::from_bytes(bytes).map_err(|err| KoitError::FromFormat(err.into()))?)
}
pub async fn reload(&self) -> Result<D, KoitError> {
let new_data = self.load_from_backend().await?;
Ok(self.replace(new_data).await)
}
pub fn into_parts(self) -> (D, B) {
(self.data.into_inner(), self.backend.into_inner())
}
}
#[cfg(feature = "file-backend")]
#[cfg_attr(docsrs, doc(cfg(feature = "file-backend")))]
pub type FileDatabase<D, F> = Database<D, backend::File, F>;
#[cfg(feature = "file-backend")]
impl<D, F> FileDatabase<D, F>
where
F: Format<D>,
{
pub async fn load_from_path<P>(path: P) -> Result<Self, KoitError>
where
P: AsRef<std::path::Path>,
{
let mut backend = backend::File::from_path(path)
.await
.map_err(|err| KoitError::BackendCreation(err.into()))?;
let bytes = backend
.read()
.await
.map_err(|err| KoitError::BackendRead(err.into()))?;
let data = F::from_bytes(bytes).map_err(|err| KoitError::FromFormat(err.into()))?;
Ok(Database {
data: RwLock::new(data),
backend: Mutex::new(backend),
_format: PhantomData,
})
}
pub async fn load_from_path_or_else<P, T>(path: P, factory: T) -> Result<Self, KoitError>
where
P: AsRef<std::path::Path>,
T: FnOnce() -> D,
{
let (mut backend, exists) = backend::File::from_path_or_create(path)
.await
.map_err(|e| KoitError::BackendCreation(e.into()))?;
let data = if exists {
let bytes = backend
.read()
.await
.map_err(|err| KoitError::BackendRead(err.into()))?;
F::from_bytes(bytes).map_err(|err| KoitError::FromFormat(err.into()))?
} else {
factory()
};
let db = Database {
data: RwLock::new(data),
backend: Mutex::new(backend),
_format: PhantomData,
};
db.save().await?;
Ok(db)
}
pub async fn load_from_path_or_default<P>(path: P) -> Result<Self, KoitError>
where
P: AsRef<std::path::Path>,
D: std::default::Default,
{
Self::load_from_path_or_else(path, std::default::Default::default).await
}
}