summaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
Diffstat (limited to 'src/document')
-rw-r--r--src/document/replica.rs29
-rw-r--r--src/document/store.rs23
2 files changed, 50 insertions, 2 deletions
diff --git a/src/document/replica.rs b/src/document/replica.rs
index af807ea..e24f93c 100644
--- a/src/document/replica.rs
+++ b/src/document/replica.rs
@@ -9,7 +9,7 @@
use std::fmt;
use std::ops::{Deref, DerefMut};
-use automerge::AutoCommit;
+use automerge::{AutoCommit, ChangeHash};
/// One generation of the swarm document.
///
@@ -77,14 +77,39 @@ impl fmt::Debug for Epoch {
///
/// Derefs to the document, so code that only needs the `AutoCommit` keeps working
/// with a guard of `Replica`.
+///
+/// The heads are cached: `AutoCommit::get_heads` takes `&mut self` (it closes a pending
+/// transaction), so without the cache every keepalive and status read would need the
+/// write lock. Every path that changes the document under the write lock calls
+/// [`Replica::refresh_heads`] before releasing it (`AppState::write_doc`, `mutate_doc`,
+/// `switch_epoch`, the sync apply); readers take the read lock and use
+/// [`Replica::heads`].
pub struct Replica {
pub epoch: Epoch,
pub doc: AutoCommit,
+ heads: Vec<ChangeHash>,
}
impl Replica {
pub fn new(epoch: Epoch, doc: AutoCommit) -> Self {
- Self { epoch, doc }
+ let mut replica = Self {
+ epoch,
+ doc,
+ heads: Vec::new(),
+ };
+ replica.refresh_heads();
+ replica
+ }
+
+ /// The document's heads as of the last refresh, sorted.
+ pub fn heads(&self) -> &[ChangeHash] {
+ &self.heads
+ }
+
+ /// Call after changing the document (under the write lock).
+ pub fn refresh_heads(&mut self) {
+ self.heads = self.doc.get_heads();
+ self.heads.sort_unstable();
}
}
diff --git a/src/document/store.rs b/src/document/store.rs
index 9e18693..573b811 100644
--- a/src/document/store.rs
+++ b/src/document/store.rs
@@ -243,6 +243,29 @@ pub fn keys_at(doc: &AutoCommit, path: &[String]) -> Result<Vec<String>, StoreEr
Ok(merged_keys(doc, &maps).into_iter().collect())
}
+/// Every concurrent value at `path`, newest first; empty if there is none. Where
+/// `read_path` picks one (or merges concurrent maps into one), this keeps them apart.
+pub(crate) fn read_candidates(
+ doc: &AutoCommit,
+ path: &[String],
+) -> Result<Vec<JsonValue>, StoreError> {
+ let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?;
+ let mut maps = vec![automerge::ROOT];
+ for segment in parents {
+ maps = match resolve(doc, &maps, segment, None)? {
+ Some(Resolved::Maps(next)) => next,
+ _ => return Ok(Vec::new()),
+ };
+ }
+ candidates(doc, &maps, leaf)?
+ .into_iter()
+ .map(|(value, id)| match value {
+ Value::Scalar(scalar) => scalar_to_json(scalar.into_owned()),
+ Value::Object(_) => object_to_json(doc, &id, None),
+ })
+ .collect()
+}
+
/// Writes (replaces) the value at `path`, creating intermediate maps.
pub fn write_path(
doc: &mut AutoCommit,