config ping test

This commit is contained in:
appflowy 2022-01-02 10:34:42 +08:00
parent 12e8424e8a
commit 1a869c0003
22 changed files with 335 additions and 447 deletions

View File

@ -73,7 +73,7 @@ impl DocumentWebSocketActor {
.map_err(internal_error)??;
tracing::debug!(
"[DocumentWebSocketActor]: receive client data: {}:{}, {:?}",
"[DocumentWebSocketActor]: client data: {}:{}, {:?}",
document_client_data.doc_id,
document_client_data.id,
document_client_data.ty

View File

@ -54,7 +54,7 @@ impl DocumentTest {
#[derive(Clone)]
struct ScriptContext {
client_edit_context: Option<Arc<ClientDocumentEditor>>,
client_editor: Option<Arc<ClientDocumentEditor>>,
client_sdk: FlowySDKTest,
client_user_session: Arc<UserSession>,
ws_conn: Arc<FlowyWSConnect>,
@ -69,7 +69,7 @@ impl ScriptContext {
let doc_id = create_doc(&client_sdk).await;
Self {
client_edit_context: None,
client_editor: None,
client_sdk,
client_user_session: user_session,
ws_conn: ws_manager,
@ -81,10 +81,10 @@ impl ScriptContext {
async fn open_doc(&mut self) {
let doc_id = self.doc_id.clone();
let edit_context = self.client_sdk.document_ctx.controller.open(doc_id).await.unwrap();
self.client_edit_context = Some(edit_context);
self.client_editor = Some(edit_context);
}
fn client_edit_context(&self) -> Arc<ClientDocumentEditor> { self.client_edit_context.as_ref().unwrap().clone() }
fn client_editor(&self) -> Arc<ClientDocumentEditor> { self.client_editor.as_ref().unwrap().clone() }
}
async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript>) {
@ -106,23 +106,23 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
},
DocScript::ClientInsertText(index, s) => {
sleep(Duration::from_millis(2000)).await;
context.read().client_edit_context().insert(index, s).await.unwrap();
context.read().client_editor().insert(index, s).await.unwrap();
},
DocScript::ClientFormatText(interval, attribute) => {
context
.read()
.client_edit_context()
.client_editor()
.format(interval, attribute)
.await
.unwrap();
},
DocScript::AssertClient(s) => {
sleep(Duration::from_millis(2000)).await;
let json = context.read().client_edit_context().doc_json().await.unwrap();
let json = context.read().client_editor().doc_json().await.unwrap();
assert_eq(s, &json);
},
DocScript::AssertServer(s, rev_id) => {
sleep(Duration::from_millis(100)).await;
sleep(Duration::from_millis(2000)).await;
let persistence = Data::new(context.read().server.app_ctx.persistence.kv_store());
let doc_identifier: flowy_collaboration::protobuf::DocumentId = DocumentId {
doc_id
@ -148,6 +148,7 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
let kv_store = Data::new(context.read().server.app_ctx.persistence.kv_store());
reset_doc(&doc_id, RepeatedRevision::new(vec![revision]), kv_store.get_ref()).await;
sleep(Duration::from_millis(2000)).await;
},
// DocScript::Sleep(sec) => {
// sleep(Duration::from_secs(sec)).await;

View File

@ -1,5 +1,5 @@
use crate::document_test::edit_script::{DocScript, DocumentTest};
use flowy_collaboration::document::{Document, FlowyDoc};
use flowy_collaboration::document::{Document, NewlineDoc};
use lib_ot::{core::Interval, rich_text::RichTextAttribute};
#[rustfmt::skip]
@ -76,7 +76,7 @@ async fn delta_sync_while_editing_with_attribute() {
#[actix_rt::test]
async fn delta_sync_with_http_request() {
let test = DocumentTest::new().await;
let mut document = Document::new::<FlowyDoc>();
let mut document = Document::new::<NewlineDoc>();
document.insert(0, "123").unwrap();
document.insert(3, "456").unwrap();
@ -94,14 +94,13 @@ async fn delta_sync_with_http_request() {
#[actix_rt::test]
async fn delta_sync_with_server_push_delta() {
let test = DocumentTest::new().await;
let mut document = Document::new::<FlowyDoc>();
let mut document = Document::new::<NewlineDoc>();
document.insert(0, "123").unwrap();
let json = document.to_json();
test.run_scripts(vec![
DocScript::ClientOpenDoc,
DocScript::ServerSaveDocument(json, 3),
DocScript::ClientConnectWS,
DocScript::AssertClient(r#"[{"insert":"123\n\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"123\n\n"}]"#, 3),
])
@ -142,7 +141,7 @@ async fn delta_sync_with_server_push_delta() {
#[actix_rt::test]
async fn delta_sync_while_local_rev_less_than_server_rev() {
let test = DocumentTest::new().await;
let mut document = Document::new::<FlowyDoc>();
let mut document = Document::new::<NewlineDoc>();
document.insert(0, "123").unwrap();
let json = document.to_json();
@ -150,7 +149,7 @@ async fn delta_sync_while_local_rev_less_than_server_rev() {
DocScript::ClientOpenDoc,
DocScript::ServerSaveDocument(json, 3),
DocScript::ClientInsertText(0, "abc"),
DocScript::ClientConnectWS,
// DocScript::ClientConnectWS,
DocScript::AssertClient(r#"[{"insert":"abc\n123\n"}]"#),
DocScript::AssertServer(r#"[{"insert":"abc\n123\n"}]"#, 4),
])
@ -185,7 +184,7 @@ async fn delta_sync_while_local_rev_less_than_server_rev() {
#[actix_rt::test]
async fn delta_sync_while_local_rev_greater_than_server_rev() {
let test = DocumentTest::new().await;
let mut document = Document::new::<FlowyDoc>();
let mut document = Document::new::<NewlineDoc>();
document.insert(0, "123").unwrap();
let json = document.to_json();

View File

@ -150,7 +150,7 @@ impl ClientDocumentEditor {
async fn save_local_delta(&self, delta: RichTextDelta, md5: String) -> Result<RevId, FlowyError> {
let delta_data = delta.to_bytes();
let (base_rev_id, rev_id) = self.rev_manager.next_rev_id();
let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair();
let user_id = self.user.user_id()?;
let revision = Revision::new(&self.doc_id, base_rev_id, rev_id, delta_data, &user_id, md5);
let _ = self.rev_manager.add_local_revision(&revision).await?;

View File

@ -1,6 +1,7 @@
use async_stream::stream;
use flowy_collaboration::{
document::{history::UndoResult, Document, NewlineDoc},
entities::revision::Revision,
errors::CollaborateError,
};
@ -12,7 +13,6 @@ use lib_ot::{
};
use std::sync::Arc;
use tokio::sync::{mpsc, oneshot, RwLock};
use flowy_collaboration::document::{Document, history::UndoResult};
pub(crate) struct EditorCommandQueue {
doc_id: String,
@ -53,10 +53,29 @@ impl EditorCommandQueue {
async fn handle_message(&self, msg: EditorCommand) -> Result<(), FlowyError> {
match msg {
EditorCommand::ComposeDelta { delta, ret } => {
let result = self.composed_delta(delta).await;
let _ = ret.send(result);
let fut = || async {
let mut document = self.document.write().await;
let _ = document.compose_delta(delta)?;
let md5 = document.md5();
drop(document);
Ok::<String, CollaborateError>(md5)
};
let _ = ret.send(fut().await);
},
EditorCommand::ProcessRemoteRevision { revisions, ret } => {
EditorCommand::OverrideDelta { delta, ret } => {
let fut = || async {
let mut document = self.document.write().await;
let _ = document.set_delta(delta);
let md5 = document.md5();
drop(document);
Ok::<String, CollaborateError>(md5)
};
let _ = ret.send(fut().await);
},
EditorCommand::TransformRevision { revisions, ret } => {
let f = || async {
let mut new_delta = RichTextDelta::new();
for revision in revisions {
@ -73,15 +92,22 @@ impl EditorCommandQueue {
}
let read_guard = self.document.read().await;
let (server_prime, client_prime) = read_guard.delta().transform(&new_delta)?;
drop(read_guard);
let mut server_prime: Option<RichTextDelta> = None;
let client_prime: RichTextDelta;
if read_guard.is_empty::<NewlineDoc>() {
// Do nothing
client_prime = new_delta;
} else {
let (s_prime, c_prime) = read_guard.delta().transform(&new_delta)?;
client_prime = c_prime;
server_prime = Some(s_prime);
}
let transform_delta = TransformDeltas {
drop(read_guard);
Ok::<TransformDeltas, CollaborateError>(TransformDeltas {
client_prime,
server_prime,
};
Ok::<TransformDeltas, CollaborateError>(transform_delta)
})
};
let _ = ret.send(f().await);
},
@ -138,22 +164,6 @@ impl EditorCommandQueue {
}
Ok(())
}
#[tracing::instrument(level = "debug", skip(self, delta), fields(compose_result), err)]
async fn composed_delta(&self, delta: RichTextDelta) -> Result<String, CollaborateError> {
// tracing::debug!("{:?} thread handle_message", thread::current(),);
let mut document = self.document.write().await;
tracing::Span::current().record(
"composed_delta",
&format!("doc_id:{} - {}", &self.doc_id, delta.to_json()).as_str(),
);
let _ = document.compose_delta(delta)?;
let md5 = document.md5();
drop(document);
Ok(md5)
}
}
pub(crate) type Ret<T> = oneshot::Sender<Result<T, CollaborateError>>;
@ -166,7 +176,11 @@ pub(crate) enum EditorCommand {
delta: RichTextDelta,
ret: Ret<DocumentMD5>,
},
ProcessRemoteRevision {
OverrideDelta {
delta: RichTextDelta,
ret: Ret<DocumentMD5>,
},
TransformRevision {
revisions: Vec<Revision>,
ret: Ret<TransformDeltas>,
},
@ -212,5 +226,5 @@ pub(crate) enum EditorCommand {
pub(crate) struct TransformDeltas {
pub client_prime: RichTextDelta,
pub server_prime: RichTextDelta,
pub server_prime: Option<RichTextDelta>,
}

View File

@ -6,6 +6,7 @@ use crate::{
},
sql_tables::{RevisionChangeset, RevisionTableState},
};
use std::borrow::Cow;
use flowy_collaboration::entities::revision::{Revision, RevisionRange, RevisionState};
use flowy_database::ConnectionPool;
@ -46,13 +47,14 @@ impl RevisionCache {
if self.memory_cache.contains(&revision.rev_id) {
return Err(FlowyError::internal().context(format!("Duplicate remote revision id: {}", revision.rev_id)));
}
let state = state.as_ref().clone();
let rev_id = revision.rev_id;
let record = RevisionRecord {
revision,
state,
write_to_disk,
};
self.memory_cache.add(&record).await;
self.memory_cache.add(Cow::Borrowed(&record)).await;
self.set_latest_rev_id(rev_id);
Ok(record)
}
@ -63,10 +65,9 @@ impl RevisionCache {
match self.memory_cache.get(&rev_id).await {
None => match self.disk_cache.read_revision_records(&self.doc_id, Some(vec![rev_id])) {
Ok(mut records) => {
if records.is_empty() {
tracing::warn!("Can't find revision in {} with rev_id: {}", &self.doc_id, rev_id);
if !records.is_empty() {
assert_eq!(records.len(), 1);
}
assert_eq!(records.len(), 1);
records.pop()
},
Err(e) => {
@ -108,23 +109,20 @@ impl RevisionCache {
}
#[tracing::instrument(level = "debug", skip(self, doc_id, revisions))]
pub fn reset_document(&self, doc_id: &str, revisions: Vec<Revision>) -> FlowyResult<()> {
let disk_cache = self.disk_cache.clone();
let conn = disk_cache.db_pool().get().map_err(internal_error)?;
let records = revisions
pub async fn reset_document(&self, doc_id: &str, revisions: Vec<Revision>) -> FlowyResult<()> {
let revision_records = revisions
.to_vec()
.into_iter()
.map(|revision| RevisionRecord {
revision,
state: RevisionState::Local,
write_to_disk: true,
write_to_disk: false,
})
.collect::<Vec<_>>();
conn.immediate_transaction::<_, FlowyError, _>(|| {
let _ = disk_cache.delete_revision_records(doc_id, None, &*conn)?;
let _ = disk_cache.write_revision_records(records, &*conn)?;
Ok(())
})
let _ = self.memory_cache.reset_with_revisions(&revision_records).await?;
let _ = self.disk_cache.reset_with_revisions(doc_id, revision_records)?;
Ok(())
}
#[inline]

View File

@ -38,6 +38,8 @@ pub trait RevisionDiskCache: Sync + Send {
conn: &SqliteConnection,
) -> Result<(), Self::Error>;
fn reset_with_revisions(&self, doc_id: &str, revision_records: Vec<RevisionRecord>) -> Result<(), Self::Error>;
fn db_pool(&self) -> Arc<ConnectionPool>;
}
@ -99,6 +101,15 @@ impl RevisionDiskCache for Persistence {
Ok(())
}
fn reset_with_revisions(&self, doc_id: &str, revision_records: Vec<RevisionRecord>) -> Result<(), Self::Error> {
let conn = self.db_pool().get().map_err(internal_error)?;
conn.immediate_transaction::<_, FlowyError, _>(|| {
let _ = self.delete_revision_records(doc_id, None, &*conn)?;
let _ = self.write_revision_records(revision_records, &*conn)?;
Ok(())
})
}
fn db_pool(&self) -> Arc<ConnectionPool> { self.pool.clone() }
}

View File

@ -2,7 +2,8 @@ use crate::services::doc::RevisionRecord;
use dashmap::DashMap;
use flowy_collaboration::entities::revision::RevisionRange;
use flowy_error::{FlowyError, FlowyResult};
use std::{sync::Arc, time::Duration};
use futures_util::{stream, stream::StreamExt};
use std::{borrow::Cow, sync::Arc, time::Duration};
use tokio::{sync::RwLock, task::JoinHandle};
pub(crate) trait RevisionMemoryCacheDelegate: Send + Sync {
@ -31,7 +32,12 @@ impl RevisionMemoryCache {
pub(crate) fn contains(&self, rev_id: &i64) -> bool { self.revs_map.contains_key(rev_id) }
pub(crate) async fn add(&self, record: &RevisionRecord) {
pub(crate) async fn add<'a>(&'a self, record: Cow<'a, RevisionRecord>) {
let record = match record {
Cow::Borrowed(record) => record.clone(),
Cow::Owned(record) => record,
};
if let Some(rev_id) = self.pending_write_revs.read().await.last() {
if *rev_id >= record.revision.rev_id {
tracing::error!("Duplicated revision added to memory_cache");
@ -71,6 +77,21 @@ impl RevisionMemoryCache {
Ok(revs)
}
pub(crate) async fn reset_with_revisions(&self, revision_records: &[RevisionRecord]) -> FlowyResult<()> {
self.revs_map.clear();
self.pending_write_revs.write().await.clear();
if let Some(handler) = self.defer_save.write().await.take() {
handler.abort();
}
stream::iter(revision_records)
.for_each(|record| async move {
self.add(Cow::Borrowed(record)).await;
})
.await;
Ok(())
}
async fn make_checkpoint(&self) {
// https://github.com/async-graphql/async-graphql/blob/ed8449beec3d9c54b94da39bab33cec809903953/src/dataloader/mod.rs#L362
if let Some(handler) = self.defer_save.write().await.take() {

View File

@ -7,11 +7,13 @@ use dashmap::DashMap;
use flowy_collaboration::{
entities::{
doc::DocumentInfo,
prelude::pair_rev_id_from_revisions,
revision::{RepeatedRevision, Revision, RevisionRange, RevisionState},
},
util::{md5, RevIdCounter},
};
use flowy_error::FlowyResult;
use futures_util::{future, stream, stream::StreamExt};
use lib_infra::future::FutureResult;
use lib_ot::{
core::{Operation, OperationTransformable},
@ -30,13 +32,13 @@ pub struct RevisionManager {
user_id: String,
rev_id_counter: RevIdCounter,
cache: Arc<RevisionCache>,
sync_seq: Arc<RevisionSyncSeq>,
sync_seq: Arc<RevisionSyncSequence>,
}
impl RevisionManager {
pub fn new(user_id: &str, doc_id: &str, cache: Arc<RevisionCache>) -> Self {
let rev_id_counter = RevIdCounter::new(0);
let sync_seq = Arc::new(RevisionSyncSeq::new());
let sync_seq = Arc::new(RevisionSyncSequence::new());
Self {
doc_id: doc_id.to_string(),
user_id: user_id.to_owned(),
@ -62,10 +64,13 @@ impl RevisionManager {
#[tracing::instrument(level = "debug", skip(self, revisions), err)]
pub async fn reset_document(&self, revisions: RepeatedRevision) -> FlowyResult<()> {
self.cache.reset_document(&self.doc_id, revisions.into_inner())
let rev_id = pair_rev_id_from_revisions(&revisions).1;
let _ = self.cache.reset_document(&self.doc_id, revisions.into_inner()).await?;
self.rev_id_counter.set(rev_id);
Ok(())
}
#[tracing::instrument(level = "debug", skip(self, revision))]
#[tracing::instrument(level = "debug", skip(self, revision), err)]
pub async fn add_remote_revision(&self, revision: &Revision) -> Result<(), FlowyError> {
if revision.delta_data.is_empty() {
return Err(FlowyError::internal().context("Delta data should be empty"));
@ -98,7 +103,7 @@ impl RevisionManager {
pub fn set_rev_id(&self, rev_id: i64) { self.rev_id_counter.set(rev_id); }
pub fn next_rev_id(&self) -> (i64, i64) {
pub fn next_rev_id_pair(&self) -> (i64, i64) {
let cur = self.rev_id_counter.value();
let next = self.rev_id_counter.next();
(cur, next)
@ -131,23 +136,23 @@ impl RevisionManager {
}
}
struct RevisionSyncSeq {
struct RevisionSyncSequence {
revs_map: Arc<DashMap<i64, RevisionRecord>>,
local_revs: Arc<RwLock<VecDeque<i64>>>,
}
impl std::default::Default for RevisionSyncSeq {
impl std::default::Default for RevisionSyncSequence {
fn default() -> Self {
let local_revs = Arc::new(RwLock::new(VecDeque::new()));
RevisionSyncSeq {
RevisionSyncSequence {
revs_map: Arc::new(DashMap::new()),
local_revs,
}
}
}
impl RevisionSyncSeq {
fn new() -> Self { RevisionSyncSeq::default() }
impl RevisionSyncSequence {
fn new() -> Self { RevisionSyncSequence::default() }
async fn add_revision(&self, record: RevisionRecord) -> Result<(), OTError> {
// The last revision's rev_id must be greater than the new one.
@ -216,22 +221,16 @@ impl RevisionLoader {
let _ = self.cache.add(revision.clone(), RevisionState::Ack, true).await?;
revisions = vec![revision];
} else {
for record in &records {
match record.state {
RevisionState::Local => {
//
match self
.cache
.add(record.revision.clone(), RevisionState::Local, false)
.await
{
Ok(_) => {},
Err(e) => tracing::error!("{}", e),
}
},
RevisionState::Ack => {},
}
}
// Sync the records if their state is RevisionState::Local.
stream::iter(records.clone())
.filter(|record| future::ready(record.state == RevisionState::Local))
.for_each(|record| async move {
match self.cache.add(record.revision, record.state, false).await {
Ok(_) => {},
Err(e) => tracing::error!("{}", e),
}
})
.await;
revisions = records.into_iter().map(|record| record.revision).collect::<_>();
}
@ -274,7 +273,7 @@ fn correct_delta_if_need(delta: &mut RichTextDelta) {
}
#[cfg(feature = "flowy_unit_test")]
impl RevisionSyncSeq {
impl RevisionSyncSequence {
#[allow(dead_code)]
pub fn revs_map(&self) -> Arc<DashMap<i64, RevisionRecord>> { self.revs_map.clone() }
#[allow(dead_code)]

View File

@ -94,7 +94,7 @@ impl DocumentWSReceiver for HttpWebSocketManager {
fn receive_ws_data(&self, doc_data: DocumentServerWSData) {
match self.ws_msg_tx.send(doc_data) {
Ok(_) => {},
Err(e) => tracing::error!("Propagate ws message failed. {}", e),
Err(e) => tracing::error!(" Propagate ws message failed. {}", e),
}
}
@ -106,6 +106,10 @@ impl DocumentWSReceiver for HttpWebSocketManager {
}
}
impl std::ops::Drop for HttpWebSocketManager {
fn drop(&mut self) { tracing::debug!("{} HttpWebSocketManager was drop", self.doc_id) }
}
pub trait DocumentWSSteamConsumer: Send + Sync {
fn receive_push_revision(&self, bytes: Bytes) -> FutureResult<(), FlowyError>;
fn receive_ack(&self, id: String, ty: DocumentServerWSDataType) -> FutureResult<(), FlowyError>;
@ -177,7 +181,7 @@ impl DocumentWSStream {
.await
.map_err(internal_error)?;
tracing::debug!("[DocumentStream]: receives new message: {:?}", ty);
tracing::debug!("[DocumentStream]: new message: {:?}", ty);
match ty {
DocumentServerWSDataType::ServerPushRev => {
let _ = self.consumer.receive_push_revision(bytes).await?;

View File

@ -19,7 +19,8 @@ use flowy_error::{internal_error, FlowyError, FlowyResult};
use lib_infra::future::FutureResult;
use crate::services::doc::web_socket::local_ws_impl::LocalWebSocketManager;
use flowy_collaboration::entities::ws::DocumentServerWSDataType;
use flowy_collaboration::entities::{revision::pair_rev_id_from_revisions, ws::DocumentServerWSDataType};
use lib_ot::rich_text::RichTextDelta;
use lib_ws::WSConnectState;
use std::{collections::VecDeque, convert::TryFrom, sync::Arc};
use tokio::sync::{broadcast, mpsc::UnboundedSender, oneshot, RwLock};
@ -162,6 +163,68 @@ impl DocumentWSSinkDataProvider for DocumentWSSinkDataProviderAdapter {
}
}
async fn transform_pushed_revisions(
revisions: &[Revision],
edit_cmd: &UnboundedSender<EditorCommand>,
) -> FlowyResult<TransformDeltas> {
let (ret, rx) = oneshot::channel::<CollaborateResult<TransformDeltas>>();
// Transform the revision
let _ = edit_cmd.send(EditorCommand::TransformRevision {
revisions: revisions.to_vec(),
ret,
});
let transformed_delta = rx.await.map_err(internal_error)??;
Ok(transformed_delta)
}
async fn compose_pushed_delta(
delta: RichTextDelta,
edit_cmd: &UnboundedSender<EditorCommand>,
) -> FlowyResult<DocumentMD5> {
// compose delta
let (ret, rx) = oneshot::channel::<CollaborateResult<DocumentMD5>>();
let _ = edit_cmd.send(EditorCommand::ComposeDelta { delta, ret });
let md5 = rx.await.map_err(internal_error)??;
Ok(md5)
}
async fn override_client_delta(
delta: RichTextDelta,
edit_cmd: &UnboundedSender<EditorCommand>,
) -> FlowyResult<DocumentMD5> {
let (ret, rx) = oneshot::channel::<CollaborateResult<DocumentMD5>>();
let _ = edit_cmd.send(EditorCommand::OverrideDelta { delta, ret });
let md5 = rx.await.map_err(internal_error)??;
Ok(md5)
}
async fn make_client_and_server_revision(
doc_id: &str,
user_id: &str,
base_rev_id: i64,
rev_id: i64,
client_delta: RichTextDelta,
server_delta: Option<RichTextDelta>,
md5: DocumentMD5,
) -> (Revision, Option<Revision>) {
let client_revision = Revision::new(
&doc_id,
base_rev_id,
rev_id,
client_delta.to_bytes(),
&user_id,
md5.clone(),
);
match server_delta {
None => (client_revision, None),
Some(server_delta) => {
let server_revision = Revision::new(&doc_id, base_rev_id, rev_id, server_delta.to_bytes(), &user_id, md5);
(client_revision, Some(server_revision))
},
}
}
#[tracing::instrument(level = "debug", skip(edit_cmd_tx, rev_manager, bytes))]
pub(crate) async fn handle_push_rev(
doc_id: &str,
@ -170,67 +233,60 @@ pub(crate) async fn handle_push_rev(
rev_manager: Arc<RevisionManager>,
bytes: Bytes,
) -> FlowyResult<Option<Revision>> {
// Transform the revision
let (ret, rx) = oneshot::channel::<CollaborateResult<TransformDeltas>>();
let mut revisions = RepeatedRevision::try_from(bytes)?.into_inner();
if revisions.is_empty() {
return Ok(None);
}
let first_revision = revisions.first().unwrap();
if let Some(local_revision) = rev_manager.get_revision(first_revision.rev_id).await {
if local_revision.md5 != first_revision.md5 {
if local_revision.md5 == first_revision.md5 {
// The local revision is equal to the pushed revision. Just ignore it.
revisions = revisions.split_off(1);
if revisions.is_empty() {
return Ok(None);
}
} else {
return Ok(None);
}
}
let revisions = revisions.split_off(1);
if revisions.is_empty() {
return Ok(None);
}
let _ = edit_cmd_tx.send(EditorCommand::ProcessRemoteRevision {
revisions: revisions.clone(),
ret,
});
let TransformDeltas {
client_prime,
server_prime,
} = rx.await.map_err(internal_error)??;
} = transform_pushed_revisions(&revisions, &edit_cmd_tx).await?;
match server_prime {
None => {
// The server_prime is None means the client local revisions conflict with the
// server, and it needs to override the client delta.
let md5 = override_client_delta(client_prime.clone(), &edit_cmd_tx).await?;
let repeated_revision = RepeatedRevision::new(revisions);
assert_eq!(repeated_revision.last().unwrap().md5, md5);
let _ = rev_manager.reset_document(repeated_revision).await?;
Ok(None)
},
Some(server_prime) => {
let md5 = compose_pushed_delta(client_prime.clone(), &edit_cmd_tx).await?;
for revision in &revisions {
let _ = rev_manager.add_remote_revision(revision).await?;
}
let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair();
let (client_revision, server_revision) = make_client_and_server_revision(
doc_id,
user_id,
base_rev_id,
rev_id,
client_prime,
Some(server_prime),
md5,
)
.await;
for revision in &revisions {
let _ = rev_manager.add_remote_revision(revision).await?;
// save the client revision
let _ = rev_manager.add_remote_revision(&client_revision).await?;
Ok(server_revision)
},
}
// compose delta
let (ret, rx) = oneshot::channel::<CollaborateResult<DocumentMD5>>();
let _ = edit_cmd_tx.send(EditorCommand::ComposeDelta {
delta: client_prime.clone(),
ret,
});
let md5 = rx.await.map_err(internal_error)??;
let (local_base_rev_id, local_rev_id) = rev_manager.next_rev_id();
// save the revision
let revision = Revision::new(
&doc_id,
local_base_rev_id,
local_rev_id,
client_prime.to_bytes(),
&user_id,
md5.clone(),
);
let _ = rev_manager.add_remote_revision(&revision).await?;
// send the server_prime delta
Ok(Some(Revision::new(
&doc_id,
local_base_rev_id,
local_rev_id,
server_prime.to_bytes(),
&user_id,
md5,
)))
}
#[derive(Clone)]

View File

@ -1,116 +0,0 @@
use std::{
ffi::OsString,
fs,
fs::File,
io,
io::{Read, Write},
path::{Path, PathBuf},
str,
time::SystemTime,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FileId(pub(crate) String);
impl std::convert::From<String> for FileId {
fn from(s: String) -> Self { FileId(s) }
}
#[derive(Debug, Clone, Copy)]
pub enum CharacterEncoding {
Utf8,
Utf8WithBom,
}
const UTF8_BOM: &str = "\u{feff}";
impl CharacterEncoding {
pub(crate) fn guess(s: &[u8]) -> Self {
if s.starts_with(UTF8_BOM.as_bytes()) {
CharacterEncoding::Utf8WithBom
} else {
CharacterEncoding::Utf8
}
}
}
#[derive(Debug)]
pub enum FileError {
Io(io::Error, PathBuf),
UnknownEncoding(PathBuf),
HasChanged(PathBuf),
}
#[derive(Clone, Debug)]
pub struct FileInfo {
pub path: PathBuf,
pub modified_time: Option<SystemTime>,
pub has_changed: bool,
pub encoding: CharacterEncoding,
}
#[allow(dead_code)]
pub(crate) fn try_load_file<P>(path: P) -> Result<(String, FileInfo), FileError>
where
P: AsRef<Path>,
{
let mut f = File::open(path.as_ref()).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;
let mut bytes = Vec::new();
f.read_to_end(&mut bytes).map_err(|e| FileError::Io(e, path.as_ref().to_owned()))?;
let encoding = CharacterEncoding::guess(&bytes);
let s = try_decode(bytes, encoding, path.as_ref())?;
let info = FileInfo {
encoding,
path: path.as_ref().to_owned(),
modified_time: get_modified_time(&path),
has_changed: false,
};
Ok((s, info))
}
#[allow(dead_code)]
pub(crate) fn try_save(path: &Path, text: &str, encoding: CharacterEncoding, _file_info: Option<&FileInfo>) -> io::Result<()> {
let tmp_extension = path.extension().map_or_else(
|| OsString::from("swp"),
|ext| {
let mut ext = ext.to_os_string();
ext.push(".swp");
ext
},
);
let tmp_path = &path.with_extension(tmp_extension);
let mut f = File::create(tmp_path)?;
match encoding {
CharacterEncoding::Utf8WithBom => f.write_all(UTF8_BOM.as_bytes())?,
CharacterEncoding::Utf8 => (),
}
f.write_all(text.as_bytes())?;
fs::rename(tmp_path, path)?;
Ok(())
}
#[allow(dead_code)]
pub(crate) fn try_decode(bytes: Vec<u8>, encoding: CharacterEncoding, path: &Path) -> Result<String, FileError> {
match encoding {
CharacterEncoding::Utf8 => Ok(String::from(
str::from_utf8(&bytes).map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?,
)),
CharacterEncoding::Utf8WithBom => {
let s = String::from_utf8(bytes).map_err(|_e| FileError::UnknownEncoding(path.to_owned()))?;
Ok(String::from(&s[UTF8_BOM.len()..]))
},
}
}
#[allow(dead_code)]
pub(crate) fn create_dir_if_not_exist(dir: &str) -> Result<(), io::Error> {
let _ = fs::create_dir_all(dir)?;
Ok(())
}
pub(crate) fn get_modified_time<P: AsRef<Path>>(path: P) -> Option<SystemTime> {
File::open(path).and_then(|f| f.metadata()).and_then(|meta| meta.modified()).ok()
}

View File

@ -1,120 +0,0 @@
use crate::{module::DocumentUser, services::file_manager::*};
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::Arc,
};
pub struct FileManager {
pub user: Arc<dyn DocumentUser>,
open_files: HashMap<PathBuf, FileId>,
file_info: HashMap<FileId, FileInfo>,
}
impl FileManager {
pub(crate) fn new(user: Arc<dyn DocumentUser>) -> Self {
Self {
user,
open_files: HashMap::new(),
file_info: HashMap::new(),
}
}
#[allow(dead_code)]
pub(crate) fn open<T>(&mut self, path: &Path, id: T) -> Result<String, FileError>
where
T: Into<FileId>,
{
if !path.exists() {
return Ok("".to_string());
}
let file_id = id.into();
let (s, info) = try_load_file(path)?;
self.open_files.insert(path.to_owned(), file_id.clone());
self.file_info.insert(file_id, info);
Ok(s)
}
#[allow(dead_code)]
pub(crate) fn save<T>(&mut self, path: &Path, text: &String, id: T) -> Result<(), FileError>
where
T: Into<FileId>,
{
let file_id = id.into();
let is_existing = self.file_info.contains_key(&file_id);
if is_existing {
self.save_existing(path, text, &file_id)
} else {
self.save_new(path, text, &file_id)
}
}
#[allow(dead_code)]
pub(crate) fn close<T>(&mut self, id: T)
where
T: Into<FileId>,
{
if let Some(file_info) = self.file_info.remove(&id.into()) {
self.open_files.remove(&file_info.path);
}
}
#[allow(dead_code)]
pub(crate) fn create_file(&mut self, id: &str, dir: &str, text: &str) -> Result<PathBuf, FileError> {
let path = PathBuf::from(format!("{}/{}", dir, id));
let file_id: FileId = id.to_owned().into();
tracing::info!("Create document at: {:?}", path);
let _ = self.save_new(&path, text, &file_id)?;
Ok(path)
}
#[allow(dead_code)]
pub(crate) fn get_info(&self, id: &FileId) -> Option<&FileInfo> { self.file_info.get(id) }
#[allow(dead_code)]
pub(crate) fn get_file_id(&self, path: &Path) -> Option<FileId> { self.open_files.get(path).cloned() }
#[allow(dead_code)]
pub fn check_file(&mut self, path: &Path, id: &FileId) -> bool {
if let Some(info) = self.file_info.get_mut(&id) {
let modified_time = get_modified_time(path);
if modified_time != info.modified_time {
info.has_changed = true
}
return info.has_changed;
}
false
}
#[allow(dead_code)]
fn save_new(&mut self, path: &Path, text: &str, id: &FileId) -> Result<(), FileError> {
try_save(path, text, CharacterEncoding::Utf8, self.get_info(id))
.map_err(|e| FileError::Io(e, path.to_owned()))?;
let info = FileInfo {
encoding: CharacterEncoding::Utf8,
path: path.to_owned(),
modified_time: get_modified_time(path),
has_changed: false,
};
self.open_files.insert(path.to_owned(), id.clone());
self.file_info.insert(id.clone(), info);
Ok(())
}
#[allow(dead_code)]
fn save_existing(&mut self, path: &Path, text: &String, id: &FileId) -> Result<(), FileError> {
let prev_path = self.file_info[id].path.clone();
if prev_path != path {
self.save_new(path, text, id)?;
self.open_files.remove(&prev_path);
} else if self.file_info[&id].has_changed {
return Err(FileError::HasChanged(path.to_owned()));
} else {
let encoding = self.file_info[&id].encoding;
try_save(path, text, encoding, self.get_info(id)).map_err(|e| FileError::Io(e, path.to_owned()))?;
self.file_info.get_mut(&id).unwrap().modified_time = get_modified_time(path);
}
Ok(())
}
}

View File

@ -1,5 +0,0 @@
mod file;
mod manager;
pub use file::*;
pub use manager::*;

View File

@ -1,6 +1,6 @@
#![cfg_attr(rustfmt, rustfmt::skip)]
use crate::editor::{TestBuilder, TestOp::*};
use flowy_collaboration::document::{FlowyDoc, PlainDoc};
use flowy_collaboration::document::{NewlineDoc, PlainDoc};
use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr};
use unicode_segmentation::UnicodeSegmentation;
use lib_ot::rich_text::RichTextDelta;
@ -85,7 +85,7 @@ fn attributes_bold_added_with_new_line() {
r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\na\n"},{"insert":"456","attributes":{"bold":"true"}},{"insert":"\n"}]"#,
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -128,7 +128,7 @@ fn attributes_bold_added_italic() {
r#"[{"insert":"12345678","attributes":{"bold":"true","italic":"true"}},{"insert":"\n"}]"#,
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -390,7 +390,7 @@ fn attributes_header_insert_newline_at_middle() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -415,7 +415,7 @@ fn attributes_header_insert_double_newline_at_middle() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -430,7 +430,7 @@ fn attributes_header_insert_newline_at_trailing() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -446,7 +446,7 @@ fn attributes_header_insert_double_newline_at_trailing() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -460,7 +460,7 @@ fn attributes_link_added() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -479,7 +479,7 @@ fn attributes_link_format_with_bold() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -498,7 +498,7 @@ fn attributes_link_insert_char_at_head() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -513,7 +513,7 @@ fn attributes_link_insert_char_at_middle() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -532,7 +532,7 @@ fn attributes_link_insert_char_at_trailing() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -547,7 +547,7 @@ fn attributes_link_insert_newline_at_middle() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -563,7 +563,7 @@ fn attributes_link_auto_format() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -579,7 +579,7 @@ fn attributes_link_auto_format_exist() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -595,7 +595,7 @@ fn attributes_link_auto_format_exist2() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -606,7 +606,7 @@ fn attributes_bullet_added() {
AssertDocJson(0, r#"[{"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -627,7 +627,7 @@ fn attributes_bullet_added_2() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -644,7 +644,7 @@ fn attributes_bullet_remove_partial() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -660,7 +660,7 @@ fn attributes_bullet_auto_exit() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -700,7 +700,7 @@ fn attributes_preserve_block_when_insert_newline_inside() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -717,7 +717,7 @@ fn attributes_preserve_header_format_on_merge() {
AssertDocJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -736,7 +736,7 @@ fn attributes_format_emoji() {
r#"[{"insert":"👋 "},{"insert":"\n","attributes":{"header":1}}]"#,
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -756,7 +756,7 @@ fn attributes_preserve_list_format_on_merge() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -795,5 +795,5 @@ fn delta_compose() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}

View File

@ -5,7 +5,7 @@ mod serde_test;
mod undo_redo_test;
use derive_more::Display;
use flowy_collaboration::document::{CustomDocument, Document};
use flowy_collaboration::document::{Document, InitialDocumentText};
use lib_ot::{
core::*,
rich_text::{RichTextAttribute, RichTextAttributes, RichTextDelta},
@ -266,7 +266,7 @@ impl TestBuilder {
}
}
pub fn run_scripts<C: CustomDocument>(mut self, scripts: Vec<TestOp>) {
pub fn run_scripts<C: InitialDocumentText>(mut self, scripts: Vec<TestOp>) {
self.documents = vec![Document::new::<C>(), Document::new::<C>()];
self.primes = vec![None, None];
self.deltas = vec![None, None];

View File

@ -1,6 +1,6 @@
#![allow(clippy::all)]
use crate::editor::{Rng, TestBuilder, TestOp::*};
use flowy_collaboration::document::{FlowyDoc, PlainDoc};
use flowy_collaboration::document::{NewlineDoc, PlainDoc};
use lib_ot::{
core::*,
rich_text::{AttributeBuilder, RichTextAttribute, RichTextAttributes, RichTextDelta},
@ -731,5 +731,5 @@ fn delta_compose_with_missing_delta() {
AssertDocJson(0, r#"[{"insert":"1234\n"}]"#),
AssertStr(1, r#"4\n"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}

View File

@ -1,11 +1,11 @@
use crate::editor::{TestBuilder, TestOp::*};
use flowy_collaboration::document::{FlowyDoc, PlainDoc, RECORD_THRESHOLD};
use flowy_collaboration::document::{NewlineDoc, PlainDoc, RECORD_THRESHOLD};
use lib_ot::core::{Interval, NEW_LINE, WHITESPACE};
#[test]
fn history_insert_undo() {
let ops = vec![Insert(0, "123", 0), Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#)];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -19,7 +19,7 @@ fn history_insert_undo_with_lagging() {
Undo(0),
AssertDocJson(0, r#"[{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -32,7 +32,7 @@ fn history_insert_redo() {
Redo(0),
AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -51,7 +51,7 @@ fn history_insert_redo_with_lagging() {
Undo(0),
AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -62,7 +62,7 @@ fn history_bold_undo() {
Undo(0),
AssertDocJson(0, r#"[{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -74,7 +74,7 @@ fn history_bold_undo_with_lagging() {
Undo(0),
AssertDocJson(0, r#"[{"insert":"123\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -87,7 +87,7 @@ fn history_bold_redo() {
Redo(0),
AssertDocJson(0, r#" [{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -101,7 +101,7 @@ fn history_bold_redo_with_lagging() {
Redo(0),
AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -133,7 +133,7 @@ fn history_delete_undo_2() {
Undo(0),
AssertDocJson(0, r#"[{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -160,7 +160,7 @@ fn history_delete_undo_with_lagging() {
"#,
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -174,7 +174,7 @@ fn history_delete_redo() {
Redo(0),
AssertDocJson(0, r#"[{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -193,7 +193,7 @@ fn history_replace_undo() {
Undo(0),
AssertDocJson(0, r#"[{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -214,7 +214,7 @@ fn history_replace_undo_with_lagging() {
Undo(0),
AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":"true"}},{"insert":"\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -233,7 +233,7 @@ fn history_replace_redo() {
"#,
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -252,7 +252,7 @@ fn history_header_added_undo() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -271,7 +271,7 @@ fn history_link_added_undo() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -290,7 +290,7 @@ fn history_link_auto_format_undo_with_lagging() {
AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -313,7 +313,7 @@ fn history_bullet_undo() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -341,7 +341,7 @@ fn history_bullet_undo_with_lagging() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}
#[test]
@ -368,5 +368,5 @@ fn history_undo_attribute_on_merge_between_line() {
),
];
TestBuilder::new().run_scripts::<FlowyDoc>(ops);
TestBuilder::new().run_scripts::<NewlineDoc>(ops);
}

View File

@ -14,18 +14,18 @@ use crate::{
errors::CollaborateError,
};
pub trait CustomDocument {
fn init_delta() -> RichTextDelta;
pub trait InitialDocumentText {
fn initial_delta() -> RichTextDelta;
}
pub struct PlainDoc();
impl CustomDocument for PlainDoc {
fn init_delta() -> RichTextDelta { RichTextDelta::new() }
impl InitialDocumentText for PlainDoc {
fn initial_delta() -> RichTextDelta { RichTextDelta::new() }
}
pub struct FlowyDoc();
impl CustomDocument for FlowyDoc {
fn init_delta() -> RichTextDelta { initial_delta() }
pub struct NewlineDoc();
impl InitialDocumentText for NewlineDoc {
fn initial_delta() -> RichTextDelta { initial_delta() }
}
pub struct Document {
@ -37,7 +37,7 @@ pub struct Document {
}
impl Document {
pub fn new<C: CustomDocument>() -> Self { Self::from_delta(C::init_delta()) }
pub fn new<C: InitialDocumentText>() -> Self { Self::from_delta(C::initial_delta()) }
pub fn from_delta(delta: RichTextDelta) -> Self {
Document {
@ -193,6 +193,8 @@ impl Document {
},
}
}
pub fn is_empty<C: InitialDocumentText>(&self) -> bool { self.delta == C::initial_delta() }
}
impl Document {

View File

@ -108,13 +108,9 @@ impl std::ops::DerefMut for RepeatedRevision {
impl RepeatedRevision {
pub fn new(items: Vec<Revision>) -> Self {
if cfg!(debug_assertions) {
let mut sorted_items = items.clone();
sorted_items.sort_by(|a, b| a.rev_id.cmp(&b.rev_id));
assert_eq!(sorted_items, items, "The items passed in should be sorted")
}
Self { items }
let mut sorted_items = items.clone();
sorted_items.sort_by(|a, b| a.rev_id.cmp(&b.rev_id));
Self { items: sorted_items }
}
pub fn empty() -> Self { RepeatedRevision { items: vec![] } }
@ -122,6 +118,21 @@ impl RepeatedRevision {
pub fn into_inner(self) -> Vec<Revision> { self.items }
}
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)
}
}
#[derive(Clone, Debug, ProtoBuf, Default)]
pub struct RevId {
#[pb(index = 1)]
@ -186,6 +197,10 @@ pub enum RevisionState {
Ack = 1,
}
impl AsRef<RevisionState> for RevisionState {
fn as_ref(&self) -> &RevisionState { &self }
}
#[derive(Debug, ProtoBuf_Enum, Clone, Eq, PartialEq)]
pub enum RevType {
DeprecatedLocal = 0,

View File

@ -88,7 +88,10 @@ impl ServerDocumentManager {
let doc_id = client_data.doc_id.clone();
match self.get_document_handler(&doc_id).await {
None => Ok(()),
None => {
tracing::warn!("Document:{} doesn't exist, ignore pinging", doc_id);
Ok(())
},
Some(handler) => {
let _ = handler.apply_ping(doc_id.clone(), rev_id, user).await?;
Ok(())
@ -216,7 +219,7 @@ impl OpenDocHandle {
impl std::ops::Drop for OpenDocHandle {
fn drop(&mut self) {
log::debug!("{} OpenDocHandle drop", self.doc_id);
log::debug!("{} OpenDocHandle was drop", self.doc_id);
}
}
@ -313,7 +316,7 @@ impl DocumentCommandQueue {
impl std::ops::Drop for DocumentCommandQueue {
fn drop(&mut self) {
log::debug!("{} DocumentCommandQueue drop", self.doc_id);
log::debug!("{} DocumentCommandQueue was drop", self.doc_id);
}
}

View File

@ -101,12 +101,15 @@ impl RevisionSynchronizer {
// delta.
let from_rev_id = first_revision.rev_id;
let to_rev_id = server_base_rev_id;
let _ = self.push_revisions_to_user(user, persistence, from_rev_id, to_rev_id);
let _ = self
.push_revisions_to_user(user, persistence, from_rev_id, to_rev_id)
.await;
},
}
Ok(())
}
#[tracing::instrument(level = "debug", skip(self, user, persistence), err)]
pub async fn pong(
&self,
doc_id: String,
@ -127,7 +130,9 @@ impl RevisionSynchronizer {
let from_rev_id = rev_id;
let to_rev_id = server_base_rev_id;
tracing::trace!("[Pong]: Push revisions to user");
let _ = self.push_revisions_to_user(user, persistence, from_rev_id, to_rev_id);
let _ = self
.push_revisions_to_user(user, persistence, from_rev_id, to_rev_id)
.await;
},
}
Ok(())
@ -201,6 +206,7 @@ impl RevisionSynchronizer {
},
};
tracing::debug!("Push revision: {} -> {} to client", from, to);
let data = DocumentServerWSDataBuilder::build_push_message(&self.doc_id, revisions);
user.receive(SyncResponse::Push(data));
}