mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
128 lines
3.3 KiB
Rust
128 lines
3.3 KiB
Rust
use crate::errors::SyncError;
|
|
use dissimilar::Chunk;
|
|
use document_model::document::DocumentInfo;
|
|
use lib_ot::core::{DeltaOperationBuilder, OTString, OperationAttributes};
|
|
use lib_ot::{
|
|
core::{DeltaOperations, OperationTransform, NEW_LINE, WHITESPACE},
|
|
text_delta::DeltaTextOperations,
|
|
};
|
|
use revision_model::Revision;
|
|
use serde::de::DeserializeOwned;
|
|
|
|
#[inline]
|
|
pub fn find_newline(s: &str) -> Option<usize> {
|
|
s.find(NEW_LINE)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_newline(s: &str) -> bool {
|
|
s == NEW_LINE
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_whitespace(s: &str) -> bool {
|
|
s == WHITESPACE
|
|
}
|
|
|
|
#[inline]
|
|
pub fn contain_newline(s: &str) -> bool {
|
|
s.contains(NEW_LINE)
|
|
}
|
|
|
|
pub fn recover_operation_from_revisions<T>(
|
|
revisions: Vec<Revision>,
|
|
validator: impl Fn(&DeltaOperations<T>) -> bool,
|
|
) -> Option<DeltaOperations<T>>
|
|
where
|
|
T: OperationAttributes + DeserializeOwned + OperationAttributes,
|
|
{
|
|
let mut new_operations = DeltaOperations::<T>::new();
|
|
for revision in revisions {
|
|
if let Ok(operations) = DeltaOperations::<T>::from_bytes(revision.bytes) {
|
|
match new_operations.compose(&operations) {
|
|
Ok(composed_operations) => {
|
|
if validator(&composed_operations) {
|
|
new_operations = composed_operations;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
Err(_) => break,
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if new_operations.is_empty() {
|
|
None
|
|
} else {
|
|
Some(new_operations)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn make_document_info_from_revisions(
|
|
doc_id: &str,
|
|
revisions: Vec<Revision>,
|
|
) -> Result<Option<DocumentInfo>, SyncError> {
|
|
if revisions.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
|
|
let mut delta = DeltaTextOperations::new();
|
|
let mut base_rev_id = 0;
|
|
let mut rev_id = 0;
|
|
for revision in revisions {
|
|
base_rev_id = revision.base_rev_id;
|
|
rev_id = revision.rev_id;
|
|
|
|
if revision.bytes.is_empty() {
|
|
tracing::warn!("revision delta_data is empty");
|
|
}
|
|
|
|
let new_delta = DeltaTextOperations::from_bytes(revision.bytes)?;
|
|
delta = delta.compose(&new_delta)?;
|
|
}
|
|
|
|
Ok(Some(DocumentInfo {
|
|
doc_id: doc_id.to_owned(),
|
|
data: delta.json_bytes().to_vec(),
|
|
rev_id,
|
|
base_rev_id,
|
|
}))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn rev_id_from_str(s: &str) -> Result<i64, SyncError> {
|
|
let rev_id = s
|
|
.to_owned()
|
|
.parse::<i64>()
|
|
.map_err(|e| SyncError::internal().context(format!("Parse rev_id from {} failed. {}", s, e)))?;
|
|
Ok(rev_id)
|
|
}
|
|
|
|
pub fn cal_diff<T: OperationAttributes>(old: String, new: String) -> Option<DeltaOperations<T>> {
|
|
let chunks = dissimilar::diff(&old, &new);
|
|
let mut delta_builder = DeltaOperationBuilder::<T>::new();
|
|
for chunk in &chunks {
|
|
match chunk {
|
|
Chunk::Equal(s) => {
|
|
delta_builder = delta_builder.retain(OTString::from(*s).utf16_len());
|
|
}
|
|
Chunk::Delete(s) => {
|
|
delta_builder = delta_builder.delete(OTString::from(*s).utf16_len());
|
|
}
|
|
Chunk::Insert(s) => {
|
|
delta_builder = delta_builder.insert(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
let delta = delta_builder.build();
|
|
if delta.is_empty() {
|
|
None
|
|
} else {
|
|
Some(delta)
|
|
}
|
|
}
|