use std::ops::RangeBounds;
use crate::automerge::SaveOptions;
use crate::automerge::{current_state, diff};
use crate::exid::ExId;
use crate::iter::{Keys, ListRange, MapRange, Values};
use crate::marks::{ExpandMark, Mark, MarkSet};
use crate::patches::{PatchLog, TextRepresentation};
use crate::sync::SyncDoc;
use crate::transaction::{CommitOptions, Transactable};
use crate::types::Clock;
use crate::{hydrate, OnPartialLoad};
use crate::{sync, ObjType, Parents, Patch, ReadDoc, ScalarValue};
use crate::{
transaction::TransactionInner, ActorId, Automerge, AutomergeError, Change, ChangeHash, Cursor,
Prop, Value,
};
use crate::{LoadOptions, VerificationMode};
#[derive(Debug, Clone)]
pub struct AutoCommit {
pub(crate) doc: Automerge,
transaction: Option<(PatchLog, TransactionInner)>,
patch_log: PatchLog,
diff_cursor: Vec<ChangeHash>,
diff_cache: Option<(OpRange, Vec<Patch>)>,
save_cursor: Vec<ChangeHash>,
isolation: Option<Vec<ChangeHash>>,
}
impl Default for AutoCommit {
fn default() -> Self {
AutoCommit {
doc: Automerge::new(),
transaction: None,
patch_log: PatchLog::inactive(TextRepresentation::default()),
diff_cursor: Vec::new(),
diff_cache: None,
save_cursor: Vec::new(),
isolation: None,
}
}
}
impl AutoCommit {
pub fn new() -> AutoCommit {
AutoCommit::default()
}
pub fn load(data: &[u8]) -> Result<Self, AutomergeError> {
let doc = Automerge::load(data)?;
Ok(Self {
doc,
transaction: None,
patch_log: PatchLog::inactive(TextRepresentation::default()),
diff_cursor: Vec::new(),
diff_cache: None,
save_cursor: Vec::new(),
isolation: None,
})
}
pub fn load_unverified_heads(data: &[u8]) -> Result<Self, AutomergeError> {
let doc = Automerge::load_unverified_heads(data)?;
Ok(Self {
doc,
transaction: None,
patch_log: PatchLog::inactive(TextRepresentation::default()),
diff_cursor: Vec::new(),
diff_cache: None,
save_cursor: Vec::new(),
isolation: None,
})
}
#[deprecated(since = "0.5.2", note = "use `load_with_options` instead")]
pub fn load_with(
data: &[u8],
on_error: OnPartialLoad,
mode: VerificationMode,
) -> Result<Self, AutomergeError> {
Self::load_with_options(
data,
LoadOptions::new()
.on_partial_load(on_error)
.verification_mode(mode),
)
}
pub fn load_with_options(
data: &[u8],
options: LoadOptions<'_>,
) -> Result<Self, AutomergeError> {
let doc = Automerge::load_with_options(data, options)?;
Ok(Self {
doc,
transaction: None,
patch_log: PatchLog::inactive(TextRepresentation::default()),
diff_cursor: Vec::new(),
diff_cache: None,
save_cursor: Vec::new(),
isolation: None,
})
}
pub fn reset_diff_cursor(&mut self) {
self.ensure_transaction_closed();
self.patch_log = PatchLog::inactive(TextRepresentation::default());
self.diff_cursor = Vec::new();
}
pub fn update_diff_cursor(&mut self) {
self.ensure_transaction_closed();
self.patch_log.set_active(true);
self.patch_log.truncate();
self.diff_cursor = self.doc.get_heads();
}
pub fn diff_cursor(&self) -> Vec<ChangeHash> {
self.diff_cursor.clone()
}
pub fn make_patches(&self, patch_log: &mut PatchLog) -> Vec<Patch> {
self.doc.make_patches(patch_log)
}
pub fn diff(&mut self, before: &[ChangeHash], after: &[ChangeHash]) -> Vec<Patch> {
self.ensure_transaction_closed();
let range = OpRange::new(before, after);
if let Some((r, patches)) = &self.diff_cache {
if r == &range {
return patches.clone();
}
}
let heads = self.doc.get_heads();
let patches = if range.after() == heads
&& range.before() == self.diff_cursor
&& self.patch_log.is_active()
{
self.patch_log.make_patches(&self.doc)
} else if range.before().is_empty() && range.after() == heads {
let mut patch_log = PatchLog::active(self.patch_log.text_rep());
patch_log.heads = None;
current_state::log_current_state_patches(&self.doc, &mut patch_log);
patch_log.make_patches(&self.doc)
} else {
let before_clock = self.doc.clock_at(range.before());
let after_clock = self.doc.clock_at(range.after());
let mut patch_log = PatchLog::active(self.patch_log.text_rep());
patch_log.heads = Some(range.after().to_vec());
diff::log_diff(&self.doc, &before_clock, &after_clock, &mut patch_log);
patch_log.make_patches(&self.doc)
};
self.diff_cache = Some((range, patches));
self.diff_cache.as_ref().unwrap().1.clone()
}
pub fn diff_incremental(&mut self) -> Vec<Patch> {
self.ensure_transaction_closed();
let heads = self.doc.get_heads();
let diff_cursor = self.diff_cursor();
let patches = self.diff(&diff_cursor, &heads);
self.update_diff_cursor();
patches
}
pub fn fork(&mut self) -> Self {
self.ensure_transaction_closed();
Self {
doc: self.doc.fork(),
transaction: self.transaction.clone(),
patch_log: PatchLog::inactive(self.patch_log.text_rep()),
diff_cursor: vec![],
diff_cache: None,
save_cursor: vec![],
isolation: None,
}
}
pub fn fork_at(&mut self, heads: &[ChangeHash]) -> Result<Self, AutomergeError> {
self.ensure_transaction_closed();
Ok(Self {
doc: self.doc.fork_at(heads)?,
transaction: self.transaction.clone(),
patch_log: PatchLog::inactive(self.patch_log.text_rep()),
diff_cursor: vec![],
diff_cache: None,
save_cursor: vec![],
isolation: None,
})
}
#[doc(hidden)]
pub fn document(&mut self) -> &Automerge {
self.ensure_transaction_closed();
&self.doc
}
pub fn with_actor(mut self, actor: ActorId) -> Self {
self.ensure_transaction_closed();
self.doc.set_actor(actor);
self
}
pub fn set_actor(&mut self, actor: ActorId) -> &mut Self {
self.ensure_transaction_closed();
self.doc.set_actor(actor);
self
}
pub fn get_actor(&self) -> &ActorId {
self.doc.get_actor()
}
pub fn isolate(&mut self, heads: &[ChangeHash]) {
self.ensure_transaction_closed();
self.patch_to(heads);
self.isolation = Some(heads.to_vec())
}
pub fn integrate(&mut self) {
self.ensure_transaction_closed();
self.patch_to(self.doc.get_heads().as_slice());
self.isolation = None;
}
fn ensure_transaction_open(&mut self) {
if self.transaction.is_none() {
let args = self.doc.transaction_args(self.isolation.as_deref());
let inner = TransactionInner::new(args);
self.transaction = Some((self.patch_log.branch(), inner))
}
}
fn ensure_transaction_closed(&mut self) {
if let Some((patch_log, tx)) = self.transaction.take() {
self.patch_log.merge(patch_log);
let hash = tx.commit(&mut self.doc, None, None);
if self.isolation.is_some() && hash.is_some() {
self.isolation = hash.map(|h| vec![h])
}
}
}
pub fn load_incremental(&mut self, data: &[u8]) -> Result<usize, AutomergeError> {
self.ensure_transaction_closed();
if self.isolation.is_some() {
self.doc
.load_incremental_log_patches(data, &mut PatchLog::null())
} else {
self.doc
.load_incremental_log_patches(data, &mut self.patch_log)
}
}
pub fn apply_changes(
&mut self,
changes: impl IntoIterator<Item = Change>,
) -> Result<(), AutomergeError> {
self.ensure_transaction_closed();
if self.isolation.is_some() {
self.doc
.apply_changes_log_patches(changes, &mut PatchLog::null())
} else {
self.doc
.apply_changes_log_patches(changes, &mut self.patch_log)
}
}
pub fn merge(&mut self, other: &mut AutoCommit) -> Result<Vec<ChangeHash>, AutomergeError> {
self.ensure_transaction_closed();
other.ensure_transaction_closed();
if self.isolation.is_some() {
self.doc
.merge_and_log_patches(&mut other.doc, &mut PatchLog::null())
} else {
self.doc
.merge_and_log_patches(&mut other.doc, &mut self.patch_log)
}
}
pub fn save(&mut self) -> Vec<u8> {
self.save_with_options(SaveOptions::default())
}
pub fn save_with_options(&mut self, options: SaveOptions) -> Vec<u8> {
self.ensure_transaction_closed();
let bytes = self.doc.save_with_options(options);
if !bytes.is_empty() {
self.save_cursor = self.doc.get_heads()
}
bytes
}
pub fn save_and_verify(&mut self) -> Result<Vec<u8>, AutomergeError> {
let bytes = self.save();
Self::load(&bytes)?;
Ok(bytes)
}
pub fn save_nocompress(&mut self) -> Vec<u8> {
self.save_with_options(SaveOptions {
deflate: false,
..Default::default()
})
}
pub fn save_incremental(&mut self) -> Vec<u8> {
self.ensure_transaction_closed();
let bytes = self.doc.save_after(&self.save_cursor);
if !bytes.is_empty() {
self.save_cursor = self.doc.get_heads()
}
bytes
}
pub fn save_after(&mut self, heads: &[ChangeHash]) -> Vec<u8> {
self.ensure_transaction_closed();
self.doc.save_after(heads)
}
pub fn get_missing_deps(&mut self, heads: &[ChangeHash]) -> Vec<ChangeHash> {
self.ensure_transaction_closed();
self.doc.get_missing_deps(heads)
}
pub fn get_last_local_change(&mut self) -> Option<&Change> {
self.ensure_transaction_closed();
self.doc.get_last_local_change()
}
pub fn get_changes(&mut self, have_deps: &[ChangeHash]) -> Vec<&Change> {
self.ensure_transaction_closed();
self.doc.get_changes(have_deps)
}
pub fn get_change_by_hash(&mut self, hash: &ChangeHash) -> Option<&Change> {
self.ensure_transaction_closed();
self.doc.get_change_by_hash(hash)
}
pub fn get_changes_added<'a>(&mut self, other: &'a mut Self) -> Vec<&'a Change> {
self.ensure_transaction_closed();
other.ensure_transaction_closed();
self.doc.get_changes_added(&other.doc)
}
#[doc(hidden)]
pub fn import(&self, s: &str) -> Result<(ExId, ObjType), AutomergeError> {
self.doc.import(s)
}
#[doc(hidden)]
pub fn import_obj(&self, s: &str) -> Result<ExId, AutomergeError> {
self.doc.import_obj(s)
}
#[doc(hidden)]
pub fn dump(&mut self) {
self.ensure_transaction_closed();
self.doc.dump()
}
#[cfg(feature = "optree-visualisation")]
pub fn visualise_optree(&self, objects: Option<Vec<ExId>>) -> String {
self.doc.visualise_optree(objects)
}
pub fn get_heads(&mut self) -> Vec<ChangeHash> {
self.ensure_transaction_closed();
if let Some(i) = &self.isolation {
i.clone()
} else {
self.doc.get_heads()
}
}
pub fn set_text_rep(&mut self, text_rep: TextRepresentation) {
self.patch_log.set_text_rep(text_rep)
}
pub fn get_text_rep(&mut self) -> TextRepresentation {
self.patch_log.text_rep()
}
pub fn with_text_rep(mut self, text_rep: TextRepresentation) -> Self {
self.patch_log.set_text_rep(text_rep);
self
}
pub fn commit(&mut self) -> Option<ChangeHash> {
self.commit_with(CommitOptions::default())
}
pub fn commit_with(&mut self, options: CommitOptions) -> Option<ChangeHash> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.take().unwrap();
self.patch_log.merge(patch_log);
let hash = tx.commit(&mut self.doc, options.message, options.time);
if self.isolation.is_some() && hash.is_some() {
self.isolation = hash.map(|h| vec![h])
}
hash
}
pub fn rollback(&mut self) -> usize {
self.transaction
.take()
.map(|(_, tx)| tx.rollback(&mut self.doc))
.unwrap_or(0)
}
pub fn empty_change(&mut self, options: CommitOptions) -> ChangeHash {
self.ensure_transaction_closed();
let args = self.doc.transaction_args(None);
TransactionInner::empty(&mut self.doc, args, options.message, options.time)
}
pub fn sync(&mut self) -> impl SyncDoc + '_ {
self.ensure_transaction_closed();
SyncWrapper { inner: self }
}
pub fn hash_for_opid(&self, opid: &ExId) -> Option<ChangeHash> {
self.doc.hash_for_opid(opid)
}
pub fn hydrate(&self, heads: Option<&[ChangeHash]>) -> hydrate::Value {
self.doc.hydrate(heads)
}
fn get_scope(&self, heads: Option<&[ChangeHash]>) -> Option<Clock> {
if let Some(h) = heads {
return Some(self.doc.clock_at(h));
}
match (&self.isolation, &self.transaction) {
(Some(_), Some((_, t))) => t.get_scope().clone(),
(Some(i), None) => Some(self.doc.clock_at(i)),
_ => None,
}
}
fn patch_to(&mut self, after: &[ChangeHash]) {
let before = self.get_heads();
if before.as_slice() != after {
let before_clock = self.doc.clock_at(&before);
let after_clock = self.doc.clock_at(after);
diff::log_diff(&self.doc, &before_clock, &after_clock, &mut self.patch_log);
}
}
}
impl ReadDoc for AutoCommit {
fn parents<O: AsRef<ExId>>(&self, obj: O) -> Result<Parents<'_>, AutomergeError> {
self.doc.parents_for(obj.as_ref(), self.get_scope(None))
}
fn parents_at<O: AsRef<ExId>>(
&self,
obj: O,
heads: &[ChangeHash],
) -> Result<Parents<'_>, AutomergeError> {
self.doc
.parents_for(obj.as_ref(), self.get_scope(Some(heads)))
}
fn keys<O: AsRef<ExId>>(&self, obj: O) -> Keys<'_> {
self.doc.keys_for(obj.as_ref(), self.get_scope(None))
}
fn keys_at<O: AsRef<ExId>>(&self, obj: O, heads: &[ChangeHash]) -> Keys<'_> {
self.doc.keys_for(obj.as_ref(), self.get_scope(Some(heads)))
}
fn map_range<'a, O: AsRef<ExId>, R: RangeBounds<String> + 'a>(
&'a self,
obj: O,
range: R,
) -> MapRange<'a, R> {
self.doc
.map_range_for(obj.as_ref(), range, self.get_scope(None))
}
fn map_range_at<'a, O: AsRef<ExId>, R: RangeBounds<String> + 'a>(
&'a self,
obj: O,
range: R,
heads: &[ChangeHash],
) -> MapRange<'a, R> {
self.doc
.map_range_for(obj.as_ref(), range, self.get_scope(Some(heads)))
}
fn list_range<O: AsRef<ExId>, R: RangeBounds<usize>>(
&self,
obj: O,
range: R,
) -> ListRange<'_, R> {
self.doc
.list_range_for(obj.as_ref(), range, self.get_scope(None))
}
fn list_range_at<O: AsRef<ExId>, R: RangeBounds<usize>>(
&self,
obj: O,
range: R,
heads: &[ChangeHash],
) -> ListRange<'_, R> {
self.doc
.list_range_for(obj.as_ref(), range, self.get_scope(Some(heads)))
}
fn values<O: AsRef<ExId>>(&self, obj: O) -> Values<'_> {
self.doc.values_for(obj.as_ref(), self.get_scope(None))
}
fn values_at<O: AsRef<ExId>>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> {
self.doc
.values_for(obj.as_ref(), self.get_scope(Some(heads)))
}
fn length<O: AsRef<ExId>>(&self, obj: O) -> usize {
self.doc.length_for(obj.as_ref(), self.get_scope(None))
}
fn length_at<O: AsRef<ExId>>(&self, obj: O, heads: &[ChangeHash]) -> usize {
self.doc
.length_for(obj.as_ref(), self.get_scope(Some(heads)))
}
fn object_type<O: AsRef<ExId>>(&self, obj: O) -> Result<ObjType, AutomergeError> {
self.doc.object_type(obj)
}
fn marks<O: AsRef<ExId>>(&self, obj: O) -> Result<Vec<Mark<'_>>, AutomergeError> {
self.doc.marks_for(obj.as_ref(), self.get_scope(None))
}
fn marks_at<O: AsRef<ExId>>(
&self,
obj: O,
heads: &[ChangeHash],
) -> Result<Vec<Mark<'_>>, AutomergeError> {
self.doc
.marks_for(obj.as_ref(), self.get_scope(Some(heads)))
}
fn get_marks<O: AsRef<ExId>>(
&self,
obj: O,
index: usize,
heads: Option<&[ChangeHash]>,
) -> Result<MarkSet, AutomergeError> {
self.doc
.get_marks_for(obj.as_ref(), index, self.get_scope(heads))
}
fn text<O: AsRef<ExId>>(&self, obj: O) -> Result<String, AutomergeError> {
self.doc.text_for(obj.as_ref(), self.get_scope(None))
}
fn text_at<O: AsRef<ExId>>(
&self,
obj: O,
heads: &[ChangeHash],
) -> Result<String, AutomergeError> {
self.doc.text_for(obj.as_ref(), self.get_scope(Some(heads)))
}
fn get_cursor<O: AsRef<ExId>>(
&self,
obj: O,
position: usize,
at: Option<&[ChangeHash]>,
) -> Result<Cursor, AutomergeError> {
self.doc
.get_cursor_for(obj.as_ref(), position, self.get_scope(at))
}
fn get_cursor_position<O: AsRef<ExId>>(
&self,
obj: O,
address: &Cursor,
at: Option<&[ChangeHash]>,
) -> Result<usize, AutomergeError> {
self.doc
.get_cursor_position_for(obj.as_ref(), address, self.get_scope(at))
}
fn get<O: AsRef<ExId>, P: Into<Prop>>(
&self,
obj: O,
prop: P,
) -> Result<Option<(Value<'_>, ExId)>, AutomergeError> {
self.doc
.get_for(obj.as_ref(), prop.into(), self.get_scope(None))
}
fn get_at<O: AsRef<ExId>, P: Into<Prop>>(
&self,
obj: O,
prop: P,
heads: &[ChangeHash],
) -> Result<Option<(Value<'_>, ExId)>, AutomergeError> {
self.doc
.get_for(obj.as_ref(), prop.into(), self.get_scope(Some(heads)))
}
fn get_all<O: AsRef<ExId>, P: Into<Prop>>(
&self,
obj: O,
prop: P,
) -> Result<Vec<(Value<'_>, ExId)>, AutomergeError> {
self.doc
.get_all_for(obj.as_ref(), prop.into(), self.get_scope(None))
}
fn get_all_at<O: AsRef<ExId>, P: Into<Prop>>(
&self,
obj: O,
prop: P,
heads: &[ChangeHash],
) -> Result<Vec<(Value<'_>, ExId)>, AutomergeError> {
self.doc
.get_all_for(obj.as_ref(), prop.into(), self.get_scope(Some(heads)))
}
fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec<ChangeHash> {
self.doc.get_missing_deps(heads)
}
fn get_change_by_hash(&self, hash: &ChangeHash) -> Option<&Change> {
self.doc.get_change_by_hash(hash)
}
}
impl Transactable for AutoCommit {
fn pending_ops(&self) -> usize {
self.transaction
.as_ref()
.map(|(_, t)| t.pending_ops())
.unwrap_or(0)
}
fn put<O: AsRef<ExId>, P: Into<Prop>, V: Into<ScalarValue>>(
&mut self,
obj: O,
prop: P,
value: V,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.put(&mut self.doc, patch_log, obj.as_ref(), prop, value)
}
fn put_object<O: AsRef<ExId>, P: Into<Prop>>(
&mut self,
obj: O,
prop: P,
value: ObjType,
) -> Result<ExId, AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.put_object(&mut self.doc, patch_log, obj.as_ref(), prop, value)
}
fn insert<O: AsRef<ExId>, V: Into<ScalarValue>>(
&mut self,
obj: O,
index: usize,
value: V,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.insert(&mut self.doc, patch_log, obj.as_ref(), index, value)
}
fn insert_object<O: AsRef<ExId>>(
&mut self,
obj: O,
index: usize,
value: ObjType,
) -> Result<ExId, AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.insert_object(&mut self.doc, patch_log, obj.as_ref(), index, value)
}
fn increment<O: AsRef<ExId>, P: Into<Prop>>(
&mut self,
obj: O,
prop: P,
value: i64,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.increment(&mut self.doc, patch_log, obj.as_ref(), prop, value)
}
fn delete<O: AsRef<ExId>, P: Into<Prop>>(
&mut self,
obj: O,
prop: P,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.delete(&mut self.doc, patch_log, obj.as_ref(), prop)
}
fn splice<O: AsRef<ExId>, V: IntoIterator<Item = ScalarValue>>(
&mut self,
obj: O,
pos: usize,
del: isize,
vals: V,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.splice(&mut self.doc, patch_log, obj.as_ref(), pos, del, vals)
}
fn splice_text<O: AsRef<ExId>>(
&mut self,
obj: O,
pos: usize,
del: isize,
text: &str,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.splice_text(&mut self.doc, patch_log, obj.as_ref(), pos, del, text)?;
Ok(())
}
fn mark<O: AsRef<ExId>>(
&mut self,
obj: O,
mark: Mark<'_>,
expand: ExpandMark,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.mark(&mut self.doc, patch_log, obj.as_ref(), mark, expand)
}
fn unmark<O: AsRef<ExId>>(
&mut self,
obj: O,
key: &str,
start: usize,
end: usize,
expand: ExpandMark,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
tx.unmark(
&mut self.doc,
patch_log,
obj.as_ref(),
key,
start,
end,
expand,
)
}
fn base_heads(&self) -> Vec<ChangeHash> {
if let Some(i) = &self.isolation {
i.clone()
} else {
self.doc.get_heads()
}
}
fn update_text<S: AsRef<str>>(
&mut self,
obj: &ExId,
new_text: S,
) -> Result<(), AutomergeError> {
self.ensure_transaction_open();
let (patch_log, tx) = self.transaction.as_mut().unwrap();
crate::text_diff::myers_diff(&mut self.doc, tx, patch_log, obj, new_text)
}
}
struct SyncWrapper<'a> {
inner: &'a mut AutoCommit,
}
impl<'a> SyncDoc for SyncWrapper<'a> {
fn generate_sync_message(&self, sync_state: &mut sync::State) -> Option<sync::Message> {
self.inner.doc.generate_sync_message(sync_state)
}
fn receive_sync_message(
&mut self,
sync_state: &mut sync::State,
message: sync::Message,
) -> Result<(), AutomergeError> {
self.inner.ensure_transaction_closed();
if self.inner.isolation.is_some() {
self.inner.doc.receive_sync_message_log_patches(
sync_state,
message,
&mut PatchLog::null(),
)
} else {
self.inner.doc.receive_sync_message_log_patches(
sync_state,
message,
&mut self.inner.patch_log,
)
}
}
fn receive_sync_message_log_patches(
&mut self,
sync_state: &mut sync::State,
message: sync::Message,
patch_log: &mut PatchLog,
) -> Result<(), AutomergeError> {
self.inner
.doc
.receive_sync_message_log_patches(sync_state, message, patch_log)
}
}
#[derive(Debug, Clone, PartialEq)]
struct OpRange {
before_len: usize,
hashes: Vec<ChangeHash>,
}
impl OpRange {
fn new(before: &[ChangeHash], after: &[ChangeHash]) -> Self {
let mut hashes = Vec::with_capacity(before.len() + after.len());
hashes.extend(before);
hashes.extend(after);
let range = Self {
before_len: before.len(),
hashes,
};
assert_eq!(before, range.before());
assert_eq!(after, range.after());
range
}
fn before(&self) -> &[ChangeHash] {
&self.hashes[0..self.before_len]
}
fn after(&self) -> &[ChangeHash] {
&self.hashes[self.before_len..]
}
}
#[cfg(test)]
mod tests {
fn is_send<S: Send>() {}
#[test]
fn test_autocommit_is_send() {
is_send::<super::AutoCommit>();
}
}