In the example_cli we were not always committing (seemingly by mistake). This then caused all the examples to have to compensate by manually committing.
110 lines
3.5 KiB
Rust
110 lines
3.5 KiB
Rust
use core::convert::Infallible;
|
|
|
|
use crate::Append;
|
|
|
|
/// `Persist` wraps a [`PersistBackend`] (`B`) to create a convenient staging area for changes (`C`)
|
|
/// before they are persisted.
|
|
///
|
|
/// Not all changes to the in-memory representation needs to be written to disk right away, so
|
|
/// [`Persist::stage`] can be used to *stage* changes first and then [`Persist::commit`] can be used
|
|
/// to write changes to disk.
|
|
#[derive(Debug)]
|
|
pub struct Persist<B, C> {
|
|
backend: B,
|
|
stage: C,
|
|
}
|
|
|
|
impl<B, C> Persist<B, C>
|
|
where
|
|
B: PersistBackend<C>,
|
|
C: Default + Append,
|
|
{
|
|
/// Create a new [`Persist`] from [`PersistBackend`].
|
|
pub fn new(backend: B) -> Self {
|
|
Self {
|
|
backend,
|
|
stage: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Stage a `changeset` to be committed later with [`commit`].
|
|
///
|
|
/// [`commit`]: Self::commit
|
|
pub fn stage(&mut self, changeset: C) {
|
|
self.stage.append(changeset)
|
|
}
|
|
|
|
/// Get the changes that have not been committed yet.
|
|
pub fn staged(&self) -> &C {
|
|
&self.stage
|
|
}
|
|
|
|
/// Commit the staged changes to the underlying persistence backend.
|
|
///
|
|
/// Changes that are committed (if any) are returned.
|
|
///
|
|
/// # Error
|
|
///
|
|
/// Returns a backend-defined error if this fails.
|
|
pub fn commit(&mut self) -> Result<Option<C>, B::WriteError> {
|
|
if self.stage.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
self.backend
|
|
.write_changes(&self.stage)
|
|
// if written successfully, take and return `self.stage`
|
|
.map(|_| Some(core::mem::take(&mut self.stage)))
|
|
}
|
|
|
|
/// Stages a new changeset and commits it (along with any other previously staged changes) to
|
|
/// the persistence backend
|
|
///
|
|
/// Convience method for calling [`stage`] and then [`commit`].
|
|
///
|
|
/// [`stage`]: Self::stage
|
|
/// [`commit`]: Self::commit
|
|
pub fn stage_and_commit(&mut self, changeset: C) -> Result<Option<C>, B::WriteError> {
|
|
self.stage(changeset);
|
|
self.commit()
|
|
}
|
|
}
|
|
|
|
/// A persistence backend for [`Persist`].
|
|
///
|
|
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures
|
|
/// that are to be persisted, or retrieved from persistence.
|
|
pub trait PersistBackend<C> {
|
|
/// The error the backend returns when it fails to write.
|
|
type WriteError: core::fmt::Debug;
|
|
|
|
/// The error the backend returns when it fails to load changesets `C`.
|
|
type LoadError: core::fmt::Debug;
|
|
|
|
/// Writes a changeset to the persistence backend.
|
|
///
|
|
/// It is up to the backend what it does with this. It could store every changeset in a list or
|
|
/// it inserts the actual changes into a more structured database. All it needs to guarantee is
|
|
/// that [`load_from_persistence`] restores a keychain tracker to what it should be if all
|
|
/// changesets had been applied sequentially.
|
|
///
|
|
/// [`load_from_persistence`]: Self::load_from_persistence
|
|
fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>;
|
|
|
|
/// Return the aggregate changeset `C` from persistence.
|
|
fn load_from_persistence(&mut self) -> Result<Option<C>, Self::LoadError>;
|
|
}
|
|
|
|
impl<C> PersistBackend<C> for () {
|
|
type WriteError = Infallible;
|
|
|
|
type LoadError = Infallible;
|
|
|
|
fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn load_from_persistence(&mut self) -> Result<Option<C>, Self::LoadError> {
|
|
Ok(None)
|
|
}
|
|
}
|