sync with server when ws becomes avaliable

This commit is contained in:
appflowy 2021-10-04 14:24:35 +08:00
parent 15d628c750
commit c748d17daf
26 changed files with 370 additions and 199 deletions

View file

@ -16,6 +16,7 @@ class ErrorCode extends $pb.ProtobufEnum {
static const ErrorCode UndoFail = ErrorCode._(200, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UndoFail'); static const ErrorCode UndoFail = ErrorCode._(200, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UndoFail');
static const ErrorCode RedoFail = ErrorCode._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RedoFail'); static const ErrorCode RedoFail = ErrorCode._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RedoFail');
static const ErrorCode OutOfBound = ErrorCode._(202, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OutOfBound'); static const ErrorCode OutOfBound = ErrorCode._(202, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OutOfBound');
static const ErrorCode DuplicateRevision = ErrorCode._(400, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DuplicateRevision');
static const ErrorCode UserUnauthorized = ErrorCode._(999, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized'); static const ErrorCode UserUnauthorized = ErrorCode._(999, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
static const ErrorCode InternalError = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InternalError'); static const ErrorCode InternalError = ErrorCode._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InternalError');
@ -26,6 +27,7 @@ class ErrorCode extends $pb.ProtobufEnum {
UndoFail, UndoFail,
RedoFail, RedoFail,
OutOfBound, OutOfBound,
DuplicateRevision,
UserUnauthorized, UserUnauthorized,
InternalError, InternalError,
]; ];

View file

@ -18,13 +18,14 @@ const ErrorCode$json = const {
const {'1': 'UndoFail', '2': 200}, const {'1': 'UndoFail', '2': 200},
const {'1': 'RedoFail', '2': 201}, const {'1': 'RedoFail', '2': 201},
const {'1': 'OutOfBound', '2': 202}, const {'1': 'OutOfBound', '2': 202},
const {'1': 'DuplicateRevision', '2': 400},
const {'1': 'UserUnauthorized', '2': 999}, const {'1': 'UserUnauthorized', '2': 999},
const {'1': 'InternalError', '2': 1000}, const {'1': 'InternalError', '2': 1000},
], ],
}; };
/// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`. /// Descriptor for `ErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSEAoMRG9jSWRJbnZhbGlkEAASDwoLRG9jTm90Zm91bmQQARISCg5Xc0Nvbm5lY3RFcnJvchAKEg0KCFVuZG9GYWlsEMgBEg0KCFJlZG9GYWlsEMkBEg8KCk91dE9mQm91bmQQygESFQoQVXNlclVuYXV0aG9yaXplZBDnBxISCg1JbnRlcm5hbEVycm9yEOgH'); final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode('CglFcnJvckNvZGUSEAoMRG9jSWRJbnZhbGlkEAASDwoLRG9jTm90Zm91bmQQARISCg5Xc0Nvbm5lY3RFcnJvchAKEg0KCFVuZG9GYWlsEMgBEg0KCFJlZG9GYWlsEMkBEg8KCk91dE9mQm91bmQQygESFgoRRHVwbGljYXRlUmV2aXNpb24QkAMSFQoQVXNlclVuYXV0aG9yaXplZBDnBxISCg1JbnRlcm5hbEVycm9yEOgH');
@$core.Deprecated('Use docErrorDescriptor instead') @$core.Deprecated('Use docErrorDescriptor instead')
const DocError$json = const { const DocError$json = const {
'1': 'DocError', '1': 'DocError',

View file

@ -90,10 +90,14 @@ path = "src/lib.rs"
name = "backend" name = "backend"
path = "src/main.rs" path = "src/main.rs"
[features]
flowy_test = []
[dev-dependencies] [dev-dependencies]
parking_lot = "0.11" parking_lot = "0.11"
once_cell = "1.7.2" once_cell = "1.7.2"
linkify = "0.5.0" linkify = "0.5.0"
backend = { path = ".", features = ["flowy_test"]}
flowy-user = { path = "../rust-lib/flowy-user", features = ["http_server"] } flowy-user = { path = "../rust-lib/flowy-user", features = ["http_server"] }
flowy-workspace = { path = "../rust-lib/flowy-workspace", features = ["http_server"] } flowy-workspace = { path = "../rust-lib/flowy-workspace", features = ["http_server"] }
flowy-ws = { path = "../rust-lib/flowy-ws" } flowy-ws = { path = "../rust-lib/flowy-ws" }

View file

@ -50,7 +50,7 @@ pub(crate) async fn read_doc(pool: &PgPool, params: QueryDocParams) -> Result<Do
} }
#[tracing::instrument(level = "debug", skip(pool, params), err)] #[tracing::instrument(level = "debug", skip(pool, params), err)]
pub(crate) async fn update_doc(pool: &PgPool, mut params: UpdateDocParams) -> Result<(), ServerError> { pub async fn update_doc(pool: &PgPool, mut params: UpdateDocParams) -> Result<(), ServerError> {
let doc_id = Uuid::parse_str(&params.doc_id)?; let doc_id = Uuid::parse_str(&params.doc_id)?;
let mut transaction = pool let mut transaction = pool
.begin() .begin()

View file

@ -107,7 +107,7 @@ impl EditDocActor {
user: user.clone(), user: user.clone(),
socket: socket.clone(), socket: socket.clone(),
}; };
let _ = ret.send(self.edit_doc.new_connection(user, rev_id, self.pg_pool.clone()).await); let _ = ret.send(self.edit_doc.new_doc_user(user, rev_id).await);
}, },
} }
} }

View file

@ -5,7 +5,6 @@ use crate::service::{
}; };
use actix_web::web::Data; use actix_web::web::Data;
use crate::service::doc::edit::interval::Interval;
use bytes::Bytes; use bytes::Bytes;
use dashmap::DashMap; use dashmap::DashMap;
use flowy_document::{ use flowy_document::{
@ -54,9 +53,18 @@ impl ServerEditDoc {
pub fn document_json(&self) -> String { self.document.read().to_json() } pub fn document_json(&self) -> String { self.document.read().to_json() }
pub async fn new_connection(&self, user: EditUser, rev_id: i64, _pg_pool: Data<PgPool>) -> Result<(), ServerError> { #[tracing::instrument(
level = "debug",
skip(self, user),
fields(
user_id = %user.id(),
rev_id = %rev_id,
)
)]
pub async fn new_doc_user(&self, user: EditUser, rev_id: i64) -> Result<(), ServerError> {
self.users.insert(user.id(), user.clone()); self.users.insert(user.id(), user.clone());
let cur_rev_id = self.rev_id.load(SeqCst); let cur_rev_id = self.rev_id.load(SeqCst);
if cur_rev_id > rev_id { if cur_rev_id > rev_id {
let doc_delta = self.document.read().delta().clone(); let doc_delta = self.document.read().delta().clone();
let cli_revision = self.mk_revision(rev_id, doc_delta); let cli_revision = self.mk_revision(rev_id, doc_delta);

View file

@ -1,10 +1,13 @@
use crate::document::helper::{DocScript, DocumentTest}; use crate::document::helper::{DocScript, DocumentTest};
use flowy_document::services::doc::{Document, FlowyDoc};
use flowy_ot::core::Delta;
#[actix_rt::test] #[actix_rt::test]
async fn edit_doc_insert_text() { async fn sync_doc_insert_text() {
let test = DocumentTest::new().await; let test = DocumentTest::new().await;
test.run_scripts(vec![ test.run_scripts(vec![
DocScript::ConnectWs, DocScript::ConnectWs,
DocScript::OpenDoc,
DocScript::SendText(0, "abc"), DocScript::SendText(0, "abc"),
DocScript::SendText(3, "123"), DocScript::SendText(3, "123"),
DocScript::SendText(6, "efg"), DocScript::SendText(6, "efg"),
@ -15,21 +18,52 @@ async fn edit_doc_insert_text() {
} }
#[actix_rt::test] #[actix_rt::test]
async fn edit_doc_insert_large_text() { async fn sync_open_empty_doc_and_sync_from_server() {
let test = DocumentTest::new().await; let test = DocumentTest::new().await;
let mut document = Document::new::<FlowyDoc>();
document.insert(0, "123").unwrap();
document.insert(3, "456").unwrap();
let json = document.to_json();
test.run_scripts(vec![ test.run_scripts(vec![
DocScript::ConnectWs, DocScript::SetServerDocument(json, 3),
DocScript::SendText(0, "abc"), DocScript::OpenDoc,
DocScript::SendText(0, "abc"), DocScript::AssertClient(r#"[{"insert":"123456\n"}]"#),
DocScript::SendText(0, "abc"), DocScript::AssertServer(r#"[{"insert":"123456\n"}]"#),
DocScript::SendText(0, "abc"), ])
DocScript::SendText(0, "abc"), .await;
DocScript::SendText(0, "abc"), }
DocScript::SendText(0, "abc"),
DocScript::SendText(0, "abc"), #[actix_rt::test]
/* DocScript::AssertClient(r#"[{"insert":"abc123efg\n"}]"#), async fn sync_open_empty_doc_and_sync_from_server_using_ws() {
* DocScript::AssertServer(r#"[{"insert":"abc123efg\n"}]"#), */ let test = DocumentTest::new().await;
let mut document = Document::new::<FlowyDoc>();
document.insert(0, "123").unwrap();
let json = document.to_json();
test.run_scripts(vec![
DocScript::OpenDoc,
DocScript::SetServerDocument(json, 3),
DocScript::ConnectWs,
DocScript::AssertClient(r#"[{"insert":"123\n\n"}]"#),
])
.await;
}
#[actix_rt::test]
async fn sync_open_non_empty_doc_and_sync_with_sever() {
let test = DocumentTest::new().await;
let mut document = Document::new::<FlowyDoc>();
document.insert(0, "123").unwrap();
let json = document.to_json();
test.run_scripts(vec![
DocScript::OpenDoc,
DocScript::SetServerDocument(json, 3),
DocScript::SendText(0, "abc"),
DocScript::ConnectWs,
DocScript::AssertClient(r#"[{"insert":"123\nabc\n"}]"#),
// DocScript::AssertServer(r#"[{"insert":"123\nabc\n"}]"#),
]) ])
.await; .await;
} }

View file

@ -5,7 +5,7 @@ use futures_util::{stream, stream::StreamExt};
use sqlx::PgPool; use sqlx::PgPool;
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
use backend::service::doc::doc::DocManager; use backend::service::doc::{crud::update_doc, doc::DocManager};
use flowy_document::{entities::doc::QueryDocParams, services::doc::edit::ClientEditDoc as ClientEditDocContext}; use flowy_document::{entities::doc::QueryDocParams, services::doc::edit::ClientEditDoc as ClientEditDocContext};
use flowy_net::config::ServerConfig; use flowy_net::config::ServerConfig;
use flowy_test::{workspace::ViewTest, FlowyTest}; use flowy_test::{workspace::ViewTest, FlowyTest};
@ -13,6 +13,10 @@ use flowy_user::services::user::UserSession;
// use crate::helper::*; // use crate::helper::*;
use crate::helper::{spawn_server, TestServer}; use crate::helper::{spawn_server, TestServer};
use flowy_document::protobuf::UpdateDocParams;
use flowy_ot::core::Delta;
use parking_lot::RwLock;
use serde::__private::Formatter;
pub struct DocumentTest { pub struct DocumentTest {
server: TestServer, server: TestServer,
@ -24,6 +28,22 @@ pub enum DocScript {
SendText(usize, &'static str), SendText(usize, &'static str),
AssertClient(&'static str), AssertClient(&'static str),
AssertServer(&'static str), AssertServer(&'static str),
SetServerDocument(String, i64), // delta_json, rev_id
OpenDoc,
}
impl std::fmt::Display for DocScript {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let name = match self {
DocScript::ConnectWs => "ConnectWs",
DocScript::SendText(_, _) => "SendText",
DocScript::AssertClient(_) => "AssertClient",
DocScript::AssertServer(_) => "AssertServer",
DocScript::SetServerDocument(_, _) => "SetServerDocument",
DocScript::OpenDoc => "OpenDoc",
};
f.write_str(&format!("******** {} *********", name))
}
} }
impl DocumentTest { impl DocumentTest {
@ -37,54 +57,92 @@ impl DocumentTest {
pub async fn run_scripts(self, scripts: Vec<DocScript>) { pub async fn run_scripts(self, scripts: Vec<DocScript>) {
let _ = self.flowy_test.sign_up().await; let _ = self.flowy_test.sign_up().await;
let DocumentTest { server, flowy_test } = self; let DocumentTest { server, flowy_test } = self;
let script_context = ScriptContext { let script_context = Arc::new(RwLock::new(ScriptContext::new(flowy_test, server).await));
client_edit_context: create_doc(&flowy_test).await,
user_session: flowy_test.sdk.user_session.clone(),
doc_manager: server.app_ctx.doc_biz.manager.clone(),
pool: Data::new(server.pg_pool.clone()),
};
run_scripts(script_context, scripts).await; run_scripts(script_context, scripts).await;
std::mem::forget(flowy_test);
sleep(Duration::from_secs(5)).await; sleep(Duration::from_secs(5)).await;
} }
} }
#[derive(Clone)] #[derive(Clone)]
struct ScriptContext { struct ScriptContext {
client_edit_context: Arc<ClientEditDocContext>, client_edit_context: Option<Arc<ClientEditDocContext>>,
flowy_test: FlowyTest,
user_session: Arc<UserSession>, user_session: Arc<UserSession>,
doc_manager: Arc<DocManager>, doc_manager: Arc<DocManager>,
pool: Data<PgPool>, pool: Data<PgPool>,
doc_id: String,
} }
async fn run_scripts(context: ScriptContext, scripts: Vec<DocScript>) { impl ScriptContext {
async fn new(flowy_test: FlowyTest, server: TestServer) -> Self {
let user_session = flowy_test.sdk.user_session.clone();
let doc_id = create_doc(&flowy_test).await;
Self {
client_edit_context: None,
flowy_test,
user_session,
doc_manager: server.app_ctx.doc_biz.manager.clone(),
pool: Data::new(server.pg_pool.clone()),
doc_id,
}
}
async fn open_doc(&mut self) {
let flowy_document = self.flowy_test.sdk.flowy_document.clone();
let pool = self.user_session.db_pool().unwrap();
let doc_id = self.doc_id.clone();
let edit_context = flowy_document.open(QueryDocParams { doc_id }, pool).await.unwrap();
self.client_edit_context = Some(edit_context);
}
fn client_edit_context(&self) -> Arc<ClientEditDocContext> { self.client_edit_context.as_ref().unwrap().clone() }
}
impl Drop for ScriptContext {
fn drop(&mut self) {
// std::mem::forget(self.flowy_test);
}
}
async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript>) {
let mut fut_scripts = vec![]; let mut fut_scripts = vec![];
for script in scripts { for script in scripts {
let context = context.clone(); let context = context.clone();
let fut = async move { let fut = async move {
let doc_id = context.read().doc_id.clone();
match script { match script {
DocScript::ConnectWs => { DocScript::ConnectWs => {
let token = context.user_session.token().unwrap(); // sleep(Duration::from_millis(300)).await;
let _ = context.user_session.start_ws_connection(&token).await.unwrap(); let user_session = context.read().user_session.clone();
let token = user_session.token().unwrap();
let _ = user_session.start_ws_connection(&token).await.unwrap();
},
DocScript::OpenDoc => {
context.write().open_doc().await;
}, },
DocScript::SendText(index, s) => { DocScript::SendText(index, s) => {
context.client_edit_context.insert(index, s).await.unwrap(); context.read().client_edit_context().insert(index, s).await.unwrap();
}, },
DocScript::AssertClient(s) => { DocScript::AssertClient(s) => {
let json = context.client_edit_context.doc_json().await.unwrap(); sleep(Duration::from_millis(300)).await;
let json = context.read().client_edit_context().doc_json().await.unwrap();
assert_eq(s, &json); assert_eq(s, &json);
}, },
DocScript::AssertServer(s) => { DocScript::AssertServer(s) => {
let edit_doc = context sleep(Duration::from_millis(300)).await;
.doc_manager
.get(&context.client_edit_context.doc_id, context.pool) let pg_pool = context.read().pool.clone();
.await let doc_manager = context.read().doc_manager.clone();
.unwrap() let edit_doc = doc_manager.get(&doc_id, pg_pool).await.unwrap().unwrap();
.unwrap();
let json = edit_doc.document_json().await.unwrap(); let json = edit_doc.document_json().await.unwrap();
assert_eq(s, &json); assert_eq(s, &json);
}, },
DocScript::SetServerDocument(json, rev_id) => {
let pg_pool = context.read().pool.clone();
save_doc(&doc_id, json, rev_id, pg_pool).await;
},
} }
}; };
fut_scripts.push(fut); fut_scripts.push(fut);
@ -94,6 +152,8 @@ async fn run_scripts(context: ScriptContext, scripts: Vec<DocScript>) {
while let Some(script) = stream.next().await { while let Some(script) = stream.next().await {
let _ = script.await; let _ = script.await;
} }
std::mem::forget(context);
} }
fn assert_eq(expect: &str, receive: &str) { fn assert_eq(expect: &str, receive: &str) {
@ -104,16 +164,16 @@ fn assert_eq(expect: &str, receive: &str) {
assert_eq!(expect, receive); assert_eq!(expect, receive);
} }
async fn create_doc(flowy_test: &FlowyTest) -> Arc<ClientEditDocContext> { async fn create_doc(flowy_test: &FlowyTest) -> String {
let view_test = ViewTest::new(flowy_test).await; let view_test = ViewTest::new(flowy_test).await;
let doc_id = view_test.view.id.clone(); let doc_id = view_test.view.id.clone();
let user_session = flowy_test.sdk.user_session.clone(); doc_id
let flowy_document = flowy_test.sdk.flowy_document.clone(); }
let edit_context = flowy_document async fn save_doc(doc_id: &str, json: String, rev_id: i64, pool: Data<PgPool>) {
.open(QueryDocParams { doc_id }, user_session.db_pool().unwrap()) let mut params = UpdateDocParams::new();
.await params.set_doc_id(doc_id.to_owned());
.unwrap(); params.set_data(json);
params.set_rev_id(rev_id);
edit_context let _ = update_doc(pool.get_ref(), params).await.unwrap();
} }

View file

@ -10,6 +10,10 @@ pub enum RevType {
Remote = 1, Remote = 1,
} }
impl RevType {
pub fn is_local(&self) -> bool { self == &RevType::Local }
}
impl std::default::Default for RevType { impl std::default::Default for RevType {
fn default() -> Self { RevType::Local } fn default() -> Self { RevType::Local }
} }
@ -54,7 +58,7 @@ impl std::fmt::Display for RevId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}", self.inner)) } fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}", self.inner)) }
} }
#[derive(Clone, Default, ProtoBuf)] #[derive(PartialEq, Eq, Clone, Default, ProtoBuf)]
pub struct Revision { pub struct Revision {
#[pb(index = 1)] #[pb(index = 1)]
pub base_rev_id: i64, pub base_rev_id: i64,

View file

@ -51,6 +51,7 @@ impl DocError {
static_doc_error!(undo, ErrorCode::UndoFail); static_doc_error!(undo, ErrorCode::UndoFail);
static_doc_error!(redo, ErrorCode::RedoFail); static_doc_error!(redo, ErrorCode::RedoFail);
static_doc_error!(out_of_bound, ErrorCode::OutOfBound); static_doc_error!(out_of_bound, ErrorCode::OutOfBound);
static_doc_error!(duplicate_rev, ErrorCode::DuplicateRevision);
} }
pub fn internal_error<T>(e: T) -> DocError pub fn internal_error<T>(e: T) -> DocError
@ -63,27 +64,30 @@ where
#[derive(Debug, Clone, ProtoBuf_Enum, Display, PartialEq, Eq)] #[derive(Debug, Clone, ProtoBuf_Enum, Display, PartialEq, Eq)]
pub enum ErrorCode { pub enum ErrorCode {
#[display(fmt = "DocIdInvalid")] #[display(fmt = "DocIdInvalid")]
DocIdInvalid = 0, DocIdInvalid = 0,
#[display(fmt = "DocNotfound")] #[display(fmt = "DocNotfound")]
DocNotfound = 1, DocNotfound = 1,
#[display(fmt = "Document websocket error")] #[display(fmt = "Document websocket error")]
WsConnectError = 10, WsConnectError = 10,
#[display(fmt = "Undo failed")] #[display(fmt = "Undo failed")]
UndoFail = 200, UndoFail = 200,
#[display(fmt = "Redo failed")] #[display(fmt = "Redo failed")]
RedoFail = 201, RedoFail = 201,
#[display(fmt = "Interval out of bound")] #[display(fmt = "Interval out of bound")]
OutOfBound = 202, OutOfBound = 202,
#[display(fmt = "Duplicate revision")]
DuplicateRevision = 400,
#[display(fmt = "UserUnauthorized")] #[display(fmt = "UserUnauthorized")]
UserUnauthorized = 999, UserUnauthorized = 999,
#[display(fmt = "InternalError")] #[display(fmt = "InternalError")]
InternalError = 1000, InternalError = 1000,
} }
impl std::default::Default for ErrorCode { impl std::default::Default for ErrorCode {

View file

@ -29,7 +29,7 @@ pub struct FlowyDocument {
impl FlowyDocument { impl FlowyDocument {
pub fn new( pub fn new(
user: Arc<dyn DocumentUser>, user: Arc<dyn DocumentUser>,
ws_manager: Arc<RwLock<WsDocumentManager>>, ws_manager: Arc<WsDocumentManager>,
server_config: &ServerConfig, server_config: &ServerConfig,
) -> FlowyDocument { ) -> FlowyDocument {
let server = construct_doc_server(server_config); let server = construct_doc_server(server_config);

View file

@ -221,6 +221,7 @@ pub enum ErrorCode {
UndoFail = 200, UndoFail = 200,
RedoFail = 201, RedoFail = 201,
OutOfBound = 202, OutOfBound = 202,
DuplicateRevision = 400,
UserUnauthorized = 999, UserUnauthorized = 999,
InternalError = 1000, InternalError = 1000,
} }
@ -238,6 +239,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
200 => ::std::option::Option::Some(ErrorCode::UndoFail), 200 => ::std::option::Option::Some(ErrorCode::UndoFail),
201 => ::std::option::Option::Some(ErrorCode::RedoFail), 201 => ::std::option::Option::Some(ErrorCode::RedoFail),
202 => ::std::option::Option::Some(ErrorCode::OutOfBound), 202 => ::std::option::Option::Some(ErrorCode::OutOfBound),
400 => ::std::option::Option::Some(ErrorCode::DuplicateRevision),
999 => ::std::option::Option::Some(ErrorCode::UserUnauthorized), 999 => ::std::option::Option::Some(ErrorCode::UserUnauthorized),
1000 => ::std::option::Option::Some(ErrorCode::InternalError), 1000 => ::std::option::Option::Some(ErrorCode::InternalError),
_ => ::std::option::Option::None _ => ::std::option::Option::None
@ -252,6 +254,7 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
ErrorCode::UndoFail, ErrorCode::UndoFail,
ErrorCode::RedoFail, ErrorCode::RedoFail,
ErrorCode::OutOfBound, ErrorCode::OutOfBound,
ErrorCode::DuplicateRevision,
ErrorCode::UserUnauthorized, ErrorCode::UserUnauthorized,
ErrorCode::InternalError, ErrorCode::InternalError,
]; ];
@ -284,35 +287,38 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
static file_descriptor_proto_data: &'static [u8] = b"\ static file_descriptor_proto_data: &'static [u8] = b"\
\n\x0cerrors.proto\"<\n\x08DocError\x12\x1e\n\x04code\x18\x01\x20\x01(\ \n\x0cerrors.proto\"<\n\x08DocError\x12\x1e\n\x04code\x18\x01\x20\x01(\
\x0e2\n.ErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03msg*\ \x0e2\n.ErrorCodeR\x04code\x12\x10\n\x03msg\x18\x02\x20\x01(\tR\x03msg*\
\x9c\x01\n\tErrorCode\x12\x10\n\x0cDocIdInvalid\x10\0\x12\x0f\n\x0bDocNo\ \xb4\x01\n\tErrorCode\x12\x10\n\x0cDocIdInvalid\x10\0\x12\x0f\n\x0bDocNo\
tfound\x10\x01\x12\x12\n\x0eWsConnectError\x10\n\x12\r\n\x08UndoFail\x10\ tfound\x10\x01\x12\x12\n\x0eWsConnectError\x10\n\x12\r\n\x08UndoFail\x10\
\xc8\x01\x12\r\n\x08RedoFail\x10\xc9\x01\x12\x0f\n\nOutOfBound\x10\xca\ \xc8\x01\x12\r\n\x08RedoFail\x10\xc9\x01\x12\x0f\n\nOutOfBound\x10\xca\
\x01\x12\x15\n\x10UserUnauthorized\x10\xe7\x07\x12\x12\n\rInternalError\ \x01\x12\x16\n\x11DuplicateRevision\x10\x90\x03\x12\x15\n\x10UserUnautho\
\x10\xe8\x07J\xf8\x03\n\x06\x12\x04\0\0\x0f\x01\n\x08\n\x01\x0c\x12\x03\ rized\x10\xe7\x07\x12\x12\n\rInternalError\x10\xe8\x07J\xa1\x04\n\x06\
\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\ \x12\x04\0\0\x10\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\
\x03\x02\x08\x10\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x17\n\x0c\n\x05\ \x04\x02\0\x05\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x10\n\x0b\n\x04\
\x04\0\x02\0\x06\x12\x03\x03\x04\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\ \x04\0\x02\0\x12\x03\x03\x04\x17\n\x0c\n\x05\x04\0\x02\0\x06\x12\x03\x03\
\x03\x0e\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x15\x16\n\x0b\n\x04\ \x04\r\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0e\x12\n\x0c\n\x05\x04\0\
\x04\0\x02\x01\x12\x03\x04\x04\x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\ \x02\0\x03\x12\x03\x03\x15\x16\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\
\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\x05\ \x13\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\
\x04\0\x02\x01\x03\x12\x03\x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x0f\ \x02\x01\x01\x12\x03\x04\x0b\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\
\x01\n\n\n\x03\x05\0\x01\x12\x03\x06\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\ \x04\x11\x12\n\n\n\x02\x05\0\x12\x04\x06\0\x10\x01\n\n\n\x03\x05\0\x01\
\x03\x07\x04\x15\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x07\x04\x10\n\x0c\n\ \x12\x03\x06\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\x07\x04\x15\n\x0c\n\
\x05\x05\0\x02\0\x02\x12\x03\x07\x13\x14\n\x0b\n\x04\x05\0\x02\x01\x12\ \x05\x05\0\x02\0\x01\x12\x03\x07\x04\x10\n\x0c\n\x05\x05\0\x02\0\x02\x12\
\x03\x08\x04\x14\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x0f\n\x0c\ \x03\x07\x13\x14\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x08\x04\x14\n\x0c\n\
\n\x05\x05\0\x02\x01\x02\x12\x03\x08\x12\x13\n\x0b\n\x04\x05\0\x02\x02\ \x05\x05\0\x02\x01\x01\x12\x03\x08\x04\x0f\n\x0c\n\x05\x05\0\x02\x01\x02\
\x12\x03\t\x04\x18\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\t\x04\x12\n\x0c\ \x12\x03\x08\x12\x13\n\x0b\n\x04\x05\0\x02\x02\x12\x03\t\x04\x18\n\x0c\n\
\n\x05\x05\0\x02\x02\x02\x12\x03\t\x15\x17\n\x0b\n\x04\x05\0\x02\x03\x12\ \x05\x05\0\x02\x02\x01\x12\x03\t\x04\x12\n\x0c\n\x05\x05\0\x02\x02\x02\
\x03\n\x04\x13\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\n\x04\x0c\n\x0c\n\ \x12\x03\t\x15\x17\n\x0b\n\x04\x05\0\x02\x03\x12\x03\n\x04\x13\n\x0c\n\
\x05\x05\0\x02\x03\x02\x12\x03\n\x0f\x12\n\x0b\n\x04\x05\0\x02\x04\x12\ \x05\x05\0\x02\x03\x01\x12\x03\n\x04\x0c\n\x0c\n\x05\x05\0\x02\x03\x02\
\x03\x0b\x04\x13\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x0c\n\x0c\ \x12\x03\n\x0f\x12\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x0b\x04\x13\n\x0c\n\
\n\x05\x05\0\x02\x04\x02\x12\x03\x0b\x0f\x12\n\x0b\n\x04\x05\0\x02\x05\ \x05\x05\0\x02\x04\x01\x12\x03\x0b\x04\x0c\n\x0c\n\x05\x05\0\x02\x04\x02\
\x12\x03\x0c\x04\x15\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x0e\n\ \x12\x03\x0b\x0f\x12\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x0c\x04\x15\n\x0c\
\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x0c\x11\x14\n\x0b\n\x04\x05\0\x02\ \n\x05\x05\0\x02\x05\x01\x12\x03\x0c\x04\x0e\n\x0c\n\x05\x05\0\x02\x05\
\x06\x12\x03\r\x04\x1b\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x14\n\ \x02\x12\x03\x0c\x11\x14\n\x0b\n\x04\x05\0\x02\x06\x12\x03\r\x04\x1c\n\
\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\r\x17\x1a\n\x0b\n\x04\x05\0\x02\x07\ \x0c\n\x05\x05\0\x02\x06\x01\x12\x03\r\x04\x15\n\x0c\n\x05\x05\0\x02\x06\
\x12\x03\x0e\x04\x19\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x11\n\ \x02\x12\x03\r\x18\x1b\n\x0b\n\x04\x05\0\x02\x07\x12\x03\x0e\x04\x1b\n\
\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\x0e\x14\x18b\x06proto3\ \x0c\n\x05\x05\0\x02\x07\x01\x12\x03\x0e\x04\x14\n\x0c\n\x05\x05\0\x02\
\x07\x02\x12\x03\x0e\x17\x1a\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0f\x04\
\x19\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0f\x04\x11\n\x0c\n\x05\x05\0\
\x02\x08\x02\x12\x03\x0f\x14\x18b\x06proto3\
"; ";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT; static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View file

@ -11,6 +11,7 @@ enum ErrorCode {
UndoFail = 200; UndoFail = 200;
RedoFail = 201; RedoFail = 201;
OutOfBound = 202; OutOfBound = 202;
DuplicateRevision = 400;
UserUnauthorized = 999; UserUnauthorized = 999;
InternalError = 1000; InternalError = 1000;
} }

View file

@ -27,20 +27,20 @@ use flowy_ot::core::Delta;
pub(crate) struct DocController { pub(crate) struct DocController {
server: Server, server: Server,
doc_sql: Arc<DocTableSql>, doc_sql: Arc<DocTableSql>,
ws: Arc<RwLock<WsDocumentManager>>, ws_manager: Arc<WsDocumentManager>,
cache: Arc<DocCache>, cache: Arc<DocCache>,
user: Arc<dyn DocumentUser>, user: Arc<dyn DocumentUser>,
} }
impl DocController { impl DocController {
pub(crate) fn new(server: Server, user: Arc<dyn DocumentUser>, ws: Arc<RwLock<WsDocumentManager>>) -> Self { pub(crate) fn new(server: Server, user: Arc<dyn DocumentUser>, ws: Arc<WsDocumentManager>) -> Self {
let doc_sql = Arc::new(DocTableSql {}); let doc_sql = Arc::new(DocTableSql {});
let cache = Arc::new(DocCache::new()); let cache = Arc::new(DocCache::new());
let controller = Self { let controller = Self {
server, server,
doc_sql, doc_sql,
user, user,
ws, ws_manager: ws,
cache: cache.clone(), cache: cache.clone(),
}; };
controller controller
@ -74,7 +74,7 @@ impl DocController {
pub(crate) fn close(&self, doc_id: &str) -> Result<(), DocError> { pub(crate) fn close(&self, doc_id: &str) -> Result<(), DocError> {
self.cache.remove(doc_id); self.cache.remove(doc_id);
self.ws.write().remove_handler(doc_id); self.ws_manager.remove_handler(doc_id);
Ok(()) Ok(())
} }
@ -84,7 +84,7 @@ impl DocController {
let _ = self.doc_sql.delete_doc(doc_id, &*conn)?; let _ = self.doc_sql.delete_doc(doc_id, &*conn)?;
self.cache.remove(doc_id); self.cache.remove(doc_id);
self.ws.write().remove_handler(doc_id); self.ws_manager.remove_handler(doc_id);
let _ = self.delete_doc_on_server(params)?; let _ = self.delete_doc_on_server(params)?;
Ok(()) Ok(())
} }
@ -118,7 +118,7 @@ impl DocController {
// Opti: require upgradable_read lock and then upgrade to write lock using // Opti: require upgradable_read lock and then upgrade to write lock using
// RwLockUpgradableReadGuard::upgrade(xx) of ws // RwLockUpgradableReadGuard::upgrade(xx) of ws
// let doc = self.read_doc(doc_id, pool.clone()).await?; // let doc = self.read_doc(doc_id, pool.clone()).await?;
let ws_sender = self.ws.read().sender(); let ws = self.ws_manager.ws();
let token = self.user.token()?; let token = self.user.token()?;
let user = self.user.clone(); let user = self.user.clone();
let server = Arc::new(RevisionServerImpl { let server = Arc::new(RevisionServerImpl {
@ -126,8 +126,8 @@ impl DocController {
server: self.server.clone(), server: self.server.clone(),
}); });
let edit_ctx = Arc::new(ClientEditDoc::new(doc_id, pool, ws_sender, server, user).await?); let edit_ctx = Arc::new(ClientEditDoc::new(doc_id, pool, ws, server, user).await?);
self.ws.write().register_handler(doc_id, edit_ctx.clone()); self.ws_manager.register_handler(doc_id, edit_ctx.clone());
self.cache.set(edit_ctx.clone()); self.cache.set(edit_ctx.clone());
Ok(edit_ctx) Ok(edit_ctx)
} }

View file

@ -62,6 +62,11 @@ impl DocumentEditActor {
match msg { match msg {
EditMsg::Delta { delta, ret } => { EditMsg::Delta { delta, ret } => {
let result = self.document.write().await.compose_delta(&delta); let result = self.document.write().await.compose_delta(&delta);
log::debug!(
"Compose push delta: {}. result: {}",
delta.to_json(),
self.document.read().await.to_json()
);
let _ = ret.send(result); let _ = ret.send(result);
}, },
EditMsg::Insert { index, data, ret } => { EditMsg::Insert { index, data, ret } => {
@ -102,7 +107,7 @@ impl DocumentEditActor {
let data = self.document.read().await.to_json(); let data = self.document.read().await.to_json();
let _ = ret.send(Ok(data)); let _ = ret.send(Ok(data));
}, },
EditMsg::SaveRevision { rev_id, ret } => { EditMsg::SaveDocument { rev_id, ret } => {
let result = self.save_to_disk(rev_id).await; let result = self.save_to_disk(rev_id).await;
let _ = ret.send(result); let _ = ret.send(result);
}, },

View file

@ -7,16 +7,17 @@ use crate::{
module::DocumentUser, module::DocumentUser,
services::{ services::{
doc::{ doc::{
edit::{actor::DocumentEditActor, message::EditMsg}, edit::{edit_actor::DocumentEditActor, message::EditMsg},
revision::{DocRevision, RevisionCmd, RevisionManager, RevisionServer, RevisionStoreActor}, revision::{DocRevision, RevisionCmd, RevisionManager, RevisionServer, RevisionStoreActor},
UndoResult, UndoResult,
}, },
ws::{WsDocumentHandler, WsDocumentSender}, ws::{DocumentWebSocket, WsDocumentHandler},
}, },
}; };
use bytes::Bytes; use bytes::Bytes;
use flowy_database::ConnectionPool; use flowy_database::ConnectionPool;
use flowy_ot::core::{Attribute, Delta, Interval}; use flowy_ot::core::{Attribute, Delta, Interval};
use flowy_ws::WsState;
use std::{convert::TryFrom, sync::Arc}; use std::{convert::TryFrom, sync::Arc};
use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot}; use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot};
@ -33,7 +34,7 @@ impl ClientEditDoc {
pub(crate) async fn new( pub(crate) async fn new(
doc_id: &str, doc_id: &str,
pool: Arc<ConnectionPool>, pool: Arc<ConnectionPool>,
ws: Arc<dyn WsDocumentSender>, ws: Arc<dyn DocumentWebSocket>,
server: Arc<dyn RevisionServer>, server: Arc<dyn RevisionServer>,
user: Arc<dyn DocumentUser>, user: Arc<dyn DocumentUser>,
) -> DocResult<Self> { ) -> DocResult<Self> {
@ -64,7 +65,7 @@ impl ClientEditDoc {
let _ = self.document.send(msg); let _ = self.document.send(msg);
let delta_data = rx.await.map_err(internal_error)??.to_bytes(); let delta_data = rx.await.map_err(internal_error)??.to_bytes();
let rev_id = self.mk_revision(&delta_data).await?; let rev_id = self.mk_revision(&delta_data).await?;
save(rev_id.into(), self.document.clone()).await save_document(self.document.clone(), rev_id.into()).await
} }
pub async fn delete(&self, interval: Interval) -> Result<(), DocError> { pub async fn delete(&self, interval: Interval) -> Result<(), DocError> {
@ -158,7 +159,7 @@ impl ClientEditDoc {
let _ = rx.await.map_err(internal_error)??; let _ = rx.await.map_err(internal_error)??;
let rev_id = self.mk_revision(&data).await?; let rev_id = self.mk_revision(&data).await?;
save(rev_id, self.document.clone()).await save_document(self.document.clone(), rev_id).await
} }
#[cfg(feature = "flowy_test")] #[cfg(feature = "flowy_test")]
@ -182,7 +183,7 @@ impl WsDocumentHandler for ClientEditDoc {
}, },
WsDataType::PullRev => { WsDataType::PullRev => {
let range = RevisionRange::try_from(bytes)?; let range = RevisionRange::try_from(bytes)?;
let _ = rev_manager.send_revisions(range)?; let _ = rev_manager.send_revisions(range).await?;
}, },
WsDataType::NewDocUser => {}, WsDataType::NewDocUser => {},
WsDataType::Acked => { WsDataType::Acked => {
@ -200,11 +201,12 @@ impl WsDocumentHandler for ClientEditDoc {
} }
}); });
} }
fn state_changed(&self, state: &WsState) { let _ = self.rev_manager.handle_ws_state_changed(state); }
} }
async fn save(rev_id: RevId, document: UnboundedSender<EditMsg>) -> DocResult<()> { async fn save_document(document: UnboundedSender<EditMsg>, rev_id: RevId) -> DocResult<()> {
let (ret, rx) = oneshot::channel::<DocResult<()>>(); let (ret, rx) = oneshot::channel::<DocResult<()>>();
let _ = document.send(EditMsg::SaveRevision { rev_id, ret }); let _ = document.send(EditMsg::SaveDocument { rev_id, ret });
let result = rx.await.map_err(internal_error)?; let result = rx.await.map_err(internal_error)?;
result result
} }
@ -215,24 +217,16 @@ async fn handle_push_rev(
document: UnboundedSender<EditMsg>, document: UnboundedSender<EditMsg>,
) -> DocResult<()> { ) -> DocResult<()> {
let revision = Revision::try_from(rev_bytes)?; let revision = Revision::try_from(rev_bytes)?;
let _ = rev_manager.add_revision(revision).await?; let _ = rev_manager.add_revision(revision.clone()).await?;
match rev_manager.next_compose_revision() {
None => Ok(()),
Some(revision) => {
let delta = Delta::from_bytes(&revision.delta_data)?;
let (ret, rx) = oneshot::channel::<DocResult<()>>();
let msg = EditMsg::Delta { delta, ret };
let _ = document.send(msg);
match rx.await.map_err(internal_error)? { let delta = Delta::from_bytes(&revision.delta_data)?;
Ok(_) => save(revision.rev_id.into(), document).await, let (ret, rx) = oneshot::channel::<DocResult<()>>();
Err(e) => { let msg = EditMsg::Delta { delta, ret };
rev_manager.push_compose_revision(revision); let _ = document.send(msg);
Err(e) let _ = rx.await.map_err(internal_error)??;
},
} save_document(document, revision.rev_id.into()).await;
}, Ok(())
}
} }
fn spawn_rev_store_actor( fn spawn_rev_store_actor(

View file

@ -45,7 +45,7 @@ pub enum EditMsg {
Doc { Doc {
ret: Ret<String>, ret: Ret<String>,
}, },
SaveRevision { SaveDocument {
rev_id: RevId, rev_id: RevId,
ret: Ret<()>, ret: Ret<()>,
}, },

View file

@ -1,4 +1,4 @@
mod actor; mod edit_actor;
mod edit_doc; mod edit_doc;
mod message; mod message;

View file

@ -1,13 +1,13 @@
use crate::{ use crate::{
entities::doc::{RevId, RevType, Revision, RevisionRange}, entities::doc::{RevId, RevType, Revision, RevisionRange},
errors::{DocError, DocResult}, errors::{internal_error, DocError},
services::{ services::{
doc::revision::{ doc::revision::{
actor::{RevisionCmd, RevisionStoreActor}, store_actor::{RevisionCmd, RevisionStoreActor},
util::NotifyOpenDocAction, util::NotifyOpenDocAction,
}, },
util::RevIdCounter, util::RevIdCounter,
ws::WsDocumentSender, ws::DocumentWebSocket,
}, },
}; };
use flowy_infra::{ use flowy_infra::{
@ -15,6 +15,7 @@ use flowy_infra::{
retry::{ExponentialBackoff, Retry}, retry::{ExponentialBackoff, Retry},
}; };
use flowy_ot::core::Delta; use flowy_ot::core::Delta;
use flowy_ws::WsState;
use parking_lot::RwLock; use parking_lot::RwLock;
use std::{collections::VecDeque, sync::Arc}; use std::{collections::VecDeque, sync::Arc};
use tokio::sync::{mpsc, oneshot}; use tokio::sync::{mpsc, oneshot};
@ -32,9 +33,8 @@ pub struct RevisionManager {
doc_id: String, doc_id: String,
user_id: String, user_id: String,
rev_id_counter: RevIdCounter, rev_id_counter: RevIdCounter,
ws: Arc<dyn WsDocumentSender>, ws: Arc<dyn DocumentWebSocket>,
rev_store: mpsc::Sender<RevisionCmd>, rev_store: mpsc::Sender<RevisionCmd>,
pending_revs: RwLock<VecDeque<Revision>>,
} }
impl RevisionManager { impl RevisionManager {
@ -42,45 +42,37 @@ impl RevisionManager {
doc_id: &str, doc_id: &str,
user_id: &str, user_id: &str,
rev_id: RevId, rev_id: RevId,
ws: Arc<dyn WsDocumentSender>, ws: Arc<dyn DocumentWebSocket>,
rev_store: mpsc::Sender<RevisionCmd>, rev_store: mpsc::Sender<RevisionCmd>,
) -> Self { ) -> Self {
notify_open_doc(&ws, user_id, doc_id, &rev_id); notify_open_doc(&ws, user_id, doc_id, &rev_id);
let rev_id_counter = RevIdCounter::new(rev_id.into()); let rev_id_counter = RevIdCounter::new(rev_id.into());
let pending_revs = RwLock::new(VecDeque::new());
Self { Self {
doc_id: doc_id.to_string(), doc_id: doc_id.to_string(),
user_id: user_id.to_string(), user_id: user_id.to_string(),
rev_id_counter, rev_id_counter,
ws, ws,
pending_revs,
rev_store, rev_store,
} }
} }
pub fn push_compose_revision(&self, revision: Revision) { self.pending_revs.write().push_front(revision); }
pub fn next_compose_revision(&self) -> Option<Revision> { self.pending_revs.write().pop_front() }
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn add_revision(&self, revision: Revision) -> Result<(), DocError> { pub async fn add_revision(&self, revision: Revision) -> Result<(), DocError> {
let (ret, rx) = oneshot::channel();
let cmd = RevisionCmd::Revision { let cmd = RevisionCmd::Revision {
revision: revision.clone(), revision: revision.clone(),
ret,
}; };
let _ = self.rev_store.send(cmd).await; let _ = self.rev_store.send(cmd).await;
let result = rx.await.map_err(internal_error)?;
match revision.ty { if result.is_ok() && revision.ty.is_local() {
RevType::Local => match self.ws.send(revision.into()) { match self.ws.send(revision.into()) {
Ok(_) => {}, Ok(_) => {},
Err(e) => log::error!("Send delta failed: {:?}", e), Err(e) => log::error!("Send delta failed: {:?}", e),
}, };
RevType::Remote => {
self.pending_revs.write().push_back(revision);
},
} }
Ok(()) result
} }
pub fn ack_rev(&self, rev_id: RevId) -> Result<(), DocError> { pub fn ack_rev(&self, rev_id: RevId) -> Result<(), DocError> {
@ -99,23 +91,41 @@ impl RevisionManager {
(cur, next) (cur, next)
} }
pub fn send_revisions(&self, range: RevisionRange) -> Result<(), DocError> { pub async fn send_revisions(&self, range: RevisionRange) -> Result<(), DocError> {
debug_assert!(&range.doc_id == &self.doc_id); debug_assert!(&range.doc_id == &self.doc_id);
let (ret, _rx) = oneshot::channel(); let (ret, rx) = oneshot::channel();
let sender = self.rev_store.clone(); let sender = self.rev_store.clone();
let _ = sender.send(RevisionCmd::SendRevisions { range, ret }).await;
tokio::spawn(async move { let revisions = rx.await.map_err(internal_error)??;
let _ = sender.send(RevisionCmd::SendRevisions { range, ret }).await;
});
unimplemented!() unimplemented!()
// Ok(())
}
#[tracing::instrument(
level = "debug",
skip(self),
fields(
doc_id = %self.doc_id.clone(),
rev_id = %self.rev_id(),
)
)]
pub fn handle_ws_state_changed(&self, state: &WsState) {
match state {
WsState::Init => {},
WsState::Connected(_) => {
let rev_id: RevId = self.rev_id().into();
notify_open_doc(&self.ws, &self.user_id, &self.doc_id, &rev_id);
},
WsState::Disconnected(_) => {},
}
} }
} }
// FIXME: // FIXME:
// user_id may be invalid if the user switch to another account while // user_id may be invalid if the user switch to another account while
// theNotifyOpenDocAction is flying // theNotifyOpenDocAction is flying
fn notify_open_doc(ws: &Arc<dyn WsDocumentSender>, user_id: &str, doc_id: &str, rev_id: &RevId) { fn notify_open_doc(ws: &Arc<dyn DocumentWebSocket>, user_id: &str, doc_id: &str, rev_id: &RevId) {
let action = NotifyOpenDocAction::new(user_id, doc_id, rev_id, ws); let action = NotifyOpenDocAction::new(user_id, doc_id, rev_id, ws);
let strategy = ExponentialBackoff::from_millis(50).take(3); let strategy = ExponentialBackoff::from_millis(50).take(3);
let retry = Retry::spawn(strategy, action); let retry = Retry::spawn(strategy, action);

View file

@ -1,6 +1,6 @@
mod actor;
mod manager; mod manager;
mod store_actor;
mod util; mod util;
pub use actor::*;
pub use manager::*; pub use manager::*;
pub use store_actor::*;

View file

@ -18,6 +18,7 @@ use tokio::{
pub enum RevisionCmd { pub enum RevisionCmd {
Revision { Revision {
revision: Revision, revision: Revision,
ret: oneshot::Sender<DocResult<()>>,
}, },
AckRevision { AckRevision {
rev_id: RevId, rev_id: RevId,
@ -76,8 +77,9 @@ impl RevisionStoreActor {
async fn handle_message(&self, cmd: RevisionCmd) { async fn handle_message(&self, cmd: RevisionCmd) {
match cmd { match cmd {
RevisionCmd::Revision { revision } => { RevisionCmd::Revision { revision, ret } => {
self.handle_new_revision(revision).await; let result = self.handle_new_revision(revision).await;
let _ = ret.send(result);
}, },
RevisionCmd::AckRevision { rev_id } => { RevisionCmd::AckRevision { rev_id } => {
self.handle_revision_acked(rev_id).await; self.handle_revision_acked(rev_id).await;
@ -93,11 +95,16 @@ impl RevisionStoreActor {
} }
} }
async fn handle_new_revision(&self, revision: Revision) { async fn handle_new_revision(&self, revision: Revision) -> DocResult<()> {
if self.revs.contains_key(&revision.rev_id) {
return Err(DocError::duplicate_rev().context(format!("Duplicate revision id: {}", revision.rev_id)));
}
let mut operation = RevisionOperation::new(&revision); let mut operation = RevisionOperation::new(&revision);
let _receiver = operation.receiver(); let _receiver = operation.receiver();
self.revs.insert(revision.rev_id, operation); self.revs.insert(revision.rev_id, operation);
self.save_revisions().await; self.save_revisions().await;
Ok(())
} }
async fn handle_revision_acked(&self, rev_id: RevId) { async fn handle_revision_acked(&self, rev_id: RevId) {

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
entities::doc::{NewDocUser, RevId, Revision}, entities::doc::{NewDocUser, RevId, Revision},
errors::{DocError, DocResult}, errors::{DocError, DocResult},
services::ws::WsDocumentSender, services::ws::DocumentWebSocket,
sql_tables::RevState, sql_tables::RevState,
}; };
use flowy_infra::retry::Action; use flowy_infra::retry::Action;
@ -54,11 +54,11 @@ pub(crate) struct NotifyOpenDocAction {
user_id: String, user_id: String,
rev_id: RevId, rev_id: RevId,
doc_id: String, doc_id: String,
ws: Arc<dyn WsDocumentSender>, ws: Arc<dyn DocumentWebSocket>,
} }
impl NotifyOpenDocAction { impl NotifyOpenDocAction {
pub(crate) fn new(user_id: &str, doc_id: &str, rev_id: &RevId, ws: &Arc<dyn WsDocumentSender>) -> Self { pub(crate) fn new(user_id: &str, doc_id: &str, rev_id: &RevId, ws: &Arc<dyn DocumentWebSocket>) -> Self {
Self { Self {
user_id: user_id.to_owned(), user_id: user_id.to_owned(),
rev_id: rev_id.clone(), rev_id: rev_id.clone(),

View file

@ -1,43 +1,48 @@
use crate::{entities::ws::WsDocumentData, errors::DocError}; use crate::{entities::ws::WsDocumentData, errors::DocError};
use bytes::Bytes; use bytes::Bytes;
use dashmap::DashMap;
use flowy_ws::WsState;
use std::{collections::HashMap, convert::TryInto, sync::Arc}; use std::{collections::HashMap, convert::TryInto, sync::Arc};
use tokio::sync::broadcast::error::RecvError;
pub(crate) trait WsDocumentHandler: Send + Sync { pub(crate) trait WsDocumentHandler: Send + Sync {
fn receive(&self, data: WsDocumentData); fn receive(&self, data: WsDocumentData);
fn state_changed(&self, state: &WsState);
} }
pub trait WsDocumentSender: Send + Sync { pub type WsStateReceiver = tokio::sync::broadcast::Receiver<WsState>;
pub trait DocumentWebSocket: Send + Sync {
fn send(&self, data: WsDocumentData) -> Result<(), DocError>; fn send(&self, data: WsDocumentData) -> Result<(), DocError>;
fn state_notify(&self) -> WsStateReceiver;
} }
pub struct WsDocumentManager { pub struct WsDocumentManager {
sender: Arc<dyn WsDocumentSender>, ws: Arc<dyn DocumentWebSocket>,
// key: the document id // key: the document id
ws_handlers: HashMap<String, Arc<dyn WsDocumentHandler>>, handlers: Arc<DashMap<String, Arc<dyn WsDocumentHandler>>>,
} }
impl WsDocumentManager { impl WsDocumentManager {
pub fn new(sender: Arc<dyn WsDocumentSender>) -> Self { pub fn new(ws: Arc<dyn DocumentWebSocket>) -> Self {
Self { let handlers: Arc<DashMap<String, Arc<dyn WsDocumentHandler>>> = Arc::new(DashMap::new());
sender, listen_ws_state_changed(ws.clone(), handlers.clone());
ws_handlers: HashMap::new(),
} Self { ws, handlers }
} }
pub(crate) fn register_handler(&mut self, id: &str, handler: Arc<dyn WsDocumentHandler>) { pub(crate) fn register_handler(&self, id: &str, handler: Arc<dyn WsDocumentHandler>) {
if self.ws_handlers.contains_key(id) { if self.handlers.contains_key(id) {
log::error!("Duplicate handler registered for {:?}", id); log::error!("Duplicate handler registered for {:?}", id);
} }
self.handlers.insert(id.to_string(), handler);
self.ws_handlers.insert(id.to_string(), handler);
} }
pub(crate) fn remove_handler(&mut self, id: &str) { self.ws_handlers.remove(id); } pub(crate) fn remove_handler(&self, id: &str) { self.handlers.remove(id); }
pub fn receive_data(&self, data: Bytes) { pub fn handle_ws_data(&self, data: Bytes) {
let data: WsDocumentData = data.try_into().unwrap(); let data: WsDocumentData = data.try_into().unwrap();
match self.ws_handlers.get(&data.doc_id) { match self.handlers.get(&data.doc_id) {
None => { None => {
log::error!("Can't find any source handler for {:?}", data.doc_id); log::error!("Can't find any source handler for {:?}", data.doc_id);
}, },
@ -47,5 +52,25 @@ impl WsDocumentManager {
} }
} }
pub fn sender(&self) -> Arc<dyn WsDocumentSender> { self.sender.clone() } pub fn ws(&self) -> Arc<dyn DocumentWebSocket> { self.ws.clone() }
}
#[tracing::instrument(level = "debug", skip(ws, handlers))]
fn listen_ws_state_changed(ws: Arc<dyn DocumentWebSocket>, handlers: Arc<DashMap<String, Arc<dyn WsDocumentHandler>>>) {
let mut notify = ws.state_notify();
tokio::spawn(async move {
loop {
match notify.recv().await {
Ok(state) => {
handlers.iter().for_each(|handle| {
handle.value().state_changed(&state);
});
},
Err(e) => {
log::error!("Websocket state notify error: {:?}", e);
break;
},
}
}
});
} }

View file

@ -2,10 +2,10 @@ use bytes::Bytes;
use flowy_document::{ use flowy_document::{
errors::DocError, errors::DocError,
module::DocumentUser, module::DocumentUser,
prelude::{WsDocumentManager, WsDocumentSender}, prelude::{DocumentWebSocket, WsDocumentManager},
}; };
use flowy_document::entities::ws::WsDocumentData; use flowy_document::{entities::ws::WsDocumentData, errors::internal_error, services::ws::WsStateReceiver};
use flowy_user::{errors::ErrorCode, services::user::UserSession}; use flowy_user::{errors::ErrorCode, services::user::UserSession};
use flowy_ws::{WsMessage, WsMessageHandler, WsModule}; use flowy_ws::{WsMessage, WsMessageHandler, WsModule};
use parking_lot::RwLock; use parking_lot::RwLock;
@ -18,7 +18,7 @@ pub struct DocumentDepsResolver {
impl DocumentDepsResolver { impl DocumentDepsResolver {
pub fn new(user_session: Arc<UserSession>) -> Self { Self { user_session } } pub fn new(user_session: Arc<UserSession>) -> Self { Self { user_session } }
pub fn split_into(self) -> (Arc<dyn DocumentUser>, Arc<RwLock<WsDocumentManager>>) { pub fn split_into(self) -> (Arc<dyn DocumentUser>, Arc<WsDocumentManager>) {
let user = Arc::new(DocumentUserImpl { let user = Arc::new(DocumentUserImpl {
user: self.user_session.clone(), user: self.user_session.clone(),
}); });
@ -27,7 +27,7 @@ impl DocumentDepsResolver {
user: self.user_session.clone(), user: self.user_session.clone(),
}); });
let ws_manager = Arc::new(RwLock::new(WsDocumentManager::new(sender))); let ws_manager = Arc::new(WsDocumentManager::new(sender));
let ws_handler = Arc::new(WsDocumentReceiver { let ws_handler = Arc::new(WsDocumentReceiver {
inner: ws_manager.clone(), inner: ws_manager.clone(),
@ -73,19 +73,19 @@ struct WsSenderImpl {
user: Arc<UserSession>, user: Arc<UserSession>,
} }
impl WsDocumentSender for WsSenderImpl { impl DocumentWebSocket for WsSenderImpl {
fn send(&self, data: WsDocumentData) -> Result<(), DocError> { fn send(&self, data: WsDocumentData) -> Result<(), DocError> {
let msg: WsMessage = data.into(); let msg: WsMessage = data.into();
let _ = self let sender = self.user.ws_controller.sender().map_err(internal_error)?;
.user sender.send_msg(msg).map_err(internal_error)?;
.send_ws_msg(msg)
.map_err(|e| DocError::internal().context(e))?;
Ok(()) Ok(())
} }
fn state_notify(&self) -> WsStateReceiver { self.user.ws_controller.state_subscribe() }
} }
struct WsDocumentReceiver { struct WsDocumentReceiver {
inner: Arc<RwLock<WsDocumentManager>>, inner: Arc<WsDocumentManager>,
} }
impl WsMessageHandler for WsDocumentReceiver { impl WsMessageHandler for WsDocumentReceiver {
@ -93,6 +93,6 @@ impl WsMessageHandler for WsDocumentReceiver {
fn receive_message(&self, msg: WsMessage) { fn receive_message(&self, msg: WsMessage) {
let data = Bytes::from(msg.data); let data = Bytes::from(msg.data);
self.inner.read().receive_data(data); self.inner.handle_ws_data(data);
} }
} }

View file

@ -16,10 +16,10 @@ use flowy_database::{
ExpressionMethods, ExpressionMethods,
UserDatabaseConnection, UserDatabaseConnection,
}; };
use flowy_infra::{future::wrap_future, kv::KV}; use flowy_infra::kv::KV;
use flowy_net::config::ServerConfig; use flowy_net::config::ServerConfig;
use flowy_sqlite::ConnectionPool; use flowy_sqlite::ConnectionPool;
use flowy_ws::{WsController, WsMessage, WsMessageHandler}; use flowy_ws::{WsController, WsMessage, WsMessageHandler, WsState};
use parking_lot::RwLock; use parking_lot::RwLock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
@ -50,7 +50,7 @@ pub struct UserSession {
#[allow(dead_code)] #[allow(dead_code)]
server: Server, server: Server,
session: RwLock<Option<Session>>, session: RwLock<Option<Session>>,
ws_controller: Arc<WsController>, pub ws_controller: Arc<WsController>,
status_callback: SessionStatusCallback, status_callback: SessionStatusCallback,
} }
@ -185,12 +185,6 @@ impl UserSession {
pub fn add_ws_handler(&self, handler: Arc<dyn WsMessageHandler>) { pub fn add_ws_handler(&self, handler: Arc<dyn WsMessageHandler>) {
let _ = self.ws_controller.add_handler(handler); let _ = self.ws_controller.add_handler(handler);
} }
pub fn send_ws_msg<T: Into<WsMessage>>(&self, msg: T) -> Result<(), UserError> {
let sender = self.ws_controller.sender()?;
sender.send_msg(msg)?;
Ok(())
}
} }
impl UserSession { impl UserSession {

View file

@ -6,18 +6,16 @@ use crate::{
}; };
use bytes::Bytes; use bytes::Bytes;
use dashmap::DashMap; use dashmap::DashMap;
use flowy_infra::{ use flowy_infra::retry::{Action, ExponentialBackoff, Retry};
future::{wrap_future, FnFuture},
retry::{Action, ExponentialBackoff, Retry},
};
use flowy_net::errors::ServerError; use flowy_net::errors::ServerError;
use futures::future::BoxFuture;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use parking_lot::RwLock; use parking_lot::RwLock;
use pin_project::pin_project; use pin_project::pin_project;
use std::{ use std::{
convert::TryFrom, convert::TryFrom,
fmt::Formatter,
future::Future, future::Future,
pin::Pin, pin::Pin,
sync::Arc, sync::Arc,
@ -45,6 +43,20 @@ pub enum WsState {
Disconnected(WsError), Disconnected(WsError),
} }
impl std::fmt::Display for WsState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
WsState::Init => f.write_str("Init"),
WsState::Connected(_) => f.write_str("Connected"),
WsState::Disconnected(_) => f.write_str("Disconnected"),
}
}
}
impl std::fmt::Debug for WsState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str(&format!("{}", self)) }
}
pub struct WsController { pub struct WsController {
handlers: Handlers, handlers: Handlers,
state_notify: Arc<broadcast::Sender<WsState>>, state_notify: Arc<broadcast::Sender<WsState>>,