use crate::Model;
use crate::adapters::{Adapter, FromPath};
use crate::{
JoydbError, Relation,
state::{GetRelation, State},
};
use std::fmt::Debug;
use std::ops::Drop;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[derive(Debug)]
pub struct Joydb<S: State, A: Adapter> {
inner: Arc<Mutex<InnerJoydb<S, A>>>,
}
impl<S: State, A: Adapter> Clone for Joydb<S, A> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<S: State, A: Adapter + FromPath> Joydb<S, A> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, JoydbError> {
let adapter = A::from_path(path);
let config = JoydbConfig {
mode: JoydbMode::Persistent {
adapter,
sync_policy: SyncPolicy::Instant,
},
};
Self::open_with_config(config)
}
}
impl<S: State, A: Adapter> Joydb<S, A> {
pub fn new_in_memory() -> Result<Self, JoydbError> {
let config = JoydbConfig {
mode: JoydbMode::InMemory,
};
Self::open_with_config(config)
}
pub fn open_with_config(config: JoydbConfig<A>) -> Result<Self, JoydbError> {
let maybe_sync_policy = config.sync_policy();
let inner: InnerJoydb<S, A> = InnerJoydb::open_with_config(config)?;
let arc_inner = Arc::new(Mutex::new(inner));
if let Some(SyncPolicy::Periodic(duration)) = maybe_sync_policy {
let weak_inner_db = Arc::downgrade(&arc_inner);
spawn_periodic_sync_thread(duration, weak_inner_db);
}
Ok(Self { inner: arc_inner })
}
pub fn insert<M: Model>(&self, model: &M) -> Result<(), JoydbError>
where
S: GetRelation<M>,
{
self.inner.lock().unwrap().insert(model)
}
pub fn get<M: Model>(&self, id: &M::Id) -> Result<Option<M>, JoydbError>
where
S: GetRelation<M>,
{
self.inner.lock().unwrap().get(id)
}
pub fn get_all<M: Model>(&self) -> Result<Vec<M>, JoydbError>
where
S: GetRelation<M>,
{
self.inner.lock().unwrap().get_all()
}
pub fn get_all_by<M, F>(&self, predicate: F) -> Result<Vec<M>, JoydbError>
where
M: Model,
S: GetRelation<M>,
F: Fn(&M) -> bool,
{
self.inner.lock().unwrap().get_all_by(predicate)
}
pub fn count<M: Model>(&self) -> Result<usize, JoydbError>
where
S: GetRelation<M>,
{
self.inner.lock().unwrap().count()
}
pub fn update<M: Model>(&self, new_record: &M) -> Result<(), JoydbError>
where
S: GetRelation<M>,
{
self.inner.lock().unwrap().update(new_record)
}
pub fn upsert<M: Model>(&self, record: &M) -> Result<(), JoydbError>
where
S: GetRelation<M>,
{
self.inner.lock().unwrap().upsert(record)
}
pub fn delete<M: Model>(&self, id: &M::Id) -> Result<Option<M>, JoydbError>
where
S: GetRelation<M>,
{
self.inner.lock().unwrap().delete(id)
}
pub fn delete_all_by<M, F>(&self, predicate: F) -> Result<Vec<M>, JoydbError>
where
M: Model,
S: GetRelation<M>,
F: Fn(&M) -> bool,
{
self.inner.lock().unwrap().delete_all_by(predicate)
}
pub fn flush(&self) -> Result<(), JoydbError> {
self.inner.lock().unwrap().flush()
}
}
#[derive(Debug)]
struct InnerJoydb<S: State, A: Adapter> {
state: S,
mode: JoydbMode<A>,
}
impl<S: State, A: Adapter> InnerJoydb<S, A> {
fn open_with_config(config: JoydbConfig<A>) -> Result<Self, JoydbError> {
let JoydbConfig { mode } = config;
let state = match &mode {
JoydbMode::Persistent {
adapter,
sync_policy: _,
} => adapter.load_state::<S>()?,
JoydbMode::InMemory => S::default(),
};
Ok(Self { state, mode })
}
fn flush(&mut self) -> Result<(), JoydbError> {
if self.is_dirty() {
self.write_state()?;
self.state.reset_dirty();
}
Ok(())
}
fn write_state(&mut self) -> Result<(), JoydbError> {
match &self.mode {
JoydbMode::Persistent {
adapter,
sync_policy: _,
} => adapter.write_state(&self.state),
JoydbMode::InMemory => {
Ok(())
}
}
}
fn is_dirty(&self) -> bool {
self.state.is_dirty()
}
fn get_relation_mut<M: Model>(&mut self) -> &mut Relation<M>
where
S: GetRelation<M>,
{
let state = &mut self.state;
<S as GetRelation<M>>::get_relation_mut(state)
}
fn get_relation<M: Model>(&self) -> &Relation<M>
where
S: GetRelation<M>,
{
let state = &self.state;
<S as GetRelation<M>>::get_relation(state)
}
fn insert<M: Model>(&mut self, model: &M) -> Result<(), JoydbError>
where
S: GetRelation<M>,
{
let relation = self.get_relation_mut::<M>();
relation.insert(model)?;
self.after_change()?;
Ok(())
}
fn get<M: Model>(&self, id: &M::Id) -> Result<Option<M>, JoydbError>
where
S: GetRelation<M>,
{
let relation = self.get_relation::<M>();
relation.get(id)
}
fn get_all<M: Model>(&self) -> Result<Vec<M>, JoydbError>
where
S: GetRelation<M>,
{
let relation = self.get_relation::<M>();
relation.get_all()
}
pub(crate) fn get_all_by<M, F>(&self, predicate: F) -> Result<Vec<M>, JoydbError>
where
M: Model,
S: GetRelation<M>,
F: Fn(&M) -> bool,
{
let relation = self.get_relation::<M>();
relation.get_all_by(predicate)
}
pub fn count<M: Model>(&self) -> Result<usize, JoydbError>
where
S: GetRelation<M>,
{
let relation = self.get_relation::<M>();
relation.count()
}
fn update<M: Model>(&mut self, new_record: &M) -> Result<(), JoydbError>
where
S: GetRelation<M>,
{
let relation = self.get_relation_mut::<M>();
relation.update(new_record)?;
self.after_change()?;
Ok(())
}
fn upsert<M: Model>(&mut self, record: &M) -> Result<(), JoydbError>
where
S: GetRelation<M>,
{
let relation = self.get_relation_mut::<M>();
relation.upsert(record)?;
self.after_change()?;
Ok(())
}
fn delete<M: Model>(&mut self, id: &M::Id) -> Result<Option<M>, JoydbError>
where
S: GetRelation<M>,
{
let relation = self.get_relation_mut::<M>();
let maybe_deleted_record = relation.delete(id)?;
if maybe_deleted_record.is_some() {
self.after_change()?;
}
Ok(maybe_deleted_record)
}
pub fn delete_all_by<M, F>(&mut self, predicate: F) -> Result<Vec<M>, JoydbError>
where
M: Model,
S: GetRelation<M>,
F: Fn(&M) -> bool,
{
let relation = self.get_relation_mut::<M>();
let deleted_records = relation.delete_all_by(predicate)?;
if !deleted_records.is_empty() {
self.after_change()?;
}
Ok(deleted_records)
}
fn after_change(&mut self) -> Result<(), JoydbError> {
if self.mode.is_instant_sync_policy() {
self.flush()?;
}
Ok(())
}
}
impl<S: State, A: Adapter> Drop for InnerJoydb<S, A> {
fn drop(&mut self) {
if let Err(err) = self.flush() {
eprintln!("Failed to flush the database: {}", err);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SyncPolicy {
Instant,
Periodic(Duration),
Manual,
}
#[derive(Debug)]
pub struct JoydbConfig<A: Adapter> {
pub mode: JoydbMode<A>,
}
impl<A: Adapter> JoydbConfig<A> {
fn sync_policy(&self) -> Option<SyncPolicy> {
match &self.mode {
JoydbMode::Persistent { sync_policy, .. } => Some(*sync_policy),
JoydbMode::InMemory => None,
}
}
}
#[derive(Debug)]
pub enum JoydbMode<A: Adapter> {
Persistent {
adapter: A,
sync_policy: SyncPolicy,
},
InMemory,
}
impl<A: Adapter> JoydbMode<A> {
fn is_instant_sync_policy(&self) -> bool {
match self {
JoydbMode::Persistent { sync_policy, .. } => *sync_policy == SyncPolicy::Instant,
JoydbMode::InMemory => false,
}
}
}
fn spawn_periodic_sync_thread<S: State, A: Adapter>(
interval: Duration,
weak_inner_db: std::sync::Weak<Mutex<InnerJoydb<S, A>>>,
) {
std::thread::spawn(move || {
loop {
std::thread::sleep(interval);
if let Some(inner) = weak_inner_db.upgrade() {
inner
.lock()
.expect("Failed to lock the Joydb database from the background thread")
.flush()
.expect("Failed to flush the Joydb database from the background thread");
} else {
break;
}
}
});
}