use std::path::Path;
use automerge::{
Automerge,
Change,
ChangeHash,
};
use thiserror::Error;
use crate::{
changes::{
self,
FileStore,
},
index,
traits::{
ChangeStore,
ReadableStorage,
WritableStorage,
},
};
#[derive(Debug, Error)]
pub enum Error {
#[error("missing change: {0}")]
MissingChange(ChangeHash),
#[error(transparent)]
Automerge(#[from] automerge::AutomergeError),
#[error(transparent)]
Changes(#[from] changes::fs::Error),
#[error(transparent)]
Index(#[from] index::Error),
}
pub struct Storage {
changes: FileStore,
index: index::Sanakirja,
}
impl Storage {
pub fn new(root: impl AsRef<Path>) -> Result<Self, Error> {
let changes = FileStore::new(root.as_ref().join("changes"));
let index = index::Sanakirja::new(Some(&root.as_ref().join("index")))?;
Ok(Self { changes, index })
}
pub fn readable(&self) -> Result<Readable, Error> {
Ok(Readable {
changes: self.changes.clone(),
tx: self.index.txn_begin()?,
})
}
pub fn writable(&self) -> Result<Writable<()>, Error> {
Ok(Writable {
changes: self.changes.clone(),
tx: self.index.mut_txn_begin()?,
})
}
}
pub type Readable = Tx<index::Txn>;
pub type Writable<T> = Tx<index::MutTxn<T>>;
pub struct Tx<T> {
changes: FileStore,
tx: T,
}
impl<T: sanakirja::Commit> Tx<T> {
pub fn commit(self) -> Result<(), Error> {
Ok(sanakirja::Commit::commit(self.tx).map_err(index::Error::from)?)
}
}
impl<T> ReadableStorage for Tx<index::GenericTxn<T>>
where
T: sanakirja::LoadPage<Error = sanakirja::Error>,
{
type Error = Error;
type Doc = index::Doc;
fn load(&self, doc: &str) -> Result<Option<Automerge>, Error> {
let stored = self.tx.get_doc(doc)?;
if let Some(doc) = stored {
let mut am = Automerge::new();
for change in doc.changes(&self.tx) {
let (_, hash) = change?;
let change = self
.changes
.get_change(&hash)?
.ok_or_else(|| Error::MissingChange(hash))?;
am.apply_changes(Some(change))?;
}
Ok(Some(am))
} else {
Ok(None)
}
}
fn exists(&self, doc: &str) -> Result<bool, Self::Error> {
Ok(self.tx.has_doc(doc)?)
}
fn doc(&self, doc: &str) -> Result<Option<Self::Doc>, Self::Error> {
Ok(self.tx.get_doc(doc)?)
}
}
impl<T> WritableStorage for Writable<T> {
type Error = Error;
fn store(
&mut self,
doc: &str,
changes: impl IntoIterator<Item = Change>,
) -> Result<(), Self::Error> {
let mut stored = self.tx.create_doc(doc)?;
for change in changes {
let hash = change.hash();
self.changes.write_change(change)?;
stored.put_change(&mut self.tx, hash)?;
}
Ok(())
}
}