mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: compose operation
This commit is contained in:
parent
4b654de2e6
commit
01589c6f94
@ -1,7 +1,7 @@
|
||||
use crate::entities::{EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB, TextBlockPB};
|
||||
use crate::TextEditorManager;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_sync::entities::text_block::{TextBlockDeltaPB, TextBlockIdPB};
|
||||
use flowy_sync::entities::text_block::TextBlockIdPB;
|
||||
use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::entities::{EditParams, EditPayloadPB};
|
||||
use crate::entities::EditParams;
|
||||
use crate::queue::TextBlockRevisionCompactor;
|
||||
use crate::{editor::TextBlockEditor, errors::FlowyError, TextEditorCloudService};
|
||||
use bytes::Bytes;
|
||||
|
@ -7,7 +7,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bytecount = "0.6.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
#protobuf = {version = "2.18.0"}
|
||||
#flowy-derive = { path = "../flowy-derive" }
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
|
@ -3,6 +3,7 @@ use crate::core::document::path::Path;
|
||||
use crate::core::{Node, NodeBodyChangeset, NodeData, NodeOperation, OperationTransform, Transaction};
|
||||
use crate::errors::{ErrorBuilder, OTError, OTErrorCode};
|
||||
use indextree::{Arena, Children, FollowingSiblings, NodeId};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::NodeOperationList;
|
||||
|
||||
@ -26,14 +27,13 @@ impl NodeTree {
|
||||
}
|
||||
|
||||
pub fn from_bytes(root_name: &str, bytes: Vec<u8>) -> Result<Self, OTError> {
|
||||
let operations = NodeOperationList::from_bytes(bytes)?.into_inner();
|
||||
let operations = NodeOperationList::from_bytes(bytes)?;
|
||||
Self::from_operations(root_name, operations)
|
||||
}
|
||||
|
||||
pub fn from_operations(root_name: &str, operations: Vec<NodeOperation>) -> Result<Self, OTError> {
|
||||
pub fn from_operations(root_name: &str, operations: NodeOperationList) -> Result<Self, OTError> {
|
||||
let mut node_tree = NodeTree::new(root_name);
|
||||
|
||||
for operation in operations {
|
||||
for operation in operations.into_inner().into_iter() {
|
||||
let _ = node_tree.apply_op(operation)?;
|
||||
}
|
||||
Ok(node_tree)
|
||||
@ -54,13 +54,14 @@ impl NodeTree {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::rc::Rc;
|
||||
/// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path};
|
||||
/// let nodes = vec![NodeData::new("text".to_string())];
|
||||
/// let root_path: Path = vec![0].into();
|
||||
/// let op = NodeOperation::Insert {path: root_path.clone(),nodes };
|
||||
///
|
||||
/// let mut node_tree = NodeTree::new("root");
|
||||
/// node_tree.apply_op(op).unwrap();
|
||||
/// node_tree.apply_op(Rc::new(op)).unwrap();
|
||||
/// let node_id = node_tree.node_id_at_path(&root_path).unwrap();
|
||||
/// let node_path = node_tree.path_from_node_id(node_id);
|
||||
/// debug_assert_eq!(node_path, root_path);
|
||||
@ -105,23 +106,25 @@ impl NodeTree {
|
||||
counter
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the note_id at the position of the tree with id note_id
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `node_id`:
|
||||
/// * `index`:
|
||||
/// * `node_id`: the node id of the child's parent
|
||||
/// * `index`: index of the node in parent children list
|
||||
///
|
||||
/// returns: Option<NodeId>
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::rc::Rc;
|
||||
/// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path};
|
||||
/// let node_1 = NodeData::new("text".to_string());
|
||||
/// let inserted_path: Path = vec![0].into();
|
||||
///
|
||||
/// let mut node_tree = NodeTree::new("root");
|
||||
/// node_tree.apply_op(NodeOperation::Insert {path: inserted_path.clone(),nodes: vec![node_1.clone()] }).unwrap();
|
||||
/// let op = NodeOperation::Insert {path: inserted_path.clone(),nodes: vec![node_1.clone()] };
|
||||
/// node_tree.apply_op(Rc::new(op)).unwrap();
|
||||
///
|
||||
/// let node_2 = node_tree.get_node_at_path(&inserted_path).unwrap();
|
||||
/// assert_eq!(node_2.node_type, node_1.node_type);
|
||||
@ -137,6 +140,10 @@ impl NodeTree {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns all children whose parent node id is node_id
|
||||
///
|
||||
/// * `node_id`: the children's parent node id
|
||||
///
|
||||
pub fn children_from_node(&self, node_id: NodeId) -> Children<'_, Node> {
|
||||
node_id.children(&self.arena)
|
||||
}
|
||||
@ -159,7 +166,7 @@ impl NodeTree {
|
||||
node_id.following_siblings(&self.arena)
|
||||
}
|
||||
|
||||
pub fn apply(&mut self, transaction: Transaction) -> Result<(), OTError> {
|
||||
pub fn apply_transaction(&mut self, transaction: Transaction) -> Result<(), OTError> {
|
||||
let operations = transaction.into_operations();
|
||||
for operation in operations {
|
||||
self.apply_op(operation)?;
|
||||
@ -167,10 +174,15 @@ impl NodeTree {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_op(&mut self, op: NodeOperation) -> Result<(), OTError> {
|
||||
pub fn apply_op(&mut self, op: Rc<NodeOperation>) -> Result<(), OTError> {
|
||||
let op = match Rc::try_unwrap(op) {
|
||||
Ok(op) => op,
|
||||
Err(op) => op.as_ref().clone(),
|
||||
};
|
||||
|
||||
match op {
|
||||
NodeOperation::Insert { path, nodes } => self.insert_nodes(&path, nodes),
|
||||
NodeOperation::UpdateAttributes { path, attributes, .. } => self.update_attributes(&path, attributes),
|
||||
NodeOperation::UpdateAttributes { path, new, .. } => self.update_attributes(&path, new),
|
||||
NodeOperation::UpdateBody { path, changeset } => self.update_body(&path, changeset),
|
||||
NodeOperation::Delete { path, nodes } => self.delete_node(&path, nodes),
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::core::attributes::Attributes;
|
||||
use crate::core::document::path::Path;
|
||||
use crate::core::{NodeBodyChangeset, NodeData, OperationTransform};
|
||||
use crate::core::{NodeBodyChangeset, NodeData};
|
||||
use crate::errors::OTError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "op")]
|
||||
@ -10,12 +11,11 @@ pub enum NodeOperation {
|
||||
#[serde(rename = "insert")]
|
||||
Insert { path: Path, nodes: Vec<NodeData> },
|
||||
|
||||
#[serde(rename = "update")]
|
||||
#[serde(rename = "update-attribute")]
|
||||
UpdateAttributes {
|
||||
path: Path,
|
||||
attributes: Attributes,
|
||||
#[serde(rename = "oldAttributes")]
|
||||
old_attributes: Attributes,
|
||||
new: Attributes,
|
||||
old: Attributes,
|
||||
},
|
||||
|
||||
#[serde(rename = "update-body")]
|
||||
@ -27,35 +27,6 @@ pub enum NodeOperation {
|
||||
Delete { path: Path, nodes: Vec<NodeData> },
|
||||
}
|
||||
|
||||
// impl OperationTransform for NodeOperation {
|
||||
// fn compose(&self, other: &Self) -> Result<Self, OTError>
|
||||
// where
|
||||
// Self: Sized,
|
||||
// {
|
||||
// match self {
|
||||
// NodeOperation::Insert { path, nodes } => {
|
||||
// let new_path = Path::transform(path, other.path(), nodes.len() as i64);
|
||||
// Ok((self.clone(), other.clone_with_new_path(new_path)))
|
||||
// }
|
||||
// NodeOperation::Delete { path, nodes } => {
|
||||
// let new_path = Path::transform(path, other.path(), nodes.len() as i64);
|
||||
// other.clone_with_new_path(new_path)
|
||||
// }
|
||||
// _ => other.clone(),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
|
||||
// where
|
||||
// Self: Sized,
|
||||
// {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn invert(&self, other: &Self) -> Self {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
impl NodeOperation {
|
||||
pub fn get_path(&self) -> &Path {
|
||||
match self {
|
||||
@ -83,12 +54,12 @@ impl NodeOperation {
|
||||
},
|
||||
NodeOperation::UpdateAttributes {
|
||||
path,
|
||||
attributes,
|
||||
old_attributes,
|
||||
new: attributes,
|
||||
old: old_attributes,
|
||||
} => NodeOperation::UpdateAttributes {
|
||||
path: path.clone(),
|
||||
attributes: old_attributes.clone(),
|
||||
old_attributes: attributes.clone(),
|
||||
new: old_attributes.clone(),
|
||||
old: attributes.clone(),
|
||||
},
|
||||
NodeOperation::Delete { path, nodes } => NodeOperation::Insert {
|
||||
path: path.clone(),
|
||||
@ -101,10 +72,10 @@ impl NodeOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the `other` operation to be applied to the version that has been modified.
|
||||
/// Make the `other` operation can be applied to the version after applying the `self` operation.
|
||||
/// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。
|
||||
/// For example, if the inserted position has been acquired by others, then you need to do transform to make sure
|
||||
/// your position is value.
|
||||
/// For example, if the inserted position has been acquired by others, then it's needed to do the transform to
|
||||
/// make sure the inserted position is right.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@ -143,24 +114,30 @@ impl NodeOperation {
|
||||
let new_path = path.transform(other.get_path(), nodes.len());
|
||||
*other.get_mut_path() = new_path;
|
||||
}
|
||||
_ => {}
|
||||
_ => {
|
||||
// Only insert/delete will change the path.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct NodeOperationList {
|
||||
operations: Vec<NodeOperation>,
|
||||
operations: Vec<Rc<NodeOperation>>,
|
||||
}
|
||||
|
||||
impl NodeOperationList {
|
||||
pub fn into_inner(self) -> Vec<NodeOperation> {
|
||||
pub fn into_inner(self) -> Vec<Rc<NodeOperation>> {
|
||||
self.operations
|
||||
}
|
||||
|
||||
pub fn add_op(&mut self, operation: NodeOperation) {
|
||||
self.operations.push(Rc::new(operation));
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for NodeOperationList {
|
||||
type Target = Vec<NodeOperation>;
|
||||
type Target = Vec<Rc<NodeOperation>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.operations
|
||||
@ -175,13 +152,15 @@ impl std::ops::DerefMut for NodeOperationList {
|
||||
|
||||
impl std::convert::From<Vec<NodeOperation>> for NodeOperationList {
|
||||
fn from(operations: Vec<NodeOperation>) -> Self {
|
||||
Self { operations }
|
||||
Self::new(operations)
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeOperationList {
|
||||
pub fn new(operations: Vec<NodeOperation>) -> Self {
|
||||
Self { operations }
|
||||
Self {
|
||||
operations: operations.into_iter().map(Rc::new).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, OTError> {
|
||||
|
@ -1,6 +1,28 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The `Path` represents as a path to reference to the node in the `NodeTree`.
|
||||
/// ┌─────────┐
|
||||
/// │ Root │
|
||||
/// └─────────┼──────────┐
|
||||
/// │0: Node A │
|
||||
/// └──────────┼────────────┐
|
||||
/// │0: Node A-1 │
|
||||
/// ├────────────┤
|
||||
/// │1: Node A-2 │
|
||||
/// ┌──────────┼────────────┘
|
||||
/// │1: Node B │
|
||||
/// └──────────┼────────────┐
|
||||
/// │0: Node B-1 │
|
||||
/// ├────────────┤
|
||||
/// │1: Node B-2 │
|
||||
/// ┌──────────┼────────────┘
|
||||
/// │2: Node C │
|
||||
/// └──────────┘
|
||||
///
|
||||
/// The path of Node A will be [0]
|
||||
/// The path of Node A-1 will be [0,0]
|
||||
/// The path of Node A-2 will be [0,1]
|
||||
/// The path of Node B-2 will be [1,1]
|
||||
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)]
|
||||
pub struct Path(pub Vec<usize>);
|
||||
|
||||
@ -49,21 +71,90 @@ impl From<&[usize]> for Path {
|
||||
}
|
||||
|
||||
impl Path {
|
||||
/// Calling this function if there are two changes want to modify the same path.
|
||||
///
|
||||
///
|
||||
/// The path will be changed is
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `other`:
|
||||
/// * `offset`: represents the len of nodes referenced by this path
|
||||
/// * `other`: the path that need to be transformed
|
||||
/// * `offset`: represents the len of nodes referenced by the current path
|
||||
///
|
||||
/// returns: Path
|
||||
/// If two changes modify the same path or the path was shared by them. Then it needs to do the
|
||||
/// transformation to make sure the changes are applied to the right path.
|
||||
///
|
||||
/// returns: the path represents the position that the other path reference to.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use lib_ot::core::Path;
|
||||
/// let path = Path(vec![0, 1]);
|
||||
/// for (old_path, len_of_nodes, expected_path) in vec![
|
||||
/// // Try to modify the path [0, 1], but someone has inserted one element before the
|
||||
/// // current path [0,1] in advance. That causes the modified path [0,1] to no longer
|
||||
/// // valid. It needs to do the transformation to get the right path.
|
||||
/// //
|
||||
/// // [0,2] is the path you want to modify.
|
||||
/// (Path(vec![0, 1]), 1, Path(vec![0, 2])),
|
||||
/// (Path(vec![0, 1]), 5, Path(vec![0, 6])),
|
||||
/// (Path(vec![0, 2]), 1, Path(vec![0, 3])),
|
||||
/// // Try to modify the path [0, 2,3,4], but someone has inserted one element before the
|
||||
/// // current path [0,1] in advance. That cause the prefix path [0,2] of [0,2,3,4]
|
||||
/// // no longer valid.
|
||||
/// // It needs to do the transformation to get the right path. So [0,2] is transformed to [0,3]
|
||||
/// // and the suffix [3,4] of the [0,2,3,4] remains the same. So the transformed result is
|
||||
/// //
|
||||
/// // [0,3,3,4]
|
||||
/// (Path(vec![0, 2, 3, 4]), 1, Path(vec![0, 3, 3, 4])),
|
||||
/// ] {
|
||||
/// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path);
|
||||
/// }
|
||||
/// // The path remains the same in the following test. Because the shared path is not changed.
|
||||
/// let path = Path(vec![0, 1, 2]);
|
||||
/// for (old_path, len_of_nodes, expected_path) in vec![
|
||||
/// // Try to modify the path [0,0,0,1,2], but someone has inserted one element
|
||||
/// // before [0,1,2]. [0,0,0,1,2] and [0,1,2] share the same path [0,x], because
|
||||
/// // the element was inserted at [0,1,2] that didn't affect the shared path [0, x].
|
||||
/// // So, after the transformation, the path is not changed.
|
||||
/// (Path(vec![0, 0, 0, 1, 2]), 1, Path(vec![0, 0, 0, 1, 2])),
|
||||
/// (Path(vec![0, 1]), 1, Path(vec![0, 1])),
|
||||
/// ] {
|
||||
/// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path);
|
||||
/// }
|
||||
///
|
||||
/// let path = Path(vec![1, 1]);
|
||||
/// for (old_path, len_of_nodes, expected_path) in vec![(Path(vec![1, 0]), 1, Path(vec![1, 0]))] {
|
||||
/// assert_eq!(path.transform(&old_path, len_of_nodes), expected_path);
|
||||
/// }
|
||||
/// ```
|
||||
/// For example, client A and client B want to insert a node at the same index, the server applies
|
||||
/// the changes made by client B. But, before applying the client A's changes, server transforms
|
||||
/// the changes first in order to make sure that client A modify the right position. After that,
|
||||
/// the changes can be applied to the server.
|
||||
///
|
||||
/// ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
/// │ Client A │ │ Server │ │ Client B │
|
||||
/// └─────┬────┘ └─────┬────┘ └────┬─────┘
|
||||
/// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ┐ │
|
||||
/// │ │ Root │
|
||||
/// │ │ │ 0:A │ │
|
||||
/// │ │ ─ ─ ─ ─ ─ ─ ─ ─ │
|
||||
/// │ │ ◀───────────────────────│
|
||||
/// │ │ Insert B at index 1 │
|
||||
/// │ │ │
|
||||
/// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ┐ │
|
||||
/// │ │ Root │
|
||||
/// │ │ │ 0:A │ │
|
||||
/// ├──────────────────────▶│ 1:B │
|
||||
/// │ Insert C at index 1 │ └ ─ ─ ─ ─ ─ ─ ─ ┘ │
|
||||
/// │ │ │
|
||||
/// │ │ transform index 1 to 2 │
|
||||
/// │ │ │
|
||||
/// │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ │
|
||||
/// │ │ Root │ │
|
||||
/// │ │ │ 0:A │
|
||||
/// ▼ ▼ 1:B │ ▼
|
||||
/// │ 2:C
|
||||
/// ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||
pub fn transform(&self, other: &Path, offset: usize) -> Path {
|
||||
if self.len() > other.len() {
|
||||
return other.clone();
|
||||
@ -81,7 +172,7 @@ impl Path {
|
||||
let second_last_index = self.0.len() - 1;
|
||||
let mut prefix: Vec<usize> = self.0[0..second_last_index].into();
|
||||
let mut suffix: Vec<usize> = other.0[self.0.len()..].into();
|
||||
let last_value = self.0.last().unwrap().clone();
|
||||
let last_value = *self.0.last().unwrap();
|
||||
|
||||
let other_second_last_value = other.0[second_last_index];
|
||||
|
||||
@ -96,81 +187,4 @@ impl Path {
|
||||
prefix.append(&mut suffix);
|
||||
Path(prefix)
|
||||
}
|
||||
|
||||
// delta is default to be 1
|
||||
pub fn transform3(pre_insert_path: &Path, b: &Path, offset: i64) -> Path {
|
||||
if pre_insert_path.len() > b.len() {
|
||||
return b.clone();
|
||||
}
|
||||
if pre_insert_path.is_empty() || b.is_empty() {
|
||||
return b.clone();
|
||||
}
|
||||
// check the prefix
|
||||
for i in 0..(pre_insert_path.len() - 1) {
|
||||
if pre_insert_path.0[i] != b.0[i] {
|
||||
return b.clone();
|
||||
}
|
||||
}
|
||||
let mut prefix: Vec<usize> = pre_insert_path.0[0..(pre_insert_path.len() - 1)].into();
|
||||
let mut suffix: Vec<usize> = b.0[pre_insert_path.0.len()..].into();
|
||||
let prev_insert_last: usize = *pre_insert_path.0.last().unwrap();
|
||||
let b_at_index = b.0[pre_insert_path.0.len() - 1];
|
||||
if prev_insert_last <= b_at_index {
|
||||
prefix.push(((b_at_index as i64) + offset) as usize);
|
||||
} else {
|
||||
prefix.push(b_at_index);
|
||||
}
|
||||
prefix.append(&mut suffix);
|
||||
|
||||
Path(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::Path;
|
||||
#[test]
|
||||
fn path_transform_test_1() {
|
||||
assert_eq!(
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 1]), 1) }.0,
|
||||
vec![0, 2]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 1]), 5) }.0,
|
||||
vec![0, 6]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_transform_test_2() {
|
||||
assert_eq!(
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 2]), 1) }.0,
|
||||
vec![0, 3]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_transform_test_3() {
|
||||
assert_eq!(
|
||||
{ Path::transform3(&Path(vec![0, 1]), &Path(vec![0, 2, 7, 8, 9]), 1) }.0,
|
||||
vec![0, 3, 7, 8, 9]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_transform_no_changed_test() {
|
||||
assert_eq!(
|
||||
{ Path::transform3(&Path(vec![0, 1, 2]), &Path(vec![0, 0, 7, 8, 9]), 1) }.0,
|
||||
vec![0, 0, 7, 8, 9]
|
||||
);
|
||||
assert_eq!(
|
||||
{ Path::transform3(&Path(vec![0, 1, 2]), &Path(vec![0, 1]), 1) }.0,
|
||||
vec![0, 1]
|
||||
);
|
||||
assert_eq!(
|
||||
{ Path::transform3(&Path(vec![1, 1]), &Path(vec![1, 0]), 1) }.0,
|
||||
vec![1, 0]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
use crate::core::attributes::Attributes;
|
||||
use crate::core::document::path::Path;
|
||||
use crate::core::{NodeData, NodeOperation, NodeTree};
|
||||
use crate::errors::OTError;
|
||||
use indextree::NodeId;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{NodeBodyChangeset, NodeOperationList};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Transaction {
|
||||
operations: NodeOperationList,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub fn new() -> Self {
|
||||
Transaction {
|
||||
operations: vec![].into(),
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn from_operations<T: Into<NodeOperationList>>(operations: T) -> Self {
|
||||
@ -23,25 +23,36 @@ impl Transaction {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_operations(self) -> Vec<NodeOperation> {
|
||||
pub fn into_operations(self) -> Vec<Rc<NodeOperation>> {
|
||||
self.operations.into_inner()
|
||||
}
|
||||
|
||||
/// Make the `other` to be applied to the version that has been modified.
|
||||
/// Make the `other` can be applied to the version after applying the `self` transaction.
|
||||
///
|
||||
/// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。
|
||||
/// the operations of the transaction will be transformed into the conflict operations.
|
||||
pub fn transform(&self, other: &mut Transaction) {
|
||||
for other_operation in other.iter_mut() {
|
||||
pub fn transform(&self, other: &Transaction) -> Result<Transaction, OTError> {
|
||||
let mut new_transaction = other.clone();
|
||||
for other_operation in new_transaction.iter_mut() {
|
||||
let other_operation = Rc::make_mut(other_operation);
|
||||
for operation in self.operations.iter() {
|
||||
operation.transform(other_operation);
|
||||
}
|
||||
}
|
||||
Ok(new_transaction)
|
||||
}
|
||||
|
||||
pub fn compose(&mut self, other: &Transaction) -> Result<(), OTError> {
|
||||
// For the moment, just append `other` operations to the end of `self`.
|
||||
for operation in other.operations.iter() {
|
||||
self.operations.push(operation.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Transaction {
|
||||
type Target = NodeOperationList;
|
||||
type Target = Vec<Rc<NodeOperation>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.operations
|
||||
@ -85,7 +96,7 @@ impl<'a> TransactionBuilder<'a> {
|
||||
/// let transaction = TransactionBuilder::new(&node_tree)
|
||||
/// .insert_nodes_at_path(0,vec![ NodeData::new("text_1"), NodeData::new("text_2")])
|
||||
/// .finalize();
|
||||
/// node_tree.apply(transaction).unwrap();
|
||||
/// node_tree.apply_transaction(transaction).unwrap();
|
||||
///
|
||||
/// node_tree.node_id_at_path(vec![0, 0]);
|
||||
/// ```
|
||||
@ -115,7 +126,7 @@ impl<'a> TransactionBuilder<'a> {
|
||||
/// let transaction = TransactionBuilder::new(&node_tree)
|
||||
/// .insert_node_at_path(0, NodeData::new("text"))
|
||||
/// .finalize();
|
||||
/// node_tree.apply(transaction).unwrap();
|
||||
/// node_tree.apply_transaction(transaction).unwrap();
|
||||
/// ```
|
||||
///
|
||||
pub fn insert_node_at_path<T: Into<Path>>(self, path: T, node: NodeData) -> Self {
|
||||
@ -133,10 +144,10 @@ impl<'a> TransactionBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.operations.push(NodeOperation::UpdateAttributes {
|
||||
self.operations.add_op(NodeOperation::UpdateAttributes {
|
||||
path: path.clone(),
|
||||
attributes,
|
||||
old_attributes,
|
||||
new: attributes,
|
||||
old: old_attributes,
|
||||
});
|
||||
}
|
||||
None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path),
|
||||
@ -147,7 +158,7 @@ impl<'a> TransactionBuilder<'a> {
|
||||
pub fn update_body_at_path(mut self, path: &Path, changeset: NodeBodyChangeset) -> Self {
|
||||
match self.node_tree.node_id_at_path(path) {
|
||||
Some(_) => {
|
||||
self.operations.push(NodeOperation::UpdateBody {
|
||||
self.operations.add_op(NodeOperation::UpdateBody {
|
||||
path: path.clone(),
|
||||
changeset,
|
||||
});
|
||||
@ -169,7 +180,7 @@ impl<'a> TransactionBuilder<'a> {
|
||||
node = self.node_tree.following_siblings(node).next().unwrap();
|
||||
}
|
||||
|
||||
self.operations.push(NodeOperation::Delete {
|
||||
self.operations.add_op(NodeOperation::Delete {
|
||||
path: path.clone(),
|
||||
nodes: deleted_nodes,
|
||||
});
|
||||
@ -193,7 +204,7 @@ impl<'a> TransactionBuilder<'a> {
|
||||
}
|
||||
|
||||
pub fn push(mut self, op: NodeOperation) -> Self {
|
||||
self.operations.push(op);
|
||||
self.operations.add_op(op);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::node::script::NodeScript::*;
|
||||
use crate::node::script::NodeTest;
|
||||
use lib_ot::core::{AttributeBuilder, Node, NodeTree, Transaction, TransactionBuilder};
|
||||
use lib_ot::core::{AttributeBuilder, Node};
|
||||
use lib_ot::{
|
||||
core::{NodeBodyChangeset, NodeData, NodeDataBuilder, NodeOperation, Path},
|
||||
text_delta::TextDeltaBuilder,
|
||||
@ -35,15 +35,14 @@ fn operation_insert_node_with_children_serde_test() {
|
||||
fn operation_update_node_attributes_serde_test() {
|
||||
let operation = NodeOperation::UpdateAttributes {
|
||||
path: Path(vec![0, 1]),
|
||||
attributes: AttributeBuilder::new().insert("bold", true).build(),
|
||||
old_attributes: AttributeBuilder::new().insert("bold", false).build(),
|
||||
new: AttributeBuilder::new().insert("bold", true).build(),
|
||||
old: AttributeBuilder::new().insert("bold", false).build(),
|
||||
};
|
||||
|
||||
let result = serde_json::to_string(&operation).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"op":"update","path":[0,1],"attributes":{"bold":true},"oldAttributes":{"bold":null}}"#
|
||||
r#"{"op":"update-attribute","path":[0,1],"new":{"bold":true},"old":{"bold":null}}"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -119,17 +118,17 @@ fn operation_insert_transform_one_level_path_test() {
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
node_data: node_data_1,
|
||||
rev_id: 1,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_2.clone(),
|
||||
node_data: node_data_2,
|
||||
rev_id: 2,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_3.clone(),
|
||||
node_data: node_data_3,
|
||||
rev_id: 1,
|
||||
},
|
||||
AssertNode {
|
||||
@ -157,12 +156,12 @@ fn operation_insert_transform_multiple_level_path_test() {
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
node_data: node_data_1,
|
||||
rev_id: 1,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_2.clone(),
|
||||
node_data: node_data_2,
|
||||
rev_id: 2,
|
||||
},
|
||||
InsertNode {
|
||||
@ -189,12 +188,12 @@ fn operation_delete_transform_path_test() {
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
node_data: node_data_1,
|
||||
rev_id: 1,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_2.clone(),
|
||||
node_data: node_data_2,
|
||||
rev_id: 2,
|
||||
},
|
||||
// The node's in the tree will be:
|
||||
@ -206,7 +205,7 @@ fn operation_delete_transform_path_test() {
|
||||
// but it was moved to index 2.
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_3.clone(),
|
||||
node_data: node_data_3,
|
||||
rev_id: 3,
|
||||
},
|
||||
// 0: text_1
|
||||
|
@ -1,4 +1,4 @@
|
||||
use lib_ot::core::{Node, NodeOperation, Transaction};
|
||||
use lib_ot::core::{Node, Transaction};
|
||||
use lib_ot::{
|
||||
core::attributes::Attributes,
|
||||
core::{NodeBody, NodeBodyChangeset, NodeData, NodeTree, Path, TransactionBuilder},
|
||||
@ -144,14 +144,14 @@ impl NodeTest {
|
||||
fn apply_transaction(&mut self, transaction: Transaction) {
|
||||
self.rev_id += 1;
|
||||
self.rev_operations.insert(self.rev_id, transaction.clone());
|
||||
self.node_tree.apply(transaction).unwrap();
|
||||
self.node_tree.apply_transaction(transaction).unwrap();
|
||||
}
|
||||
|
||||
fn transform_transaction_if_need(&mut self, transaction: &mut Transaction, rev_id: usize) {
|
||||
if self.rev_id >= rev_id {
|
||||
for rev_id in rev_id..=self.rev_id {
|
||||
let old_transaction = self.rev_operations.get(&rev_id).unwrap();
|
||||
old_transaction.transform(transaction);
|
||||
*transaction = old_transaction.transform(transaction).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ fn node_insert_node_in_ordered_nodes_test() {
|
||||
},
|
||||
InsertNode {
|
||||
path: path_3.clone(),
|
||||
node_data: node_3.clone(),
|
||||
node_data: node_3,
|
||||
rev_id: 3,
|
||||
},
|
||||
// 0:text_1
|
||||
@ -166,12 +166,12 @@ fn node_insert_nested_nodes_test() {
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
node_data: node_data_1,
|
||||
rev_id: 1,
|
||||
},
|
||||
InsertNode {
|
||||
path: 1.into(),
|
||||
node_data: node_data_2.clone(),
|
||||
node_data: node_data_2,
|
||||
rev_id: 2,
|
||||
},
|
||||
// the tree will be:
|
||||
@ -214,7 +214,7 @@ fn node_insert_node_before_existing_nested_nodes_test() {
|
||||
let scripts = vec![
|
||||
InsertNode {
|
||||
path: 0.into(),
|
||||
node_data: node_data_1.clone(),
|
||||
node_data: node_data_1,
|
||||
rev_id: 1,
|
||||
},
|
||||
// 0:text_1
|
||||
|
Loading…
Reference in New Issue
Block a user