BUKUHTU2FHX7K5KJC434LFCR7CCP7DIVNDCWWCXNG5Q2Q2SPS23AC
// Copyright © 2023 Kim Altintop <kim@eagain.io>
// SPDX-License-Identifier: GPL-2.0-only
use automerge::{
Automerge,
Change,
ChangeHash,
};
pub trait WritableStorage {
type Error;
fn store(
&mut self,
doc: &str,
changes: impl IntoIterator<Item = Change>,
) -> Result<(), Self::Error>;
}
pub trait ReadableStorage {
type Error;
type Doc;
fn load(&self, doc: &str) -> Result<Option<Automerge>, Self::Error>;
fn exists(&self, doc: &str) -> Result<bool, Self::Error>;
fn doc(&self, doc: &str) -> Result<Option<Self::Doc>, Self::Error>;
}
pub trait ChangeStore {
type Error;
fn write_change(&self, change: Change) -> Result<(), Self::Error>;
fn get_change(&self, hash: &ChangeHash) -> Result<Option<Change>, Self::Error>;
fn has_change(&self, hash: &ChangeHash) -> Result<bool, Self::Error>;
}
#[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>>;
mod fs;
pub use fs::FileStore;
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;
pub trait ChangeStore {
type 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)?;
}
// Copyright © 2023 Kim Altintop <kim@eagain.io>
// SPDX-License-Identifier: GPL-2.0-only
pub mod fs;
pub use fs::FileStore;