grus-lib 0.1.1

Library for accessing grus database
Documentation
use std::str;
use chrono::NaiveDateTime;
use sanakirja::{Commit, Env, Error, MutTxn};
use sanakirja::btree;
use crate::{invalid_data_error, StoreRw, ID_SQ, DB_DUE_DATES, DB_LINKS, DB_NAMES, DB_RLINKS, DB_RSESSIONS, DB_SESSIONS};
use crate::reader::ChildIds;
use crate::types::{DueDate, RTriple, Session};

pub type StoreWriter<'env> = StoreRw<MutTxn<&'env Env, ()>>;

impl<'env> StoreWriter<'env> {
	pub fn add_child(&mut self, pid: u64, name: &str) -> Result<u64, Error> {
		assert_ne!(name.len(), 0);
		let next = self.get_child(pid)?.unwrap_or(0);

		let id = self.id;
		btree::del(&mut self.txn, &mut self.links, &pid, None)?;
		btree::put(&mut self.txn, &mut self.links, &pid, &id)?;

		btree::put(&mut self.txn, &mut self.rlinks, &id, &RTriple { pid, next, prev: 0 })?;
		if next > 0 { self.modify_rt(next, pid, |rt| rt.prev = id)? };

		btree::put(&mut self.txn, &mut self.names, &id, name.as_bytes())?;
		self.id += 1;
		Ok(id)
	}

	pub fn add_session(&mut self, id: u64, session: &Session) -> Result<(), Error> {
		btree::put(&mut self.txn, &mut self.sessions, &id, session)?;
		btree::put(&mut self.txn, &mut self.rsessions, session, &id)?;
		Ok(())
	}

	pub fn delete(&mut self, pid: u64, id: u64) -> Result<(), Error> {
		btree::del(&mut self.txn, &mut self.links, &pid, Some(&id))?;

		let rt = self.get_rt(id, pid)?.ok_or_else(invalid_data_error)?;
		if rt.prev > 0 { self.modify_rt(rt.prev, pid, |prt| prt.next = rt.next)?; }
		else if rt.next > 0 { btree::put(&mut self.txn, &mut self.links, &pid, &rt.next)?; }
		if rt.next > 0 { self.modify_rt(rt.next, pid, |nrt| nrt.prev = rt.prev)? };

		self.delete_helper(pid, id)
	}

	pub fn delete_session(&mut self, id: u64, session: &Session) -> Result<(), Error> {
		btree::del(&mut self.txn, &mut self.sessions, &id, Some(session))?;
		btree::del(&mut self.txn, &mut self.rsessions, session, Some(&id))?;
		Ok(())
	}

	pub fn rename(&mut self, id: u64, name: &str) -> Result<(), Error> {
		btree::del(&mut self.txn, &mut self.names, &id, None)?;
		btree::put(&mut self.txn, &mut self.names, &id, name.as_bytes())?;
		Ok(())
	}

	pub fn set_due_date(&mut self, id: u64, date: NaiveDateTime) -> Result<(), Error> {
		btree::del(&mut self.txn, &mut self.due_dates, &id, None)?;
		btree::put(&mut self.txn, &mut self.due_dates, &id, &DueDate(date))?;
		Ok(())
	}

	pub fn unset_due_date(&mut self, id: u64) -> Result<(), Error> {
		btree::del(&mut self.txn, &mut self.due_dates, &id, None)?;
		Ok(())
	}

	pub fn move_up(&mut self, pid: u64, id: u64) -> Result<(), Error> {
		let rt = self.get_rt(id, pid)?.ok_or_else(invalid_data_error)?;
		let prt = if rt.prev > 0 {
			self.get_rt(rt.prev, pid)?.ok_or_else(invalid_data_error)?
		} else { return Ok(()) };

		self.modify_rt(id, pid, |crt| {
			crt.next = rt.prev;
			crt.prev = prt.prev;
		})?;

		if rt.next > 0 {
			self.modify_rt(rt.next, pid, |nrt| nrt.prev = rt.prev)?;
		}

		self.modify_rt(rt.prev, pid, |prt| {
			prt.next = rt.next;
			prt.prev = id;
		})?;

		if prt.prev > 0 {
			self.modify_rt(prt.prev, pid, |pprt| pprt.next = id)?;
		} else {
			btree::del(&mut self.txn, &mut self.links, &pid, None)?;
			btree::put(&mut self.txn, &mut self.links, &pid, &id)?;
		}

		Ok(())
	}

	pub fn move_down(&mut self, pid: u64, id: u64) -> Result<(), Error> {
		let rt = self.get_rt(id, pid)?.ok_or_else(invalid_data_error)?;
		let nrt = if rt.next > 0 {
			self.get_rt(rt.next, pid)?.ok_or_else(invalid_data_error)?
		} else { return Ok(()) };

		self.modify_rt(id, pid, |crt| {
			crt.prev = rt.next;
			crt.next = nrt.next;
		})?;

		if rt.prev > 0 {
			self.modify_rt(rt.prev, pid, |prt| prt.next = rt.next)?;
		} else {
			btree::del(&mut self.txn, &mut self.links, &pid, None)?;
			btree::put(&mut self.txn, &mut self.links, &pid, &rt.next)?;
		}

		self.modify_rt(rt.next, pid, |nrt| {
			nrt.prev = rt.prev;
			nrt.next = id;
		})?;

		if nrt.next > 0 {
			self.modify_rt(nrt.next, pid, |nnrt| nnrt.prev = id)?;
		}
		Ok(())
	}

	pub fn share(&mut self, src: u64, dest: u64) -> Result<bool, Error> {
		if self.is_descendent_of(dest, src)? { return Ok(false) };
		if self.get_rt(src, dest)?.is_some() { return Ok(false) };

		let next = self.get_child(dest)?.unwrap_or(0);

		btree::del(&mut self.txn, &mut self.links, &dest, None)?;
		btree::put(&mut self.txn, &mut self.links, &dest, &src)?;

		btree::put(&mut self.txn, &mut self.rlinks, &src, &RTriple { pid: dest, next, prev: 0 })?;
		if next > 0 { self.modify_rt(next, dest, |rt| rt.prev = src)? };

		Ok(true)
	}

	pub fn cut(&mut self, src_pid: u64, src: u64, dest: u64) -> Result<bool, Error> {
		if !self.share(src, dest)? { return Ok(false) };

		let is_head = btree::del(&mut self.txn, &mut self.links, &src_pid, Some(&src))?;

		let rt = self.get_rt(src, src_pid)?.ok_or_else(invalid_data_error)?;
		if is_head { btree::put(&mut self.txn, &mut self.links, &src_pid, &rt.next)?; }
		if rt.prev > 0 { self.modify_rt(rt.prev, src_pid, |prt| prt.next = rt.next)? };
		if rt.next > 0 { self.modify_rt(rt.next, src_pid, |nrt| nrt.prev = rt.prev)? };
		btree::del(&mut self.txn, &mut self.rlinks, &src, Some(&rt))?;

		Ok(true)
	}

	pub fn commit(mut self) -> Result<(), Error> {
		self.txn.set_root(ID_SQ, self.id);
		self.txn.set_root(DB_LINKS, self.links.db.into());
		self.txn.set_root(DB_RLINKS, self.rlinks.db.into());
		self.txn.set_root(DB_NAMES, self.names.db.into());
		self.txn.set_root(DB_DUE_DATES, self.due_dates.db.into());
		self.txn.set_root(DB_SESSIONS, self.sessions.db.into());
		self.txn.set_root(DB_RSESSIONS, self.rsessions.db.into());
		self.txn.commit()
	}

	fn modify_rt(&mut self, id: u64, pid: u64, f: impl Fn(&mut RTriple)) -> Result<(), Error> {
		let mut rt = self.get_rt(id, pid)?.ok_or_else(invalid_data_error)?;
		btree::del(&mut self.txn, &mut self.rlinks, &id, Some(&rt))?;
		f(&mut rt);
		btree::put(&mut self.txn, &mut self.rlinks, &id, &rt)?;
		Ok(())
	}

	fn delete_helper(&mut self, pid: u64, id: u64) -> Result<(), Error> {
		let rt = self.get_rt(id, pid)?.ok_or_else(invalid_data_error)?;
		btree::del(&mut self.txn, &mut self.rlinks, &id, Some(&rt))?;

		if let Some((&eid, _)) = btree::get(&self.txn, &self.rlinks, &id, None)? {
			if eid == id { return Ok(()) };
		}

		btree::del(&mut self.txn, &mut self.names, &id, None)?;
		btree::del(&mut self.txn, &mut self.due_dates, &id, None)?;
		self.delete_id_sessions(id)?;

		let mut child_ids = ChildIds::new(self, id)?;
		if let Some(first) = child_ids.next(self)? {
			btree::del(&mut self.txn, &mut self.links, &id, Some(&first))?;
			self.delete(id, first)?;
		}
		while let Some(child_id) = child_ids.next(self)? {
			self.delete(id, child_id)?;
		}
		Ok(())
	}

	fn is_descendent_of(&self, subj: u64, pred: u64) -> Result<bool, Error> {
		if subj == pred { return Ok(true) };
		for child_id in self.child_ids(pred)? {
			if self.is_descendent_of(subj, child_id?)? { return Ok(true) };
		}
		Ok(false)
	}

	fn delete_id_sessions(&mut self, id: u64) -> Result<(), Error> {
		while let Some((&eid, &session)) = btree::get(&self.txn, &self.sessions, &id, None)? {
			if eid != id { break };
			self.delete_session(id, &session)?;
		}
		Ok(())
	}
}

#[cfg(test)]
mod tests {
	use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
	use sanakirja::{Env, Error};
	use crate::Store;
	use crate::types::Session;

	#[test]
	fn add_child() -> Result<(), Error> {
		let store = Store { env: Env::new_anon(1 << 14, 2)? };
		store.create_base()?;

		let mut writer = store.writer()?;
		writer.add_child(0, "first")?;
		writer.add_child(1, "second")?;
		writer.commit()?;

		let reader = store.reader()?;
		let mut iter = reader.child_ids(0)?;

		let id = iter.next().unwrap()?;
		let data = reader.name(id)?.unwrap();
		assert_eq!(id, 1);
		assert_eq!(data, "first");
		assert!(iter.next().is_none());

		let mut iter = reader.child_ids(1)?;
		let id = iter.next().unwrap()?;
		let data = reader.name(id)?.unwrap();
		assert_eq!(id, 2);
		assert_eq!(data, "second");
		assert!(iter.next().is_none());

		Ok(())
	}

	#[test]
	fn add_session() -> Result<(), Error> {
		let store = Store { env: Env::new_anon(1 << 14, 2)? };
		store.create_base()?;

		let mut writer = store.writer()?;
		let start = NaiveDateTime::new(
			NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
			NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
		);
		let end = NaiveDateTime::new(
			NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
			NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
		);
		let session = Session { start, end };
		writer.add_session(0, &session)?;
		writer.commit()?;

		let reader = store.reader()?;
		assert_eq!(reader.first_session(0)?.unwrap(), session);

		Ok(())
	}
}