Browse Source

send revsion to server

appflowy 3 years ago
parent
commit
1d9d776e3a
32 changed files with 690 additions and 190 deletions
  1. 1 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/protobuf.dart
  2. 101 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pb.dart
  3. 7 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbenum.dart
  4. 23 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbjson.dart
  5. 9 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbserver.dart
  6. 2 2
      rust-lib/flowy-database/migrations/2021-09-22-074638_flowy-doc-op/up.sql
  7. 3 3
      rust-lib/flowy-database/src/schema.rs
  8. 1 0
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  9. 2 0
      rust-lib/flowy-document/Cargo.toml
  10. 2 0
      rust-lib/flowy-document/src/entities/doc/mod.rs
  11. 27 0
      rust-lib/flowy-document/src/entities/doc/revision.rs
  12. 4 0
      rust-lib/flowy-document/src/errors.rs
  13. 1 1
      rust-lib/flowy-document/src/module.rs
  14. 3 0
      rust-lib/flowy-document/src/protobuf/model/mod.rs
  15. 327 0
      rust-lib/flowy-document/src/protobuf/model/revision.rs
  16. 7 0
      rust-lib/flowy-document/src/protobuf/proto/revision.proto
  17. 2 11
      rust-lib/flowy-document/src/services/cache.rs
  18. 25 59
      rust-lib/flowy-document/src/services/doc/doc_controller.rs
  19. 1 26
      rust-lib/flowy-document/src/services/doc/document/document.rs
  20. 81 37
      rust-lib/flowy-document/src/services/doc/edit_context.rs
  21. 3 3
      rust-lib/flowy-document/src/sql_tables/doc/doc_op_sql.rs
  22. 18 5
      rust-lib/flowy-document/src/sql_tables/doc/doc_op_table.rs
  23. 5 2
      rust-lib/flowy-document/src/sql_tables/doc/doc_sql.rs
  24. 10 0
      rust-lib/flowy-document/src/sql_tables/doc/doc_table.rs
  25. 2 5
      rust-lib/flowy-document/tests/editor/mod.rs
  26. 2 2
      rust-lib/flowy-infra/src/future.rs
  27. 13 3
      rust-lib/flowy-sdk/src/deps_resolve/document_deps.rs
  28. 0 26
      rust-lib/flowy-sqlite/src/pool.rs
  29. 6 1
      rust-lib/flowy-user/src/errors.rs
  30. 0 1
      rust-lib/flowy-workspace/src/sql_tables/app/app_table.rs
  31. 0 1
      rust-lib/flowy-workspace/src/sql_tables/view/view_table.rs
  32. 2 2
      rust-lib/flowy-ws/src/errors.rs

+ 1 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/protobuf.dart

@@ -2,5 +2,6 @@
 export './ws.pb.dart';
 export './ws.pb.dart';
 export './observable.pb.dart';
 export './observable.pb.dart';
 export './errors.pb.dart';
 export './errors.pb.dart';
+export './revision.pb.dart';
 export './event.pb.dart';
 export './event.pb.dart';
 export './doc.pb.dart';
 export './doc.pb.dart';

+ 101 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pb.dart

@@ -0,0 +1,101 @@
+///
+//  Generated code. Do not modify.
+//  source: revision.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:fixnum/fixnum.dart' as $fixnum;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class Revision extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Revision', createEmptyInstance: create)
+    ..aInt64(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'baseRevId')
+    ..aInt64(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'revId')
+    ..a<$core.List<$core.int>>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'delta', $pb.PbFieldType.OY)
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'md5')
+    ..hasRequiredFields = false
+  ;
+
+  Revision._() : super();
+  factory Revision({
+    $fixnum.Int64? baseRevId,
+    $fixnum.Int64? revId,
+    $core.List<$core.int>? delta,
+    $core.String? md5,
+  }) {
+    final _result = create();
+    if (baseRevId != null) {
+      _result.baseRevId = baseRevId;
+    }
+    if (revId != null) {
+      _result.revId = revId;
+    }
+    if (delta != null) {
+      _result.delta = delta;
+    }
+    if (md5 != null) {
+      _result.md5 = md5;
+    }
+    return _result;
+  }
+  factory Revision.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Revision.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  Revision clone() => Revision()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Revision copyWith(void Function(Revision) updates) => super.copyWith((message) => updates(message as Revision)) as Revision; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Revision create() => Revision._();
+  Revision createEmptyInstance() => create();
+  static $pb.PbList<Revision> createRepeated() => $pb.PbList<Revision>();
+  @$core.pragma('dart2js:noInline')
+  static Revision getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Revision>(create);
+  static Revision? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $fixnum.Int64 get baseRevId => $_getI64(0);
+  @$pb.TagNumber(1)
+  set baseRevId($fixnum.Int64 v) { $_setInt64(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasBaseRevId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearBaseRevId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $fixnum.Int64 get revId => $_getI64(1);
+  @$pb.TagNumber(2)
+  set revId($fixnum.Int64 v) { $_setInt64(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasRevId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearRevId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.List<$core.int> get delta => $_getN(2);
+  @$pb.TagNumber(3)
+  set delta($core.List<$core.int> v) { $_setBytes(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasDelta() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearDelta() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $core.String get md5 => $_getSZ(3);
+  @$pb.TagNumber(4)
+  set md5($core.String v) { $_setString(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasMd5() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearMd5() => clearField(4);
+}
+

+ 7 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbenum.dart

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: revision.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+

+ 23 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbjson.dart

@@ -0,0 +1,23 @@
+///
+//  Generated code. Do not modify.
+//  source: revision.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use revisionDescriptor instead')
+const Revision$json = const {
+  '1': 'Revision',
+  '2': const [
+    const {'1': 'base_rev_id', '3': 1, '4': 1, '5': 3, '10': 'baseRevId'},
+    const {'1': 'rev_id', '3': 2, '4': 1, '5': 3, '10': 'revId'},
+    const {'1': 'delta', '3': 3, '4': 1, '5': 12, '10': 'delta'},
+    const {'1': 'md5', '3': 4, '4': 1, '5': 9, '10': 'md5'},
+  ],
+};
+
+/// Descriptor for `Revision`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List revisionDescriptor = $convert.base64Decode('CghSZXZpc2lvbhIeCgtiYXNlX3Jldl9pZBgBIAEoA1IJYmFzZVJldklkEhUKBnJldl9pZBgCIAEoA1IFcmV2SWQSFAoFZGVsdGEYAyABKAxSBWRlbHRhEhAKA21kNRgEIAEoCVIDbWQ1');

+ 9 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-document/revision.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: revision.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'revision.pb.dart';
+

+ 2 - 2
rust-lib/flowy-database/migrations/2021-09-22-074638_flowy-doc-op/up.sql

@@ -1,7 +1,7 @@
 -- Your SQL goes here
 -- Your SQL goes here
 CREATE TABLE op_table (
 CREATE TABLE op_table (
-    base_rev BIGINT NOT NULL DEFAULT 0,
-    rev BIGINT NOT NULL PRIMARY KEY,
+    base_rev_id BIGINT NOT NULL DEFAULT 0,
+    rev_id BIGINT NOT NULL PRIMARY KEY,
     data BLOB NOT NULL DEFAULT (x''),
     data BLOB NOT NULL DEFAULT (x''),
     md5 TEXT NOT NULL DEFAULT '',
     md5 TEXT NOT NULL DEFAULT '',
     state INTEGER NOT NULL DEFAULT 0
     state INTEGER NOT NULL DEFAULT 0

+ 3 - 3
rust-lib/flowy-database/src/schema.rs

@@ -22,9 +22,9 @@ table! {
 }
 }
 
 
 table! {
 table! {
-    op_table (rev) {
-        base_rev -> BigInt,
-        rev -> BigInt,
+    op_table (rev_id) {
+        base_rev_id -> BigInt,
+        rev_id -> BigInt,
         data -> Binary,
         data -> Binary,
         md5 -> Text,
         md5 -> Text,
         state -> Integer,
         state -> Integer,

+ 1 - 0
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -60,6 +60,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "UpdateDocParams"
         | "UpdateDocParams"
         | "DocDelta"
         | "DocDelta"
         | "QueryDocParams"
         | "QueryDocParams"
+        | "Revision"
         | "WsDocumentData"
         | "WsDocumentData"
         | "DocError"
         | "DocError"
         | "FFIRequest"
         | "FFIRequest"

+ 2 - 0
rust-lib/flowy-document/Cargo.toml

@@ -35,6 +35,8 @@ url = "2.2"
 serde = { version = "1.0", features = ["derive"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = {version = "1.0"}
 serde_json = {version = "1.0"}
 chrono = "0.4.19"
 chrono = "0.4.19"
+futures-core = { version = "0.3", default-features = false }
+md5 = "0.7.0"
 
 
 [dev-dependencies]
 [dev-dependencies]
 flowy-test = { path = "../flowy-test" }
 flowy-test = { path = "../flowy-test" }

+ 2 - 0
rust-lib/flowy-document/src/entities/doc/mod.rs

@@ -1,4 +1,6 @@
 mod doc;
 mod doc;
 pub mod parser;
 pub mod parser;
+mod revision;
 
 
 pub use doc::*;
 pub use doc::*;
+pub use revision::*;

+ 27 - 0
rust-lib/flowy-document/src/entities/doc/revision.rs

@@ -0,0 +1,27 @@
+use flowy_derive::ProtoBuf;
+
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct Revision {
+    #[pb(index = 1)]
+    pub base_rev_id: i64,
+
+    #[pb(index = 2)]
+    pub rev_id: i64,
+
+    #[pb(index = 3)]
+    pub delta: Vec<u8>,
+
+    #[pb(index = 4)]
+    pub md5: String,
+}
+
+impl Revision {
+    pub fn new(base_rev_id: i64, rev_id: i64, delta: Vec<u8>, md5: String) -> Revision {
+        Self {
+            base_rev_id,
+            rev_id,
+            delta,
+            md5,
+        }
+    }
+}

+ 4 - 0
rust-lib/flowy-document/src/errors.rs

@@ -104,6 +104,10 @@ impl std::convert::From<serde_json::Error> for DocError {
     fn from(error: serde_json::Error) -> Self { DocError::internal().context(error) }
     fn from(error: serde_json::Error) -> Self { DocError::internal().context(error) }
 }
 }
 
 
+impl std::convert::From<protobuf::ProtobufError> for DocError {
+    fn from(e: protobuf::ProtobufError) -> Self { DocError::internal().context(e) }
+}
+
 // impl std::convert::From<::r2d2::Error> for DocError {
 // impl std::convert::From<::r2d2::Error> for DocError {
 //     fn from(error: r2d2::Error) -> Self {
 //     fn from(error: r2d2::Error) -> Self {
 // ErrorBuilder::new(ErrorCode::InternalError).error(error).build() } }
 // ErrorBuilder::new(ErrorCode::InternalError).error(error).build() } }

+ 1 - 1
rust-lib/flowy-document/src/module.rs

@@ -1,6 +1,6 @@
 use std::sync::Arc;
 use std::sync::Arc;
 
 
-use bytes::Bytes;
+
 use diesel::SqliteConnection;
 use diesel::SqliteConnection;
 use parking_lot::RwLock;
 use parking_lot::RwLock;
 
 

+ 3 - 0
rust-lib/flowy-document/src/protobuf/model/mod.rs

@@ -9,6 +9,9 @@ pub use observable::*;
 mod errors; 
 mod errors; 
 pub use errors::*; 
 pub use errors::*; 
 
 
+mod revision; 
+pub use revision::*; 
+
 mod event; 
 mod event; 
 pub use event::*; 
 pub use event::*; 
 
 

+ 327 - 0
rust-lib/flowy-document/src/protobuf/model/revision.rs

@@ -0,0 +1,327 @@
+// This file is generated by rust-protobuf 2.22.1. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `revision.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct Revision {
+    // message fields
+    pub base_rev_id: i64,
+    pub rev_id: i64,
+    pub delta: ::std::vec::Vec<u8>,
+    pub md5: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a Revision {
+    fn default() -> &'a Revision {
+        <Revision as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl Revision {
+    pub fn new() -> Revision {
+        ::std::default::Default::default()
+    }
+
+    // int64 base_rev_id = 1;
+
+
+    pub fn get_base_rev_id(&self) -> i64 {
+        self.base_rev_id
+    }
+    pub fn clear_base_rev_id(&mut self) {
+        self.base_rev_id = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_base_rev_id(&mut self, v: i64) {
+        self.base_rev_id = v;
+    }
+
+    // int64 rev_id = 2;
+
+
+    pub fn get_rev_id(&self) -> i64 {
+        self.rev_id
+    }
+    pub fn clear_rev_id(&mut self) {
+        self.rev_id = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_rev_id(&mut self, v: i64) {
+        self.rev_id = v;
+    }
+
+    // bytes delta = 3;
+
+
+    pub fn get_delta(&self) -> &[u8] {
+        &self.delta
+    }
+    pub fn clear_delta(&mut self) {
+        self.delta.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_delta(&mut self, v: ::std::vec::Vec<u8>) {
+        self.delta = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_delta(&mut self) -> &mut ::std::vec::Vec<u8> {
+        &mut self.delta
+    }
+
+    // Take field
+    pub fn take_delta(&mut self) -> ::std::vec::Vec<u8> {
+        ::std::mem::replace(&mut self.delta, ::std::vec::Vec::new())
+    }
+
+    // string md5 = 4;
+
+
+    pub fn get_md5(&self) -> &str {
+        &self.md5
+    }
+    pub fn clear_md5(&mut self) {
+        self.md5.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_md5(&mut self, v: ::std::string::String) {
+        self.md5 = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_md5(&mut self) -> &mut ::std::string::String {
+        &mut self.md5
+    }
+
+    // Take field
+    pub fn take_md5(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.md5, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for Revision {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.base_rev_id = tmp;
+                },
+                2 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.rev_id = tmp;
+                },
+                3 => {
+                    ::protobuf::rt::read_singular_proto3_bytes_into(wire_type, is, &mut self.delta)?;
+                },
+                4 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.md5)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if self.base_rev_id != 0 {
+            my_size += ::protobuf::rt::value_size(1, self.base_rev_id, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if self.rev_id != 0 {
+            my_size += ::protobuf::rt::value_size(2, self.rev_id, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if !self.delta.is_empty() {
+            my_size += ::protobuf::rt::bytes_size(3, &self.delta);
+        }
+        if !self.md5.is_empty() {
+            my_size += ::protobuf::rt::string_size(4, &self.md5);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if self.base_rev_id != 0 {
+            os.write_int64(1, self.base_rev_id)?;
+        }
+        if self.rev_id != 0 {
+            os.write_int64(2, self.rev_id)?;
+        }
+        if !self.delta.is_empty() {
+            os.write_bytes(3, &self.delta)?;
+        }
+        if !self.md5.is_empty() {
+            os.write_string(4, &self.md5)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> Revision {
+        Revision::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "base_rev_id",
+                |m: &Revision| { &m.base_rev_id },
+                |m: &mut Revision| { &mut m.base_rev_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "rev_id",
+                |m: &Revision| { &m.rev_id },
+                |m: &mut Revision| { &mut m.rev_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeBytes>(
+                "delta",
+                |m: &Revision| { &m.delta },
+                |m: &mut Revision| { &mut m.delta },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "md5",
+                |m: &Revision| { &m.md5 },
+                |m: &mut Revision| { &mut m.md5 },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<Revision>(
+                "Revision",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static Revision {
+        static instance: ::protobuf::rt::LazyV2<Revision> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(Revision::new)
+    }
+}
+
+impl ::protobuf::Clear for Revision {
+    fn clear(&mut self) {
+        self.base_rev_id = 0;
+        self.rev_id = 0;
+        self.delta.clear();
+        self.md5.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for Revision {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for Revision {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x0erevision.proto\"i\n\x08Revision\x12\x1e\n\x0bbase_rev_id\x18\x01\
+    \x20\x01(\x03R\tbaseRevId\x12\x15\n\x06rev_id\x18\x02\x20\x01(\x03R\x05r\
+    evId\x12\x14\n\x05delta\x18\x03\x20\x01(\x0cR\x05delta\x12\x10\n\x03md5\
+    \x18\x04\x20\x01(\tR\x03md5J\x86\x02\n\x06\x12\x04\0\0\x06\x01\n\x08\n\
+    \x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x01\0\x06\x01\n\n\n\x03\
+    \x04\0\x01\x12\x03\x01\x08\x10\n\x0b\n\x04\x04\0\x02\0\x12\x03\x02\x04\
+    \x1a\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x02\x04\t\n\x0c\n\x05\x04\0\x02\
+    \0\x01\x12\x03\x02\n\x15\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x02\x18\x19\
+    \n\x0b\n\x04\x04\0\x02\x01\x12\x03\x03\x04\x15\n\x0c\n\x05\x04\0\x02\x01\
+    \x05\x12\x03\x03\x04\t\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x03\n\x10\n\
+    \x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x03\x13\x14\n\x0b\n\x04\x04\0\x02\
+    \x02\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x04\x04\t\
+    \n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x04\n\x0f\n\x0c\n\x05\x04\0\x02\
+    \x02\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x05\x04\
+    \x13\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\
+    \x02\x03\x01\x12\x03\x05\x0b\x0e\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\
+    \x05\x11\x12b\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 7 - 0
rust-lib/flowy-document/src/protobuf/proto/revision.proto

@@ -0,0 +1,7 @@
+syntax = "proto3";
+message Revision {
+    int64 base_rev_id = 1;
+    int64 rev_id = 2;
+    bytes delta = 3;
+    string md5 = 4;
+}

+ 2 - 11
rust-lib/flowy-document/src/services/cache.rs

@@ -1,19 +1,10 @@
-use std::{convert::TryInto, fmt::Debug, sync::Arc};
+use std::sync::Arc;
 
 
-use bytes::Bytes;
 use dashmap::DashMap;
 use dashmap::DashMap;
-use parking_lot::RwLock;
-
-use flowy_database::ConnectionPool;
-use flowy_ot::{core::Delta, errors::OTError};
 
 
 use crate::{
 use crate::{
-    entities::doc::Doc,
     errors::DocError,
     errors::DocError,
-    services::{
-        doc::edit_context::{DocId, EditDocContext},
-        ws::WsManager,
-    },
+    services::doc::edit_context::{DocId, EditDocContext},
 };
 };
 
 
 pub(crate) struct DocCache {
 pub(crate) struct DocCache {

+ 25 - 59
rust-lib/flowy-document/src/services/doc/doc_controller.rs

@@ -2,19 +2,15 @@ use crate::{
     entities::doc::{CreateDocParams, Doc, DocDelta, QueryDocParams, UpdateDocParams},
     entities::doc::{CreateDocParams, Doc, DocDelta, QueryDocParams, UpdateDocParams},
     errors::{internal_error, DocError},
     errors::{internal_error, DocError},
     module::DocumentUser,
     module::DocumentUser,
-    services::{
-        cache::DocCache,
-        doc::edit_context::{DocId, EditDocContext, EditDocPersistence},
-        server::Server,
-        ws::WsManager,
-    },
-    sql_tables::doc::{DocTable, DocTableChangeset, DocTableSql, OpTableSql},
+    services::{cache::DocCache, doc::edit_context::EditDocContext, server::Server, ws::WsManager},
+    sql_tables::doc::{DocTable, DocTableSql, OpTableSql},
 };
 };
 use bytes::Bytes;
 use bytes::Bytes;
 use flowy_database::{ConnectionPool, SqliteConnection};
 use flowy_database::{ConnectionPool, SqliteConnection};
+use flowy_infra::future::ClosureFuture;
+
 use parking_lot::RwLock;
 use parking_lot::RwLock;
 use std::sync::Arc;
 use std::sync::Arc;
-use tokio::task::JoinHandle;
 
 
 pub(crate) struct DocController {
 pub(crate) struct DocController {
     server: Server,
     server: Server,
@@ -107,29 +103,17 @@ impl DocController {
     }
     }
 
 
     #[tracing::instrument(level = "debug", skip(self, pool), err)]
     #[tracing::instrument(level = "debug", skip(self, pool), err)]
-    fn read_doc_from_server(
-        &self,
-        params: QueryDocParams,
-        pool: Arc<ConnectionPool>,
-    ) -> Result<JoinHandle<Result<Arc<EditDocContext>, DocError>>, DocError> {
+    async fn read_doc_from_server(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Arc<EditDocContext>, DocError> {
         let token = self.user.token()?;
         let token = self.user.token()?;
-        let server = self.server.clone();
-        let doc_sql = self.doc_sql.clone();
-        let op_sql = self.op_sql.clone();
-        let ws = self.ws.clone();
-        let cache = self.cache.clone();
-
-        Ok(tokio::spawn(async move {
-            match server.read_doc(&token, params).await? {
-                None => Err(DocError::not_found()),
-                Some(doc) => {
-                    let doc_table = DocTable::new(doc.clone());
-                    let _ = doc_sql.create_doc_table(doc_table, &*(pool.get().map_err(internal_error)?))?;
-                    let edit_doc_ctx = make_edit_context(ws, cache, op_sql, doc)?;
-                    Ok(edit_doc_ctx)
-                },
-            }
-        }))
+        match self.server.read_doc(&token, params).await? {
+            None => Err(DocError::not_found()),
+            Some(doc) => {
+                let edit = self.make_edit_context(doc.clone())?;
+                let conn = &*(pool.get().map_err(internal_error)?);
+                let _ = self.doc_sql.create_doc_table(doc.into(), conn)?;
+                Ok(edit)
+            },
+        }
     }
     }
 
 
     #[tracing::instrument(level = "debug", skip(self), err)]
     #[tracing::instrument(level = "debug", skip(self), err)]
@@ -149,44 +133,26 @@ impl DocController {
     }
     }
 
 
     async fn _open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Arc<EditDocContext>, DocError> {
     async fn _open(&self, params: QueryDocParams, pool: Arc<ConnectionPool>) -> Result<Arc<EditDocContext>, DocError> {
-        match self.doc_sql.read_doc_table(&params.doc_id, &*(pool.get().map_err(internal_error)?)) {
-            Ok(doc_table) => {
-                let edit_doc_ctx = make_edit_context(self.ws.clone(), self.cache.clone(), self.op_sql.clone(), doc_table.into())?;
-                Ok(edit_doc_ctx)
-            },
+        match self.doc_sql.read_doc_table(&params.doc_id, pool.clone()) {
+            Ok(doc_table) => Ok(self.make_edit_context(doc_table.into())?),
             Err(error) => {
             Err(error) => {
                 if error.is_record_not_found() {
                 if error.is_record_not_found() {
                     log::debug!("Doc:{} don't exist, reading from server", params.doc_id);
                     log::debug!("Doc:{} don't exist, reading from server", params.doc_id);
-                    match self.read_doc_from_server(params, pool)?.await.map_err(internal_error)? {
-                        Ok(edit_doc_ctx) => Ok(edit_doc_ctx),
-                        Err(error) => Err(error),
-                    }
+                    Ok(self.read_doc_from_server(params, pool.clone()).await?)
                 } else {
                 } else {
                     return Err(error);
                     return Err(error);
                 }
                 }
             },
             },
         }
         }
     }
     }
-}
 
 
-fn make_edit_context(
-    ws: Arc<RwLock<WsManager>>,
-    cache: Arc<DocCache>,
-    op_sql: Arc<OpTableSql>,
-    doc: Doc,
-) -> Result<Arc<EditDocContext>, DocError> {
-    // Opti: require upgradable_read lock and then upgrade to write lock using
-    // RwLockUpgradableReadGuard::upgrade(xx)
-    let edit_doc_ctx = Arc::new(EditDocContext::new(doc, ws.read().sender.clone(), op_sql)?);
-    ws.write().register_handler(edit_doc_ctx.id.as_ref(), edit_doc_ctx.clone());
-    cache.set(edit_doc_ctx.clone());
-    Ok(edit_doc_ctx)
-}
-
-impl EditDocPersistence for DocController {
-    fn save(&self, params: UpdateDocParams, pool: Arc<ConnectionPool>) -> Result<(), DocError> {
-        let changeset = DocTableChangeset::new(params.clone());
-        let _ = self.doc_sql.update_doc_table(changeset, &*(pool.get().map_err(internal_error)?))?;
-        Ok(())
+    fn make_edit_context(&self, doc: Doc) -> Result<Arc<EditDocContext>, DocError> {
+        // Opti: require upgradable_read lock and then upgrade to write lock using
+        // RwLockUpgradableReadGuard::upgrade(xx) of ws
+        let sender = self.ws.read().sender.clone();
+        let edit_ctx = Arc::new(EditDocContext::new(doc, sender, self.op_sql.clone())?);
+        self.ws.write().register_handler(edit_ctx.id.as_ref(), edit_ctx.clone());
+        self.cache.set(edit_ctx.clone());
+        Ok(edit_ctx)
     }
     }
 }
 }

+ 1 - 26
rust-lib/flowy-document/src/services/doc/document/document.rs

@@ -4,7 +4,6 @@ use crate::{
 };
 };
 use bytes::Bytes;
 use bytes::Bytes;
 use flowy_ot::core::*;
 use flowy_ot::core::*;
-use std::convert::TryInto;
 
 
 pub trait DocumentData {
 pub trait DocumentData {
     fn into_string(self) -> Result<String, DocError>;
     fn into_string(self) -> Result<String, DocError>;
@@ -24,24 +23,10 @@ impl CustomDocument for FlowyDoc {
     fn init_delta() -> Delta { DeltaBuilder::new().insert("\n").build() }
     fn init_delta() -> Delta { DeltaBuilder::new().insert("\n").build() }
 }
 }
 
 
-#[derive(Debug, Clone)]
-pub struct RevId(pub usize);
-
-#[derive(Debug, Clone)]
-pub struct Revision {
-    rev_id: RevId,
-    pub delta: Delta,
-}
-
-impl Revision {
-    pub fn new(rev_id: RevId, delta: Delta) -> Revision { Self { rev_id, delta } }
-}
-
 pub struct Document {
 pub struct Document {
     delta: Delta,
     delta: Delta,
     history: History,
     history: History,
     view: View,
     view: View,
-    rev_id_counter: usize,
     last_edit_time: usize,
     last_edit_time: usize,
 }
 }
 
 
@@ -53,7 +38,6 @@ impl Document {
             delta,
             delta,
             history: History::new(),
             history: History::new(),
             view: View::new(),
             view: View::new(),
-            rev_id_counter: 1,
             last_edit_time: 0,
             last_edit_time: 0,
         }
         }
     }
     }
@@ -67,16 +51,11 @@ impl Document {
 
 
     pub fn to_bytes(&self) -> Vec<u8> { self.delta.clone().into_bytes() }
     pub fn to_bytes(&self) -> Vec<u8> { self.delta.clone().into_bytes() }
 
 
-    pub fn to_string(&self) -> String { self.delta.apply("").unwrap() }
+    pub fn to_plain_string(&self) -> String { self.delta.apply("").unwrap() }
 
 
     pub fn apply_delta(&mut self, data: Bytes) -> Result<(), DocError> {
     pub fn apply_delta(&mut self, data: Bytes) -> Result<(), DocError> {
         let new_delta = Delta::from_bytes(data.to_vec())?;
         let new_delta = Delta::from_bytes(data.to_vec())?;
-
         log::debug!("Apply delta: {}", new_delta);
         log::debug!("Apply delta: {}", new_delta);
-
-        let rev_id = self.next_rev_id();
-        let revision = Revision::new(rev_id, new_delta.clone());
-
         let _ = self.add_delta(&new_delta)?;
         let _ = self.add_delta(&new_delta)?;
         log::debug!("Document: {}", self.to_json());
         log::debug!("Document: {}", self.to_json());
         Ok(())
         Ok(())
@@ -173,7 +152,6 @@ impl Document {
     fn add_delta(&mut self, delta: &Delta) -> Result<(), DocError> {
     fn add_delta(&mut self, delta: &Delta) -> Result<(), DocError> {
         let composed_delta = self.delta.compose(delta)?;
         let composed_delta = self.delta.compose(delta)?;
         let mut undo_delta = delta.invert(&self.delta);
         let mut undo_delta = delta.invert(&self.delta);
-        self.rev_id_counter += 1;
 
 
         let now = chrono::Utc::now().timestamp_millis() as usize;
         let now = chrono::Utc::now().timestamp_millis() as usize;
         if now - self.last_edit_time < RECORD_THRESHOLD {
         if now - self.last_edit_time < RECORD_THRESHOLD {
@@ -206,9 +184,6 @@ impl Document {
         let inverted_delta = change.invert(&self.delta);
         let inverted_delta = change.invert(&self.delta);
         Ok((new_delta, inverted_delta))
         Ok((new_delta, inverted_delta))
     }
     }
-
-    #[allow(dead_code)]
-    fn next_rev_id(&self) -> RevId { RevId(self.rev_id_counter) }
 }
 }
 
 
 fn validate_interval(delta: &Delta, interval: &Interval) -> Result<(), DocError> {
 fn validate_interval(delta: &Delta, interval: &Interval) -> Result<(), DocError> {

+ 81 - 37
rust-lib/flowy-document/src/services/doc/edit_context.rs

@@ -1,40 +1,30 @@
 use crate::{
 use crate::{
     entities::{
     entities::{
-        doc::{Doc, UpdateDocParams},
+        doc::{Doc, Revision},
         ws::{WsDocumentData, WsSource},
         ws::{WsDocumentData, WsSource},
     },
     },
-    errors::DocError,
+    errors::{internal_error, DocError},
     services::{
     services::{
         doc::Document,
         doc::Document,
         ws::{WsHandler, WsSender},
         ws::{WsHandler, WsSender},
     },
     },
-    sql_tables::doc::OpTableSql,
+    sql_tables::doc::{OpState, OpTable, OpTableSql},
 };
 };
 use bytes::Bytes;
 use bytes::Bytes;
 use flowy_database::ConnectionPool;
 use flowy_database::ConnectionPool;
 use flowy_ot::core::Delta;
 use flowy_ot::core::Delta;
-use parking_lot::RwLock;
-use std::{convert::TryInto, sync::Arc};
-
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct DocId(pub(crate) String);
-impl AsRef<str> for DocId {
-    fn as_ref(&self) -> &str { &self.0 }
-}
-impl<T> std::convert::From<T> for DocId
-where
-    T: ToString,
-{
-    fn from(s: T) -> Self { DocId(s.to_string()) }
-}
-
-pub(crate) trait EditDocPersistence: Send + Sync {
-    fn save(&self, params: UpdateDocParams, pool: Arc<ConnectionPool>) -> Result<(), DocError>;
-}
+use parking_lot::{lock_api::RwLockWriteGuard, RawRwLock, RwLock};
+use std::{
+    convert::TryInto,
+    sync::{
+        atomic::{AtomicI64, AtomicUsize, Ordering::SeqCst},
+        Arc,
+    },
+};
 
 
 pub(crate) struct EditDocContext {
 pub(crate) struct EditDocContext {
     pub(crate) id: DocId,
     pub(crate) id: DocId,
-    pub(crate) revision: i64,
+    pub(crate) rev_counter: RevCounter,
     document: RwLock<Document>,
     document: RwLock<Document>,
     ws_sender: Arc<dyn WsSender>,
     ws_sender: Arc<dyn WsSender>,
     op_sql: Arc<OpTableSql>,
     op_sql: Arc<OpTableSql>,
@@ -43,13 +33,13 @@ pub(crate) struct EditDocContext {
 impl EditDocContext {
 impl EditDocContext {
     pub(crate) fn new(doc: Doc, ws_sender: Arc<dyn WsSender>, op_sql: Arc<OpTableSql>) -> Result<Self, DocError> {
     pub(crate) fn new(doc: Doc, ws_sender: Arc<dyn WsSender>, op_sql: Arc<OpTableSql>) -> Result<Self, DocError> {
         let id: DocId = doc.id.into();
         let id: DocId = doc.id.into();
-        let revision = doc.revision;
+        let rev_counter = RevCounter::new(doc.revision);
         let delta: Delta = doc.data.try_into()?;
         let delta: Delta = doc.data.try_into()?;
         let document = RwLock::new(Document::from_delta(delta));
         let document = RwLock::new(Document::from_delta(delta));
 
 
         Ok(Self {
         Ok(Self {
             id,
             id,
-            revision,
+            rev_counter,
             document,
             document,
             ws_sender,
             ws_sender,
             op_sql,
             op_sql,
@@ -58,31 +48,55 @@ impl EditDocContext {
 
 
     pub(crate) fn doc(&self) -> Doc {
     pub(crate) fn doc(&self) -> Doc {
         Doc {
         Doc {
-            id: self.id.0.clone(),
+            id: self.id.clone().into(),
             data: self.document.read().to_bytes(),
             data: self.document.read().to_bytes(),
-            revision: self.revision,
+            revision: self.rev_counter.value(),
         }
         }
     }
     }
 
 
     pub(crate) fn apply_delta(&self, data: Bytes, pool: Arc<ConnectionPool>) -> Result<(), DocError> {
     pub(crate) fn apply_delta(&self, data: Bytes, pool: Arc<ConnectionPool>) -> Result<(), DocError> {
-        let mut write_guard = self.document.write();
-        let _ = write_guard.apply_delta(data.clone())?;
+        let mut guard = self.document.write();
+        let base_rev_id = self.rev_counter.value();
+        let rev_id = self.rev_counter.next();
+        let _ = guard.apply_delta(data.clone())?;
+        let json = guard.to_json();
+        drop(guard);
 
 
-        match self.ws_sender.send_data(data) {
-            Ok(_) => {},
+        // Opti: it is necessary to save the rev if send success?
+        let md5 = format!("{:x}", md5::compute(json));
+        let revision = Revision::new(base_rev_id, rev_id, data.to_vec(), md5);
+        self.save_revision(revision.clone(), pool.clone());
+        match self.ws_sender.send_data(revision.try_into()?) {
+            Ok(_) => {
+                // TODO: remove the rev if send success
+                // let _ = self.delete_revision(rev_id, pool)?;
+            },
             Err(e) => {
             Err(e) => {
-                // TODO: save to local and retry
                 log::error!("Send delta failed: {:?}", e);
                 log::error!("Send delta failed: {:?}", e);
             },
             },
         }
         }
+        Ok(())
+    }
+}
+
+impl EditDocContext {
+    fn save_revision(&self, revision: Revision, pool: Arc<ConnectionPool>) -> Result<(), DocError> {
+        let conn = &*pool.get().map_err(internal_error)?;
+        conn.immediate_transaction::<_, DocError, _>(|| {
+            let op_table: OpTable = revision.into();
+            let _ = self.op_sql.create_op_table(op_table, conn)?;
+            Ok(())
+        })?;
 
 
-        // Opti: strategy to save the document
-        let save = UpdateDocParams {
-            doc_id: self.id.0.clone(),
-            data: write_guard.to_bytes(),
-        };
-        // let _ = self.persistence.save(save, pool)?;
+        Ok(())
+    }
 
 
+    fn delete_revision(&self, rev_id: i64, pool: Arc<ConnectionPool>) -> Result<(), DocError> {
+        let conn = &*pool.get().map_err(internal_error)?;
+        conn.immediate_transaction::<_, DocError, _>(|| {
+            let _ = self.op_sql.delete_op_table(rev_id, conn)?;
+            Ok(())
+        })?;
         Ok(())
         Ok(())
     }
     }
 }
 }
@@ -94,3 +108,33 @@ impl WsHandler for EditDocContext {
         }
         }
     }
     }
 }
 }
+
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct DocId(pub(crate) String);
+
+impl AsRef<str> for DocId {
+    fn as_ref(&self) -> &str { &self.0 }
+}
+
+impl<T> std::convert::From<T> for DocId
+where
+    T: ToString,
+{
+    fn from(s: T) -> Self { DocId(s.to_string()) }
+}
+
+impl std::convert::Into<String> for DocId {
+    fn into(self) -> String { self.0.clone() }
+}
+
+#[derive(Debug)]
+pub struct RevCounter(pub AtomicI64);
+
+impl RevCounter {
+    pub fn new(n: i64) -> Self { Self(AtomicI64::new(n)) }
+    pub fn next(&self) -> i64 {
+        let _ = self.0.fetch_add(1, SeqCst);
+        self.value()
+    }
+    pub fn value(&self) -> i64 { self.0.load(SeqCst) }
+}

+ 3 - 3
rust-lib/flowy-document/src/sql_tables/doc/doc_op_sql.rs

@@ -17,7 +17,7 @@ impl OpTableSql {
     }
     }
 
 
     pub(crate) fn update_op_table(&self, changeset: OpChangeset, conn: &SqliteConnection) -> Result<(), DocError> {
     pub(crate) fn update_op_table(&self, changeset: OpChangeset, conn: &SqliteConnection) -> Result<(), DocError> {
-        let filter = dsl::op_table.filter(op_table::dsl::rev.eq(changeset.rev));
+        let filter = dsl::op_table.filter(op_table::dsl::rev_id.eq(changeset.rev_id));
         let affected_row = diesel::update(filter).set(changeset).execute(conn)?;
         let affected_row = diesel::update(filter).set(changeset).execute(conn)?;
         debug_assert_eq!(affected_row, 1);
         debug_assert_eq!(affected_row, 1);
         Ok(())
         Ok(())
@@ -28,8 +28,8 @@ impl OpTableSql {
         Ok(ops)
         Ok(ops)
     }
     }
 
 
-    pub(crate) fn delete_op_table(&self, rev: i64, conn: &SqliteConnection) -> Result<(), DocError> {
-        let filter = dsl::op_table.filter(op_table::dsl::rev.eq(rev));
+    pub(crate) fn delete_op_table(&self, rev_id: i64, conn: &SqliteConnection) -> Result<(), DocError> {
+        let filter = dsl::op_table.filter(op_table::dsl::rev_id.eq(rev_id));
         let affected_row = diesel::delete(filter).execute(conn)?;
         let affected_row = diesel::delete(filter).execute(conn)?;
         debug_assert_eq!(affected_row, 1);
         debug_assert_eq!(affected_row, 1);
         Ok(())
         Ok(())

+ 18 - 5
rust-lib/flowy-document/src/sql_tables/doc/doc_op_table.rs

@@ -1,12 +1,13 @@
+use crate::entities::doc::Revision;
 use diesel::sql_types::Integer;
 use diesel::sql_types::Integer;
 use flowy_database::schema::op_table;
 use flowy_database::schema::op_table;
 
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
 #[table_name = "op_table"]
 #[table_name = "op_table"]
-#[primary_key(rev)]
+#[primary_key(rev_id)]
 pub(crate) struct OpTable {
 pub(crate) struct OpTable {
-    pub(crate) base_rev: i64,
-    pub(crate) rev: i64,
+    pub(crate) base_rev_id: i64,
+    pub(crate) rev_id: i64,
     pub(crate) data: Vec<u8>,
     pub(crate) data: Vec<u8>,
     pub(crate) md5: String,
     pub(crate) md5: String,
     pub(crate) state: OpState,
     pub(crate) state: OpState,
@@ -47,8 +48,20 @@ impl_sql_integer_expression!(OpState);
 
 
 #[derive(AsChangeset, Identifiable, Default, Debug)]
 #[derive(AsChangeset, Identifiable, Default, Debug)]
 #[table_name = "op_table"]
 #[table_name = "op_table"]
-#[primary_key(rev)]
+#[primary_key(rev_id)]
 pub(crate) struct OpChangeset {
 pub(crate) struct OpChangeset {
-    pub(crate) rev: i64,
+    pub(crate) rev_id: i64,
     pub(crate) state: Option<OpState>,
     pub(crate) state: Option<OpState>,
 }
 }
+
+impl std::convert::Into<OpTable> for Revision {
+    fn into(self) -> OpTable {
+        OpTable {
+            base_rev_id: self.base_rev_id,
+            rev_id: self.rev_id,
+            data: self.delta,
+            md5: self.md5,
+            state: OpState::Local,
+        }
+    }
+}

+ 5 - 2
rust-lib/flowy-document/src/sql_tables/doc/doc_sql.rs

@@ -1,12 +1,14 @@
 use crate::{
 use crate::{
-    errors::DocError,
+    errors::{internal_error, DocError},
     sql_tables::doc::{DocTable, DocTableChangeset},
     sql_tables::doc::{DocTable, DocTableChangeset},
 };
 };
 use flowy_database::{
 use flowy_database::{
     prelude::*,
     prelude::*,
     schema::{doc_table, doc_table::dsl},
     schema::{doc_table, doc_table::dsl},
+    ConnectionPool,
     SqliteConnection,
     SqliteConnection,
 };
 };
+use std::sync::Arc;
 
 
 pub struct DocTableSql {}
 pub struct DocTableSql {}
 
 
@@ -21,7 +23,8 @@ impl DocTableSql {
         Ok(())
         Ok(())
     }
     }
 
 
-    pub(crate) fn read_doc_table(&self, doc_id: &str, conn: &SqliteConnection) -> Result<DocTable, DocError> {
+    pub(crate) fn read_doc_table(&self, doc_id: &str, pool: Arc<ConnectionPool>) -> Result<DocTable, DocError> {
+        let conn = &*pool.get().map_err(internal_error)?;
         let doc_table = dsl::doc_table.filter(doc_table::id.eq(doc_id)).first::<DocTable>(conn)?;
         let doc_table = dsl::doc_table.filter(doc_table::id.eq(doc_id)).first::<DocTable>(conn)?;
 
 
         Ok(doc_table)
         Ok(doc_table)

+ 10 - 0
rust-lib/flowy-document/src/sql_tables/doc/doc_table.rs

@@ -44,3 +44,13 @@ impl std::convert::Into<Doc> for DocTable {
         }
         }
     }
     }
 }
 }
+
+impl std::convert::From<Doc> for DocTable {
+    fn from(doc: Doc) -> Self {
+        Self {
+            id: doc.id,
+            data: doc.data,
+            revision: doc.revision,
+        }
+    }
+}

+ 2 - 5
rust-lib/flowy-document/tests/editor/mod.rs

@@ -174,7 +174,7 @@ impl TestBuilder {
                 std::thread::sleep(Duration::from_millis(*mills_sec as u64));
                 std::thread::sleep(Duration::from_millis(*mills_sec as u64));
             },
             },
             TestOp::AssertStr(delta_i, expected) => {
             TestOp::AssertStr(delta_i, expected) => {
-                assert_eq!(&self.documents[*delta_i].to_string(), expected);
+                assert_eq!(&self.documents[*delta_i].to_plain_string(), expected);
             },
             },
 
 
             TestOp::AssertOpsJson(delta_i, expected) => {
             TestOp::AssertOpsJson(delta_i, expected) => {
@@ -199,10 +199,6 @@ impl TestBuilder {
     }
     }
 }
 }
 
 
-pub fn debug_print_delta(delta: &Delta) {
-    eprintln!("😁 {}", serde_json::to_string(delta).unwrap());
-}
-
 pub struct Rng(StdRng);
 pub struct Rng(StdRng);
 
 
 impl Default for Rng {
 impl Default for Rng {
@@ -210,6 +206,7 @@ impl Default for Rng {
 }
 }
 
 
 impl Rng {
 impl Rng {
+    #[allow(dead_code)]
     pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) }
     pub fn from_seed(seed: [u8; 32]) -> Self { Rng(StdRng::from_seed(seed)) }
 
 
     pub fn gen_string(&mut self, len: usize) -> String { (0..len).map(|_| self.0.gen::<char>()).collect() }
     pub fn gen_string(&mut self, len: usize) -> String { (0..len).map(|_| self.0.gen::<char>()).collect() }

+ 2 - 2
rust-lib/flowy-infra/src/future.rs

@@ -8,12 +8,12 @@ use std::{
 };
 };
 
 
 #[pin_project]
 #[pin_project]
-pub struct RequestFuture<T> {
+pub struct ClosureFuture<T> {
     #[pin]
     #[pin]
     pub fut: Pin<Box<dyn Future<Output = T> + Sync + Send>>,
     pub fut: Pin<Box<dyn Future<Output = T> + Sync + Send>>,
 }
 }
 
 
-impl<T> Future for RequestFuture<T>
+impl<T> Future for ClosureFuture<T>
 where
 where
     T: Send + Sync,
     T: Send + Sync,
 {
 {

+ 13 - 3
rust-lib/flowy-sdk/src/deps_resolve/document_deps.rs

@@ -5,7 +5,7 @@ use flowy_document::{
     prelude::{WsManager, WsSender, WS_ID},
     prelude::{WsManager, WsSender, WS_ID},
 };
 };
 
 
-use flowy_user::services::user::UserSession;
+use flowy_user::{errors::ErrorCode, services::user::UserSession};
 use flowy_ws::{WsMessage, WsMessageHandler};
 use flowy_ws::{WsMessage, WsMessageHandler};
 use parking_lot::RwLock;
 use parking_lot::RwLock;
 use std::{path::Path, sync::Arc};
 use std::{path::Path, sync::Arc};
@@ -51,9 +51,19 @@ impl DocumentUser for DocumentUserImpl {
         Ok(doc_dir)
         Ok(doc_dir)
     }
     }
 
 
-    fn user_id(&self) -> Result<String, DocError> { self.user.user_id().map_err(|e| DocError::internal().context(e)) }
+    fn user_id(&self) -> Result<String, DocError> {
+        self.user.user_id().map_err(|e| match e.code {
+            ErrorCode::InternalError => DocError::internal().context(e.msg),
+            _ => DocError::internal().context(e),
+        })
+    }
 
 
-    fn token(&self) -> Result<String, DocError> { self.user.token().map_err(|e| DocError::internal().context(e)) }
+    fn token(&self) -> Result<String, DocError> {
+        self.user.token().map_err(|e| match e.code {
+            ErrorCode::InternalError => DocError::internal().context(e.msg),
+            _ => DocError::internal().context(e),
+        })
+    }
 }
 }
 
 
 struct WsSenderImpl {
 struct WsSenderImpl {

+ 0 - 26
rust-lib/flowy-sqlite/src/pool.rs

@@ -49,32 +49,6 @@ impl ConnectionPool {
     }
     }
 }
 }
 
 
-#[derive(Default, Debug, Clone)]
-pub struct ConnCounter(Arc<ConnCounterInner>);
-
-impl std::ops::Deref for ConnCounter {
-    type Target = ConnCounterInner;
-
-    fn deref(&self) -> &Self::Target { &*self.0 }
-}
-
-#[derive(Default, Debug)]
-pub struct ConnCounterInner {
-    max_number: AtomicUsize,
-    current_number: AtomicUsize,
-}
-
-impl ConnCounterInner {
-    pub fn get_max_num(&self) -> usize { self.max_number.load(SeqCst) }
-
-    pub fn reset(&self) {
-        // reset max_number to current_number
-        let _ = self
-            .max_number
-            .fetch_update(SeqCst, SeqCst, |_| Some(self.current_number.load(SeqCst)));
-    }
-}
-
 pub type OnExecFunc = Box<dyn Fn() -> Box<dyn Fn(&SqliteConnection, &str)> + Send + Sync>;
 pub type OnExecFunc = Box<dyn Fn() -> Box<dyn Fn(&SqliteConnection, &str)> + Send + Sync>;
 
 
 pub struct PoolConfig {
 pub struct PoolConfig {

+ 6 - 1
rust-lib/flowy-user/src/errors.rs

@@ -110,7 +110,12 @@ impl std::convert::From<::r2d2::Error> for UserError {
 }
 }
 
 
 impl std::convert::From<flowy_ws::errors::WsError> for UserError {
 impl std::convert::From<flowy_ws::errors::WsError> for UserError {
-    fn from(error: flowy_ws::errors::WsError) -> Self { UserError::internal().context(error) }
+    fn from(error: flowy_ws::errors::WsError) -> Self {
+        match error.code {
+            flowy_ws::errors::ErrorCode::InternalError => UserError::internal().context(error.msg),
+            _ => UserError::internal().context(error),
+        }
+    }
 }
 }
 
 
 // use diesel::result::{Error, DatabaseErrorKind};
 // use diesel::result::{Error, DatabaseErrorKind};

+ 0 - 1
rust-lib/flowy-workspace/src/sql_tables/app/app_table.rs

@@ -3,7 +3,6 @@ use crate::{
         app::{App, ColorStyle, UpdateAppParams},
         app::{App, ColorStyle, UpdateAppParams},
         view::RepeatedView,
         view::RepeatedView,
     },
     },
-    impl_sql_binary_expression,
     sql_tables::workspace::WorkspaceTable,
     sql_tables::workspace::WorkspaceTable,
 };
 };
 use diesel::sql_types::Binary;
 use diesel::sql_types::Binary;

+ 0 - 1
rust-lib/flowy-workspace/src/sql_tables/view/view_table.rs

@@ -1,6 +1,5 @@
 use crate::{
 use crate::{
     entities::view::{RepeatedView, UpdateViewParams, View, ViewType},
     entities::view::{RepeatedView, UpdateViewParams, View, ViewType},
-    impl_sql_integer_expression,
     sql_tables::app::AppTable,
     sql_tables::app::AppTable,
 };
 };
 use diesel::sql_types::Integer;
 use diesel::sql_types::Integer;

+ 2 - 2
rust-lib/flowy-ws/src/errors.rs

@@ -8,10 +8,10 @@ use url::ParseError;
 #[derive(Debug, Default, Clone, ProtoBuf)]
 #[derive(Debug, Default, Clone, ProtoBuf)]
 pub struct WsError {
 pub struct WsError {
     #[pb(index = 1)]
     #[pb(index = 1)]
-    code: ErrorCode,
+    pub code: ErrorCode,
 
 
     #[pb(index = 2)]
     #[pb(index = 2)]
-    msg: String,
+    pub msg: String,
 }
 }
 
 
 macro_rules! static_user_error {
 macro_rules! static_user_error {