use crate::{ entities::{ folder::{FolderDelta, FolderInfo}, revision::{RepeatedRevision, Revision}, text_block::TextBlockInfoPB, }, errors::{CollaborateError, CollaborateResult}, protobuf::{FolderInfo as FolderInfoPB, RepeatedRevision as RepeatedRevisionPB, Revision as RevisionPB}, }; use dissimilar::Chunk; use lib_ot::core::{DeltaBuilder, FlowyStr}; use lib_ot::{ core::{Attributes, Delta, OperationTransformable, NEW_LINE, WHITESPACE}, rich_text::RichTextDelta, }; use serde::de::DeserializeOwned; use std::{ convert::TryInto, sync::atomic::{AtomicI64, Ordering::SeqCst}, }; #[inline] pub fn find_newline(s: &str) -> Option { 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) } #[inline] pub fn md5>(data: T) -> String { let md5 = format!("{:x}", md5::compute(data)); md5 } #[derive(Debug)] pub struct RevIdCounter(pub AtomicI64); impl RevIdCounter { pub fn new(n: i64) -> Self { Self(AtomicI64::new(n)) } pub fn next(&self) -> i64 { let _ = self.0.fetch_add(1, SeqCst); self.value() } pub fn value(&self) -> i64 { self.0.load(SeqCst) } pub fn set(&self, n: i64) { let _ = self.0.fetch_update(SeqCst, SeqCst, |_| Some(n)); } } #[tracing::instrument(level = "trace", skip(revisions), err)] pub fn make_delta_from_revisions(revisions: Vec) -> CollaborateResult> where T: Attributes + DeserializeOwned, { let mut delta = Delta::::new(); for revision in revisions { if revision.delta_data.is_empty() { tracing::warn!("revision delta_data is empty"); } let revision_delta = Delta::::from_bytes(revision.delta_data).map_err(|e| { let err_msg = format!("Deserialize remote revision failed: {:?}", e); CollaborateError::internal().context(err_msg) })?; delta = delta.compose(&revision_delta)?; } Ok(delta) } pub fn make_delta_from_revision_pb(revisions: Vec) -> CollaborateResult> where T: Attributes + DeserializeOwned, { let mut new_delta = Delta::::new(); for revision in revisions { let delta = Delta::::from_bytes(revision.delta_data).map_err(|e| { let err_msg = format!("Deserialize remote revision failed: {:?}", e); CollaborateError::internal().context(err_msg) })?; new_delta = new_delta.compose(&delta)?; } Ok(new_delta) } pub fn repeated_revision_from_revision_pbs(revisions: Vec) -> CollaborateResult { let repeated_revision_pb = repeated_revision_pb_from_revisions(revisions); repeated_revision_from_repeated_revision_pb(repeated_revision_pb) } pub fn repeated_revision_pb_from_revisions(revisions: Vec) -> RepeatedRevisionPB { let mut repeated_revision_pb = RepeatedRevisionPB::new(); repeated_revision_pb.set_items(revisions.into()); repeated_revision_pb } pub fn repeated_revision_from_repeated_revision_pb( repeated_revision: RepeatedRevisionPB, ) -> CollaborateResult { repeated_revision .try_into() .map_err(|e| CollaborateError::internal().context(format!("Cast repeated revision failed: {:?}", e))) } pub fn pair_rev_id_from_revision_pbs(revisions: &[RevisionPB]) -> (i64, i64) { let mut rev_id = 0; revisions.iter().for_each(|revision| { if rev_id < revision.rev_id { rev_id = revision.rev_id; } }); if rev_id > 0 { (rev_id - 1, rev_id) } else { (0, rev_id) } } pub fn pair_rev_id_from_revisions(revisions: &[Revision]) -> (i64, i64) { let mut rev_id = 0; revisions.iter().for_each(|revision| { if rev_id < revision.rev_id { rev_id = revision.rev_id; } }); if rev_id > 0 { (rev_id - 1, rev_id) } else { (0, rev_id) } } #[inline] pub fn make_folder_from_revisions_pb( folder_id: &str, revisions: RepeatedRevisionPB, ) -> Result, CollaborateError> { match make_folder_pb_from_revisions_pb(folder_id, revisions)? { None => Ok(None), Some(pb) => { let folder_info: FolderInfo = pb.try_into().map_err(|e| CollaborateError::internal().context(e))?; Ok(Some(folder_info)) } } } #[inline] pub fn make_folder_pb_from_revisions_pb( folder_id: &str, mut revisions: RepeatedRevisionPB, ) -> Result, CollaborateError> { let revisions = revisions.take_items(); if revisions.is_empty() { return Ok(None); } let mut folder_delta = FolderDelta::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.delta_data.is_empty() { tracing::warn!("revision delta_data is empty"); } let delta = FolderDelta::from_bytes(revision.delta_data)?; folder_delta = folder_delta.compose(&delta)?; } let text = folder_delta.to_delta_str(); let mut folder_info = FolderInfoPB::new(); folder_info.set_folder_id(folder_id.to_owned()); folder_info.set_text(text); folder_info.set_base_rev_id(base_rev_id); folder_info.set_rev_id(rev_id); Ok(Some(folder_info)) } #[inline] pub fn make_document_info_from_revisions_pb( doc_id: &str, revisions: RepeatedRevisionPB, ) -> Result, CollaborateError> { match make_document_info_pb_from_revisions_pb(doc_id, revisions)? { None => Ok(None), Some(pb) => { let document_info: TextBlockInfoPB = pb.try_into().map_err(|e| { CollaborateError::internal().context(format!("Deserialize document info from pb failed: {}", e)) })?; Ok(Some(document_info)) } } } #[inline] pub fn make_document_info_pb_from_revisions_pb( doc_id: &str, mut revisions: RepeatedRevisionPB, ) -> Result, CollaborateError> { let revisions = revisions.take_items(); if revisions.is_empty() { return Ok(None); } let mut document_delta = RichTextDelta::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.delta_data.is_empty() { tracing::warn!("revision delta_data is empty"); } let delta = RichTextDelta::from_bytes(revision.delta_data)?; document_delta = document_delta.compose(&delta)?; } let text = document_delta.to_delta_str(); let mut block_info = crate::protobuf::TextBlockInfoPB::new(); block_info.set_block_id(doc_id.to_owned()); block_info.set_text(text); block_info.set_base_rev_id(base_rev_id); block_info.set_rev_id(rev_id); Ok(Some(block_info)) } #[inline] pub fn rev_id_from_str(s: &str) -> Result { let rev_id = s .to_owned() .parse::() .map_err(|e| CollaborateError::internal().context(format!("Parse rev_id from {} failed. {}", s, e)))?; Ok(rev_id) } pub fn cal_diff(old: String, new: String) -> Option> { let chunks = dissimilar::diff(&old, &new); let mut delta_builder = DeltaBuilder::::new(); for chunk in &chunks { match chunk { Chunk::Equal(s) => { delta_builder = delta_builder.retain(FlowyStr::from(*s).utf16_size()); } Chunk::Delete(s) => { delta_builder = delta_builder.delete(FlowyStr::from(*s).utf16_size()); } Chunk::Insert(s) => { delta_builder = delta_builder.insert(*s); } } } let delta = delta_builder.build(); if delta.is_empty() { None } else { Some(delta) } }