Browse Source

refactor: replace object with object's revision entity

appflowy 2 years ago
parent
commit
f8ec4b3e24
39 changed files with 2607 additions and 1074 deletions
  1. 1 4
      frontend/app_flowy/lib/workspace/application/view/view_ext.dart
  2. 394 82
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pb.dart
  3. 66 9
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pbjson.dart
  4. 16 11
      frontend/rust-lib/flowy-folder/src/event_map.rs
  5. 8 5
      frontend/rust-lib/flowy-folder/src/manager.rs
  6. 23 16
      frontend/rust-lib/flowy-folder/src/services/app/controller.rs
  7. 7 9
      frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs
  8. 8 11
      frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs
  9. 13 17
      frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs
  10. 14 14
      frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs
  11. 27 11
      frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs
  12. 40 47
      frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs
  13. 21 19
      frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs
  14. 14 18
      frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs
  15. 32 36
      frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs
  16. 33 25
      frontend/rust-lib/flowy-folder/src/services/trash/controller.rs
  17. 65 55
      frontend/rust-lib/flowy-folder/src/services/view/controller.rs
  18. 18 7
      frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs
  19. 27 15
      frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs
  20. 39 20
      frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs
  21. 2 9
      frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs
  22. 17 17
      frontend/rust-lib/flowy-folder/tests/workspace/script.rs
  23. 100 80
      frontend/rust-lib/flowy-net/src/http_server/folder.rs
  24. 26 27
      frontend/rust-lib/flowy-net/src/local_server/server.rs
  25. 3 2
      frontend/rust-lib/flowy-test/src/helper.rs
  26. 20 48
      frontend/rust-lib/flowy-user/src/services/database.rs
  27. 1 1
      shared-lib/flowy-folder-data-model/src/entities/app.rs
  28. 4 24
      shared-lib/flowy-folder-data-model/src/entities/trash.rs
  29. 55 26
      shared-lib/flowy-folder-data-model/src/entities/view.rs
  30. 1292 233
      shared-lib/flowy-folder-data-model/src/protobuf/model/view.rs
  31. 26 7
      shared-lib/flowy-folder-data-model/src/protobuf/proto/view.proto
  32. 11 24
      shared-lib/flowy-folder-data-model/src/revision/app.rs
  33. 24 7
      shared-lib/flowy-folder-data-model/src/revision/trash.rs
  34. 77 32
      shared-lib/flowy-folder-data-model/src/revision/view.rs
  35. 1 14
      shared-lib/flowy-folder-data-model/src/revision/workspace.rs
  36. 12 20
      shared-lib/flowy-folder-data-model/src/user_default.rs
  37. 4 8
      shared-lib/flowy-sync/src/client_folder/builder.rs
  38. 65 63
      shared-lib/flowy-sync/src/client_folder/folder_pad.rs
  39. 1 1
      shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

+ 1 - 4
frontend/app_flowy/lib/workspace/application/view/view_ext.dart

@@ -34,10 +34,7 @@ extension FlowyPluginExtension on FlowyPlugin {
 
 extension ViewExtension on View {
   Widget renderThumbnail({Color? iconColor}) {
-    String thumbnail = this.thumbnail;
-    if (thumbnail.isEmpty) {
-      thumbnail = "file_icon";
-    }
+    String thumbnail = "file_icon";
 
     final Widget widget = svgWidget(thumbnail, color: iconColor);
     return widget;

+ 394 - 82
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pb.dart

@@ -19,15 +19,10 @@ class View extends $pb.GeneratedMessage {
     ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId')
     ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
-    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
-    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.TextBlock, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
-    ..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'version')
-    ..aOM<RepeatedView>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: RepeatedView.create)
-    ..aInt64(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
-    ..aInt64(9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
-    ..aOS(10, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData')
-    ..aOS(11, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
-    ..a<$core.int>(12, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
+    ..e<ViewDataType>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.TextBlock, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
+    ..aInt64(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
+    ..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
+    ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'pluginType', $pb.PbFieldType.O3)
     ..hasRequiredFields = false
   ;
 
@@ -36,14 +31,9 @@ class View extends $pb.GeneratedMessage {
     $core.String? id,
     $core.String? belongToId,
     $core.String? name,
-    $core.String? desc,
     ViewDataType? dataType,
-    $fixnum.Int64? version,
-    RepeatedView? belongings,
     $fixnum.Int64? modifiedTime,
     $fixnum.Int64? createTime,
-    $core.String? extData,
-    $core.String? thumbnail,
     $core.int? pluginType,
   }) {
     final _result = create();
@@ -56,30 +46,15 @@ class View extends $pb.GeneratedMessage {
     if (name != null) {
       _result.name = name;
     }
-    if (desc != null) {
-      _result.desc = desc;
-    }
     if (dataType != null) {
       _result.dataType = dataType;
     }
-    if (version != null) {
-      _result.version = version;
-    }
-    if (belongings != null) {
-      _result.belongings = belongings;
-    }
     if (modifiedTime != null) {
       _result.modifiedTime = modifiedTime;
     }
     if (createTime != null) {
       _result.createTime = createTime;
     }
-    if (extData != null) {
-      _result.extData = extData;
-    }
-    if (thumbnail != null) {
-      _result.thumbnail = thumbnail;
-    }
     if (pluginType != null) {
       _result.pluginType = pluginType;
     }
@@ -133,6 +108,137 @@ class View extends $pb.GeneratedMessage {
   @$pb.TagNumber(3)
   void clearName() => clearField(3);
 
+  @$pb.TagNumber(4)
+  ViewDataType get dataType => $_getN(3);
+  @$pb.TagNumber(4)
+  set dataType(ViewDataType v) { setField(4, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasDataType() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearDataType() => clearField(4);
+
+  @$pb.TagNumber(5)
+  $fixnum.Int64 get modifiedTime => $_getI64(4);
+  @$pb.TagNumber(5)
+  set modifiedTime($fixnum.Int64 v) { $_setInt64(4, v); }
+  @$pb.TagNumber(5)
+  $core.bool hasModifiedTime() => $_has(4);
+  @$pb.TagNumber(5)
+  void clearModifiedTime() => clearField(5);
+
+  @$pb.TagNumber(6)
+  $fixnum.Int64 get createTime => $_getI64(5);
+  @$pb.TagNumber(6)
+  set createTime($fixnum.Int64 v) { $_setInt64(5, v); }
+  @$pb.TagNumber(6)
+  $core.bool hasCreateTime() => $_has(5);
+  @$pb.TagNumber(6)
+  void clearCreateTime() => clearField(6);
+
+  @$pb.TagNumber(7)
+  $core.int get pluginType => $_getIZ(6);
+  @$pb.TagNumber(7)
+  set pluginType($core.int v) { $_setSignedInt32(6, v); }
+  @$pb.TagNumber(7)
+  $core.bool hasPluginType() => $_has(6);
+  @$pb.TagNumber(7)
+  void clearPluginType() => clearField(7);
+}
+
+class ViewInfo extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ViewInfo', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongToId')
+    ..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
+    ..e<ViewDataType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'dataType', $pb.PbFieldType.OE, defaultOrMaker: ViewDataType.TextBlock, valueOf: ViewDataType.valueOf, enumValues: ViewDataType.values)
+    ..aOM<RepeatedView>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'belongings', subBuilder: RepeatedView.create)
+    ..aOM<ViewExtData>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'extData', subBuilder: ViewExtData.create)
+    ..hasRequiredFields = false
+  ;
+
+  ViewInfo._() : super();
+  factory ViewInfo({
+    $core.String? id,
+    $core.String? belongToId,
+    $core.String? name,
+    $core.String? desc,
+    ViewDataType? dataType,
+    RepeatedView? belongings,
+    ViewExtData? extData,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (belongToId != null) {
+      _result.belongToId = belongToId;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (desc != null) {
+      _result.desc = desc;
+    }
+    if (dataType != null) {
+      _result.dataType = dataType;
+    }
+    if (belongings != null) {
+      _result.belongings = belongings;
+    }
+    if (extData != null) {
+      _result.extData = extData;
+    }
+    return _result;
+  }
+  factory ViewInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ViewInfo.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')
+  ViewInfo clone() => ViewInfo()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  ViewInfo copyWith(void Function(ViewInfo) updates) => super.copyWith((message) => updates(message as ViewInfo)) as ViewInfo; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static ViewInfo create() => ViewInfo._();
+  ViewInfo createEmptyInstance() => create();
+  static $pb.PbList<ViewInfo> createRepeated() => $pb.PbList<ViewInfo>();
+  @$core.pragma('dart2js:noInline')
+  static ViewInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ViewInfo>(create);
+  static ViewInfo? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get belongToId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set belongToId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasBelongToId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearBelongToId() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $core.String get name => $_getSZ(2);
+  @$pb.TagNumber(3)
+  set name($core.String v) { $_setString(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasName() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearName() => clearField(3);
+
   @$pb.TagNumber(4)
   $core.String get desc => $_getSZ(3);
   @$pb.TagNumber(4)
@@ -152,69 +258,275 @@ class View extends $pb.GeneratedMessage {
   void clearDataType() => clearField(5);
 
   @$pb.TagNumber(6)
-  $fixnum.Int64 get version => $_getI64(5);
+  RepeatedView get belongings => $_getN(5);
   @$pb.TagNumber(6)
-  set version($fixnum.Int64 v) { $_setInt64(5, v); }
+  set belongings(RepeatedView v) { setField(6, v); }
   @$pb.TagNumber(6)
-  $core.bool hasVersion() => $_has(5);
+  $core.bool hasBelongings() => $_has(5);
   @$pb.TagNumber(6)
-  void clearVersion() => clearField(6);
+  void clearBelongings() => clearField(6);
+  @$pb.TagNumber(6)
+  RepeatedView ensureBelongings() => $_ensure(5);
 
   @$pb.TagNumber(7)
-  RepeatedView get belongings => $_getN(6);
+  ViewExtData get extData => $_getN(6);
   @$pb.TagNumber(7)
-  set belongings(RepeatedView v) { setField(7, v); }
+  set extData(ViewExtData v) { setField(7, v); }
   @$pb.TagNumber(7)
-  $core.bool hasBelongings() => $_has(6);
+  $core.bool hasExtData() => $_has(6);
   @$pb.TagNumber(7)
-  void clearBelongings() => clearField(7);
+  void clearExtData() => clearField(7);
   @$pb.TagNumber(7)
-  RepeatedView ensureBelongings() => $_ensure(6);
+  ViewExtData ensureExtData() => $_ensure(6);
+}
 
-  @$pb.TagNumber(8)
-  $fixnum.Int64 get modifiedTime => $_getI64(7);
-  @$pb.TagNumber(8)
-  set modifiedTime($fixnum.Int64 v) { $_setInt64(7, v); }
-  @$pb.TagNumber(8)
-  $core.bool hasModifiedTime() => $_has(7);
-  @$pb.TagNumber(8)
-  void clearModifiedTime() => clearField(8);
-
-  @$pb.TagNumber(9)
-  $fixnum.Int64 get createTime => $_getI64(8);
-  @$pb.TagNumber(9)
-  set createTime($fixnum.Int64 v) { $_setInt64(8, v); }
-  @$pb.TagNumber(9)
-  $core.bool hasCreateTime() => $_has(8);
-  @$pb.TagNumber(9)
-  void clearCreateTime() => clearField(9);
-
-  @$pb.TagNumber(10)
-  $core.String get extData => $_getSZ(9);
-  @$pb.TagNumber(10)
-  set extData($core.String v) { $_setString(9, v); }
-  @$pb.TagNumber(10)
-  $core.bool hasExtData() => $_has(9);
-  @$pb.TagNumber(10)
-  void clearExtData() => clearField(10);
-
-  @$pb.TagNumber(11)
-  $core.String get thumbnail => $_getSZ(10);
-  @$pb.TagNumber(11)
-  set thumbnail($core.String v) { $_setString(10, v); }
-  @$pb.TagNumber(11)
-  $core.bool hasThumbnail() => $_has(10);
-  @$pb.TagNumber(11)
-  void clearThumbnail() => clearField(11);
-
-  @$pb.TagNumber(12)
-  $core.int get pluginType => $_getIZ(11);
-  @$pb.TagNumber(12)
-  set pluginType($core.int v) { $_setSignedInt32(11, v); }
-  @$pb.TagNumber(12)
-  $core.bool hasPluginType() => $_has(11);
-  @$pb.TagNumber(12)
-  void clearPluginType() => clearField(12);
+class ViewExtData extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ViewExtData', createEmptyInstance: create)
+    ..aOM<ViewFilter>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'filter', subBuilder: ViewFilter.create)
+    ..aOM<ViewGroup>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'group', subBuilder: ViewGroup.create)
+    ..aOM<ViewSort>(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'sort', subBuilder: ViewSort.create)
+    ..hasRequiredFields = false
+  ;
+
+  ViewExtData._() : super();
+  factory ViewExtData({
+    ViewFilter? filter,
+    ViewGroup? group,
+    ViewSort? sort,
+  }) {
+    final _result = create();
+    if (filter != null) {
+      _result.filter = filter;
+    }
+    if (group != null) {
+      _result.group = group;
+    }
+    if (sort != null) {
+      _result.sort = sort;
+    }
+    return _result;
+  }
+  factory ViewExtData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ViewExtData.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')
+  ViewExtData clone() => ViewExtData()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  ViewExtData copyWith(void Function(ViewExtData) updates) => super.copyWith((message) => updates(message as ViewExtData)) as ViewExtData; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static ViewExtData create() => ViewExtData._();
+  ViewExtData createEmptyInstance() => create();
+  static $pb.PbList<ViewExtData> createRepeated() => $pb.PbList<ViewExtData>();
+  @$core.pragma('dart2js:noInline')
+  static ViewExtData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ViewExtData>(create);
+  static ViewExtData? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  ViewFilter get filter => $_getN(0);
+  @$pb.TagNumber(1)
+  set filter(ViewFilter v) { setField(1, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFilter() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFilter() => clearField(1);
+  @$pb.TagNumber(1)
+  ViewFilter ensureFilter() => $_ensure(0);
+
+  @$pb.TagNumber(2)
+  ViewGroup get group => $_getN(1);
+  @$pb.TagNumber(2)
+  set group(ViewGroup v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasGroup() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearGroup() => clearField(2);
+  @$pb.TagNumber(2)
+  ViewGroup ensureGroup() => $_ensure(1);
+
+  @$pb.TagNumber(3)
+  ViewSort get sort => $_getN(2);
+  @$pb.TagNumber(3)
+  set sort(ViewSort v) { setField(3, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasSort() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearSort() => clearField(3);
+  @$pb.TagNumber(3)
+  ViewSort ensureSort() => $_ensure(2);
+}
+
+class ViewFilter extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ViewFilter', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..hasRequiredFields = false
+  ;
+
+  ViewFilter._() : super();
+  factory ViewFilter({
+    $core.String? fieldId,
+  }) {
+    final _result = create();
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    return _result;
+  }
+  factory ViewFilter.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ViewFilter.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')
+  ViewFilter clone() => ViewFilter()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  ViewFilter copyWith(void Function(ViewFilter) updates) => super.copyWith((message) => updates(message as ViewFilter)) as ViewFilter; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static ViewFilter create() => ViewFilter._();
+  ViewFilter createEmptyInstance() => create();
+  static $pb.PbList<ViewFilter> createRepeated() => $pb.PbList<ViewFilter>();
+  @$core.pragma('dart2js:noInline')
+  static ViewFilter getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ViewFilter>(create);
+  static ViewFilter? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get fieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set fieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFieldId() => clearField(1);
+}
+
+enum ViewGroup_OneOfSubGroupFieldId {
+  subGroupFieldId, 
+  notSet
+}
+
+class ViewGroup extends $pb.GeneratedMessage {
+  static const $core.Map<$core.int, ViewGroup_OneOfSubGroupFieldId> _ViewGroup_OneOfSubGroupFieldIdByTag = {
+    2 : ViewGroup_OneOfSubGroupFieldId.subGroupFieldId,
+    0 : ViewGroup_OneOfSubGroupFieldId.notSet
+  };
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ViewGroup', createEmptyInstance: create)
+    ..oo(0, [2])
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'groupFieldId')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'subGroupFieldId')
+    ..hasRequiredFields = false
+  ;
+
+  ViewGroup._() : super();
+  factory ViewGroup({
+    $core.String? groupFieldId,
+    $core.String? subGroupFieldId,
+  }) {
+    final _result = create();
+    if (groupFieldId != null) {
+      _result.groupFieldId = groupFieldId;
+    }
+    if (subGroupFieldId != null) {
+      _result.subGroupFieldId = subGroupFieldId;
+    }
+    return _result;
+  }
+  factory ViewGroup.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ViewGroup.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')
+  ViewGroup clone() => ViewGroup()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  ViewGroup copyWith(void Function(ViewGroup) updates) => super.copyWith((message) => updates(message as ViewGroup)) as ViewGroup; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static ViewGroup create() => ViewGroup._();
+  ViewGroup createEmptyInstance() => create();
+  static $pb.PbList<ViewGroup> createRepeated() => $pb.PbList<ViewGroup>();
+  @$core.pragma('dart2js:noInline')
+  static ViewGroup getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ViewGroup>(create);
+  static ViewGroup? _defaultInstance;
+
+  ViewGroup_OneOfSubGroupFieldId whichOneOfSubGroupFieldId() => _ViewGroup_OneOfSubGroupFieldIdByTag[$_whichOneof(0)]!;
+  void clearOneOfSubGroupFieldId() => clearField($_whichOneof(0));
+
+  @$pb.TagNumber(1)
+  $core.String get groupFieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set groupFieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasGroupFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearGroupFieldId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get subGroupFieldId => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set subGroupFieldId($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasSubGroupFieldId() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearSubGroupFieldId() => clearField(2);
+}
+
+class ViewSort extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ViewSort', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'fieldId')
+    ..hasRequiredFields = false
+  ;
+
+  ViewSort._() : super();
+  factory ViewSort({
+    $core.String? fieldId,
+  }) {
+    final _result = create();
+    if (fieldId != null) {
+      _result.fieldId = fieldId;
+    }
+    return _result;
+  }
+  factory ViewSort.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory ViewSort.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')
+  ViewSort clone() => ViewSort()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  ViewSort copyWith(void Function(ViewSort) updates) => super.copyWith((message) => updates(message as ViewSort)) as ViewSort; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static ViewSort create() => ViewSort._();
+  ViewSort createEmptyInstance() => create();
+  static $pb.PbList<ViewSort> createRepeated() => $pb.PbList<ViewSort>();
+  @$core.pragma('dart2js:noInline')
+  static ViewSort getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ViewSort>(create);
+  static ViewSort? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get fieldId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set fieldId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasFieldId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearFieldId() => clearField(1);
 }
 
 class RepeatedView extends $pb.GeneratedMessage {

+ 66 - 9
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-folder-data-model/view.pbjson.dart

@@ -33,24 +33,81 @@ final $typed_data.Uint8List moveFolderItemTypeDescriptor = $convert.base64Decode
 @$core.Deprecated('Use viewDescriptor instead')
 const View$json = const {
   '1': 'View',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'belong_to_id', '3': 2, '4': 1, '5': 9, '10': 'belongToId'},
+    const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
+    const {'1': 'data_type', '3': 4, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
+    const {'1': 'modified_time', '3': 5, '4': 1, '5': 3, '10': 'modifiedTime'},
+    const {'1': 'create_time', '3': 6, '4': 1, '5': 3, '10': 'createTime'},
+    const {'1': 'plugin_type', '3': 7, '4': 1, '5': 5, '10': 'pluginType'},
+  ],
+};
+
+/// Descriptor for `View`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRIqCglkYXRhX3R5cGUYBCABKA4yDS5WaWV3RGF0YVR5cGVSCGRhdGFUeXBlEiMKDW1vZGlmaWVkX3RpbWUYBSABKANSDG1vZGlmaWVkVGltZRIfCgtjcmVhdGVfdGltZRgGIAEoA1IKY3JlYXRlVGltZRIfCgtwbHVnaW5fdHlwZRgHIAEoBVIKcGx1Z2luVHlwZQ==');
+@$core.Deprecated('Use viewInfoDescriptor instead')
+const ViewInfo$json = const {
+  '1': 'ViewInfo',
   '2': const [
     const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
     const {'1': 'belong_to_id', '3': 2, '4': 1, '5': 9, '10': 'belongToId'},
     const {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'},
     const {'1': 'desc', '3': 4, '4': 1, '5': 9, '10': 'desc'},
     const {'1': 'data_type', '3': 5, '4': 1, '5': 14, '6': '.ViewDataType', '10': 'dataType'},
-    const {'1': 'version', '3': 6, '4': 1, '5': 3, '10': 'version'},
-    const {'1': 'belongings', '3': 7, '4': 1, '5': 11, '6': '.RepeatedView', '10': 'belongings'},
-    const {'1': 'modified_time', '3': 8, '4': 1, '5': 3, '10': 'modifiedTime'},
-    const {'1': 'create_time', '3': 9, '4': 1, '5': 3, '10': 'createTime'},
-    const {'1': 'ext_data', '3': 10, '4': 1, '5': 9, '10': 'extData'},
-    const {'1': 'thumbnail', '3': 11, '4': 1, '5': 9, '10': 'thumbnail'},
-    const {'1': 'plugin_type', '3': 12, '4': 1, '5': 5, '10': 'pluginType'},
+    const {'1': 'belongings', '3': 6, '4': 1, '5': 11, '6': '.RepeatedView', '10': 'belongings'},
+    const {'1': 'ext_data', '3': 7, '4': 1, '5': 11, '6': '.ViewExtData', '10': 'extData'},
   ],
 };
 
-/// Descriptor for `View`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List viewDescriptor = $convert.base64Decode('CgRWaWV3Eg4KAmlkGAEgASgJUgJpZBIgCgxiZWxvbmdfdG9faWQYAiABKAlSCmJlbG9uZ1RvSWQSEgoEbmFtZRgDIAEoCVIEbmFtZRISCgRkZXNjGAQgASgJUgRkZXNjEioKCWRhdGFfdHlwZRgFIAEoDjINLlZpZXdEYXRhVHlwZVIIZGF0YVR5cGUSGAoHdmVyc2lvbhgGIAEoA1IHdmVyc2lvbhItCgpiZWxvbmdpbmdzGAcgASgLMg0uUmVwZWF0ZWRWaWV3UgpiZWxvbmdpbmdzEiMKDW1vZGlmaWVkX3RpbWUYCCABKANSDG1vZGlmaWVkVGltZRIfCgtjcmVhdGVfdGltZRgJIAEoA1IKY3JlYXRlVGltZRIZCghleHRfZGF0YRgKIAEoCVIHZXh0RGF0YRIcCgl0aHVtYm5haWwYCyABKAlSCXRodW1ibmFpbBIfCgtwbHVnaW5fdHlwZRgMIAEoBVIKcGx1Z2luVHlwZQ==');
+/// Descriptor for `ViewInfo`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List viewInfoDescriptor = $convert.base64Decode('CghWaWV3SW5mbxIOCgJpZBgBIAEoCVICaWQSIAoMYmVsb25nX3RvX2lkGAIgASgJUgpiZWxvbmdUb0lkEhIKBG5hbWUYAyABKAlSBG5hbWUSEgoEZGVzYxgEIAEoCVIEZGVzYxIqCglkYXRhX3R5cGUYBSABKA4yDS5WaWV3RGF0YVR5cGVSCGRhdGFUeXBlEi0KCmJlbG9uZ2luZ3MYBiABKAsyDS5SZXBlYXRlZFZpZXdSCmJlbG9uZ2luZ3MSJwoIZXh0X2RhdGEYByABKAsyDC5WaWV3RXh0RGF0YVIHZXh0RGF0YQ==');
+@$core.Deprecated('Use viewExtDataDescriptor instead')
+const ViewExtData$json = const {
+  '1': 'ViewExtData',
+  '2': const [
+    const {'1': 'filter', '3': 1, '4': 1, '5': 11, '6': '.ViewFilter', '10': 'filter'},
+    const {'1': 'group', '3': 2, '4': 1, '5': 11, '6': '.ViewGroup', '10': 'group'},
+    const {'1': 'sort', '3': 3, '4': 1, '5': 11, '6': '.ViewSort', '10': 'sort'},
+  ],
+};
+
+/// Descriptor for `ViewExtData`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List viewExtDataDescriptor = $convert.base64Decode('CgtWaWV3RXh0RGF0YRIjCgZmaWx0ZXIYASABKAsyCy5WaWV3RmlsdGVyUgZmaWx0ZXISIAoFZ3JvdXAYAiABKAsyCi5WaWV3R3JvdXBSBWdyb3VwEh0KBHNvcnQYAyABKAsyCS5WaWV3U29ydFIEc29ydA==');
+@$core.Deprecated('Use viewFilterDescriptor instead')
+const ViewFilter$json = const {
+  '1': 'ViewFilter',
+  '2': const [
+    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
+  ],
+};
+
+/// Descriptor for `ViewFilter`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List viewFilterDescriptor = $convert.base64Decode('CgpWaWV3RmlsdGVyEhkKCGZpZWxkX2lkGAEgASgJUgdmaWVsZElk');
+@$core.Deprecated('Use viewGroupDescriptor instead')
+const ViewGroup$json = const {
+  '1': 'ViewGroup',
+  '2': const [
+    const {'1': 'group_field_id', '3': 1, '4': 1, '5': 9, '10': 'groupFieldId'},
+    const {'1': 'sub_group_field_id', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'subGroupFieldId'},
+  ],
+  '8': const [
+    const {'1': 'one_of_sub_group_field_id'},
+  ],
+};
+
+/// Descriptor for `ViewGroup`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List viewGroupDescriptor = $convert.base64Decode('CglWaWV3R3JvdXASJAoOZ3JvdXBfZmllbGRfaWQYASABKAlSDGdyb3VwRmllbGRJZBItChJzdWJfZ3JvdXBfZmllbGRfaWQYAiABKAlIAFIPc3ViR3JvdXBGaWVsZElkQhsKGW9uZV9vZl9zdWJfZ3JvdXBfZmllbGRfaWQ=');
+@$core.Deprecated('Use viewSortDescriptor instead')
+const ViewSort$json = const {
+  '1': 'ViewSort',
+  '2': const [
+    const {'1': 'field_id', '3': 1, '4': 1, '5': 9, '10': 'fieldId'},
+  ],
+};
+
+/// Descriptor for `ViewSort`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List viewSortDescriptor = $convert.base64Decode('CghWaWV3U29ydBIZCghmaWVsZF9pZBgBIAEoCVIHZmllbGRJZA==');
 @$core.Deprecated('Use repeatedViewDescriptor instead')
 const RepeatedView$json = const {
   '1': 'RepeatedView',

+ 16 - 11
frontend/rust-lib/flowy-folder/src/event_map.rs

@@ -1,9 +1,9 @@
 use crate::{
     entities::{
-        app::{App, AppId, CreateAppParams, UpdateAppParams},
-        trash::{RepeatedTrash, RepeatedTrashId},
-        view::{CreateViewParams, RepeatedViewId, UpdateViewParams, View, ViewId},
-        workspace::{CreateWorkspaceParams, RepeatedWorkspace, UpdateWorkspaceParams, Workspace, WorkspaceId},
+        app::{AppId, CreateAppParams, UpdateAppParams},
+        trash::RepeatedTrashId,
+        view::{CreateViewParams, RepeatedViewId, UpdateViewParams, ViewId},
+        workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceId},
     },
     errors::FlowyError,
     manager::FolderManager,
@@ -11,6 +11,7 @@ use crate::{
 };
 use flowy_database::{ConnectionPool, DBConnection};
 use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
+use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
 use lib_dispatch::prelude::*;
 use lib_infra::future::FutureResult;
 use std::sync::Arc;
@@ -155,27 +156,31 @@ pub trait FolderCouldServiceV1: Send + Sync {
     fn init(&self);
 
     // Workspace
-    fn create_workspace(&self, token: &str, params: CreateWorkspaceParams) -> FutureResult<Workspace, FlowyError>;
+    fn create_workspace(
+        &self,
+        token: &str,
+        params: CreateWorkspaceParams,
+    ) -> FutureResult<WorkspaceRevision, FlowyError>;
 
-    fn read_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult<RepeatedWorkspace, FlowyError>;
+    fn read_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult<Vec<WorkspaceRevision>, FlowyError>;
 
     fn update_workspace(&self, token: &str, params: UpdateWorkspaceParams) -> FutureResult<(), FlowyError>;
 
     fn delete_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult<(), FlowyError>;
 
     // View
-    fn create_view(&self, token: &str, params: CreateViewParams) -> FutureResult<View, FlowyError>;
+    fn create_view(&self, token: &str, params: CreateViewParams) -> FutureResult<ViewRevision, FlowyError>;
 
-    fn read_view(&self, token: &str, params: ViewId) -> FutureResult<Option<View>, FlowyError>;
+    fn read_view(&self, token: &str, params: ViewId) -> FutureResult<Option<ViewRevision>, FlowyError>;
 
     fn delete_view(&self, token: &str, params: RepeatedViewId) -> FutureResult<(), FlowyError>;
 
     fn update_view(&self, token: &str, params: UpdateViewParams) -> FutureResult<(), FlowyError>;
 
     // App
-    fn create_app(&self, token: &str, params: CreateAppParams) -> FutureResult<App, FlowyError>;
+    fn create_app(&self, token: &str, params: CreateAppParams) -> FutureResult<AppRevision, FlowyError>;
 
-    fn read_app(&self, token: &str, params: AppId) -> FutureResult<Option<App>, FlowyError>;
+    fn read_app(&self, token: &str, params: AppId) -> FutureResult<Option<AppRevision>, FlowyError>;
 
     fn update_app(&self, token: &str, params: UpdateAppParams) -> FutureResult<(), FlowyError>;
 
@@ -186,5 +191,5 @@ pub trait FolderCouldServiceV1: Send + Sync {
 
     fn delete_trash(&self, token: &str, params: RepeatedTrashId) -> FutureResult<(), FlowyError>;
 
-    fn read_trash(&self, token: &str) -> FutureResult<RepeatedTrash, FlowyError>;
+    fn read_trash(&self, token: &str) -> FutureResult<Vec<TrashRevision>, FlowyError>;
 }

+ 8 - 5
frontend/rust-lib/flowy-folder/src/manager.rs

@@ -13,6 +13,7 @@ use flowy_sync::client_document::default::{initial_quill_delta_string, initial_r
 
 use flowy_error::FlowyError;
 use flowy_folder_data_model::entities::view::ViewDataType;
+
 use flowy_folder_data_model::user_default;
 use flowy_revision::disk::SQLiteTextBlockRevisionPersistence;
 use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket};
@@ -199,9 +200,9 @@ impl DefaultFolderBuilder {
         view_controller: Arc<ViewController>,
     ) -> FlowyResult<()> {
         log::debug!("Create user default workspace");
-        let workspace = user_default::create_default_workspace();
-        set_current_workspace(&workspace.id);
-        for app in workspace.apps.iter() {
+        let workspace_rev = user_default::create_default_workspace();
+        set_current_workspace(&workspace_rev.id);
+        for app in workspace_rev.apps.iter() {
             for (index, view) in app.belongings.iter().enumerate() {
                 let view_data = if index == 0 {
                     initial_read_me().to_delta_str()
@@ -214,10 +215,12 @@ impl DefaultFolderBuilder {
                     .await?;
             }
         }
-        let folder = FolderPad::new(vec![workspace.clone()], vec![])?;
+        let folder = FolderPad::new(vec![workspace_rev.clone()], vec![])?;
         let folder_id = FolderId::new(user_id);
         let _ = persistence.save_folder(user_id, &folder_id, folder).await?;
-        let repeated_workspace = RepeatedWorkspace { items: vec![workspace] };
+        let repeated_workspace = RepeatedWorkspace {
+            items: vec![workspace_rev.into()],
+        };
         send_dart_notification(token, FolderNotification::UserCreateWorkspace)
             .payload(repeated_workspace)
             .send();

+ 23 - 16
frontend/rust-lib/flowy-folder/src/services/app/controller.rs

@@ -12,6 +12,7 @@ use crate::{
     },
 };
 
+use flowy_folder_data_model::revision::AppRevision;
 use futures::{FutureExt, StreamExt};
 use std::{collections::HashSet, sync::Arc};
 
@@ -48,7 +49,7 @@ impl AppController {
         self.create_app_on_local(app).await
     }
 
-    pub(crate) async fn create_app_on_local(&self, app: App) -> Result<App, FlowyError> {
+    pub(crate) async fn create_app_on_local(&self, app: AppRevision) -> Result<App, FlowyError> {
         let _ = self
             .persistence
             .begin_transaction(|transaction| {
@@ -57,10 +58,10 @@ impl AppController {
                 Ok(())
             })
             .await?;
-        Ok(app)
+        Ok(app.into())
     }
 
-    pub(crate) async fn read_app(&self, params: AppId) -> Result<App, FlowyError> {
+    pub(crate) async fn read_app(&self, params: AppId) -> Result<AppRevision, FlowyError> {
         let app = self
             .persistence
             .begin_transaction(|transaction| {
@@ -80,14 +81,15 @@ impl AppController {
         let changeset = AppChangeset::new(params.clone());
         let app_id = changeset.id.clone();
 
-        let app = self
+        let app: App = self
             .persistence
             .begin_transaction(|transaction| {
                 let _ = transaction.update_app(changeset)?;
                 let app = transaction.read_app(&app_id)?;
                 Ok(app)
             })
-            .await?;
+            .await?
+            .into();
         send_dart_notification(&app_id, FolderNotification::AppUpdated)
             .payload(app)
             .send();
@@ -108,8 +110,8 @@ impl AppController {
         Ok(())
     }
 
-    pub(crate) async fn read_local_apps(&self, ids: Vec<String>) -> Result<Vec<App>, FlowyError> {
-        let apps = self
+    pub(crate) async fn read_local_apps(&self, ids: Vec<String>) -> Result<Vec<AppRevision>, FlowyError> {
+        let app_revs = self
             .persistence
             .begin_transaction(|transaction| {
                 let mut apps = vec![];
@@ -119,13 +121,13 @@ impl AppController {
                 Ok(apps)
             })
             .await?;
-        Ok(apps)
+        Ok(app_revs)
     }
 }
 
 impl AppController {
     #[tracing::instrument(level = "trace", skip(self), err)]
-    async fn create_app_on_server(&self, params: CreateAppParams) -> Result<App, FlowyError> {
+    async fn create_app_on_server(&self, params: CreateAppParams) -> Result<AppRevision, FlowyError> {
         let token = self.user.token()?;
         let app = self.cloud_service.create_app(&token, params).await?;
         Ok(app)
@@ -154,12 +156,13 @@ impl AppController {
         let persistence = self.persistence.clone();
         tokio::spawn(async move {
             match server.read_app(&token, params).await {
-                Ok(Some(app)) => {
+                Ok(Some(app_rev)) => {
                     match persistence
-                        .begin_transaction(|transaction| transaction.create_app(app.clone()))
+                        .begin_transaction(|transaction| transaction.create_app(app_rev.clone()))
                         .await
                     {
                         Ok(_) => {
+                            let app: App = app_rev.into();
                             send_dart_notification(&app.id, FolderNotification::AppUpdated)
                                 .payload(app)
                                 .send();
@@ -240,7 +243,11 @@ fn notify_apps_changed<'a>(
     trash_controller: Arc<TrashController>,
     transaction: &'a (dyn FolderPersistenceTransaction + 'a),
 ) -> FlowyResult<()> {
-    let repeated_app = read_local_workspace_apps(workspace_id, trash_controller, transaction)?;
+    let items = read_local_workspace_apps(workspace_id, trash_controller, transaction)?
+        .into_iter()
+        .map(|app_rev| app_rev.into())
+        .collect();
+    let repeated_app = RepeatedApp { items };
     send_dart_notification(workspace_id, FolderNotification::WorkspaceAppsChanged)
         .payload(repeated_app)
         .send();
@@ -251,9 +258,9 @@ pub fn read_local_workspace_apps<'a>(
     workspace_id: &str,
     trash_controller: Arc<TrashController>,
     transaction: &'a (dyn FolderPersistenceTransaction + 'a),
-) -> Result<RepeatedApp, FlowyError> {
-    let mut apps = transaction.read_workspace_apps(workspace_id)?;
+) -> Result<Vec<AppRevision>, FlowyError> {
+    let mut app_revs = transaction.read_workspace_apps(workspace_id)?;
     let trash_ids = trash_controller.read_trash_ids(transaction)?;
-    apps.retain(|app| !trash_ids.contains(&app.id));
-    Ok(RepeatedApp { items: apps })
+    app_revs.retain(|app| !trash_ids.contains(&app.id));
+    Ok(app_revs)
 }

+ 7 - 9
frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs

@@ -1,11 +1,9 @@
 use crate::{
-    entities::{
-        app::{App, AppId, CreateAppParams, CreateAppPayload, UpdateAppParams, UpdateAppPayload},
-        trash::Trash,
-    },
+    entities::app::{App, AppId, CreateAppParams, CreateAppPayload, UpdateAppParams, UpdateAppPayload},
     errors::FlowyError,
     services::{AppController, TrashController, ViewController},
 };
+use flowy_folder_data_model::revision::TrashRevision;
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
 use std::{convert::TryInto, sync::Arc};
 
@@ -29,8 +27,8 @@ pub(crate) async fn delete_app_handler(
         .read_local_apps(vec![params.value])
         .await?
         .into_iter()
-        .map(|app| app.into())
-        .collect::<Vec<Trash>>();
+        .map(|app_rev| app_rev.into())
+        .collect::<Vec<TrashRevision>>();
 
     let _ = trash_controller.add(trash).await?;
     Ok(())
@@ -53,8 +51,8 @@ pub(crate) async fn read_app_handler(
     view_controller: AppData<Arc<ViewController>>,
 ) -> DataResult<App, FlowyError> {
     let params: AppId = data.into_inner();
-    let mut app = app_controller.read_app(params.clone()).await?;
-    app.belongings = view_controller.read_views_belong_to(&params.value).await?;
+    let mut app_rev = app_controller.read_app(params.clone()).await?;
+    app_rev.belongings = view_controller.read_views_belong_to(&params.value).await?;
 
-    data_result(app)
+    data_result(app_rev.into())
 }

+ 8 - 11
frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs

@@ -5,11 +5,8 @@ use crate::{
 };
 use flowy_database::kv::KV;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_folder_data_model::entities::{
-    app::{App, RepeatedApp},
-    view::{RepeatedView, View},
-    workspace::Workspace,
-};
+
+use flowy_folder_data_model::revision::{AppRevision, ViewRevision, WorkspaceRevision};
 use flowy_revision::disk::SQLiteTextBlockRevisionPersistence;
 use flowy_revision::{RevisionLoader, RevisionPersistence};
 use flowy_sync::{client_folder::FolderPad, entities::revision::md5};
@@ -42,25 +39,25 @@ impl FolderMigration {
         let workspaces = conn.immediate_transaction::<_, FlowyError, _>(|| {
             let mut workspaces = WorkspaceTableSql::read_workspaces(&self.user_id, None, conn)?
                 .into_iter()
-                .map(Workspace::from)
+                .map(WorkspaceRevision::from)
                 .collect::<Vec<_>>();
 
             for workspace in workspaces.iter_mut() {
                 let mut apps = AppTableSql::read_workspace_apps(&workspace.id, conn)?
                     .into_iter()
-                    .map(App::from)
+                    .map(AppRevision::from)
                     .collect::<Vec<_>>();
 
                 for app in apps.iter_mut() {
                     let views = ViewTableSql::read_views(&app.id, conn)?
                         .into_iter()
-                        .map(View::from)
+                        .map(ViewRevision::from)
                         .collect::<Vec<_>>();
 
-                    app.belongings = RepeatedView { items: views };
+                    app.belongings = views;
                 }
 
-                workspace.apps = RepeatedApp { items: apps };
+                workspace.apps = apps;
             }
             Ok(workspaces)
         })?;
@@ -72,7 +69,7 @@ impl FolderMigration {
         }
 
         let trash = conn.immediate_transaction::<_, FlowyError, _>(|| {
-            let trash = TrashTableSql::read_all(conn)?.take_items();
+            let trash = TrashTableSql::read_all(conn)?;
             Ok(trash)
         })?;
 

+ 13 - 17
frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs

@@ -9,12 +9,8 @@ use crate::{
 };
 use flowy_database::ConnectionPool;
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_folder_data_model::entities::{
-    app::App,
-    trash::{RepeatedTrash, Trash},
-    view::View,
-    workspace::Workspace,
-};
+
+use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
 use flowy_revision::disk::{RevisionRecord, RevisionState};
 use flowy_revision::mk_revision_disk_cache;
 use flowy_sync::client_folder::initial_folder_delta;
@@ -24,27 +20,27 @@ use tokio::sync::RwLock;
 pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*};
 
 pub trait FolderPersistenceTransaction {
-    fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()>;
-    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>>;
+    fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()>;
+    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<WorkspaceRevision>>;
     fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()>;
     fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()>;
 
-    fn create_app(&self, app: App) -> FlowyResult<()>;
+    fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()>;
     fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()>;
-    fn read_app(&self, app_id: &str) -> FlowyResult<App>;
-    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>>;
-    fn delete_app(&self, app_id: &str) -> FlowyResult<App>;
+    fn read_app(&self, app_id: &str) -> FlowyResult<AppRevision>;
+    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<AppRevision>>;
+    fn delete_app(&self, app_id: &str) -> FlowyResult<AppRevision>;
     fn move_app(&self, app_id: &str, from: usize, to: usize) -> FlowyResult<()>;
 
-    fn create_view(&self, view: View) -> FlowyResult<()>;
-    fn read_view(&self, view_id: &str) -> FlowyResult<View>;
-    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>>;
+    fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()>;
+    fn read_view(&self, view_id: &str) -> FlowyResult<ViewRevision>;
+    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<ViewRevision>>;
     fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()>;
     fn delete_view(&self, view_id: &str) -> FlowyResult<()>;
     fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()>;
 
-    fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()>;
-    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash>;
+    fn create_trash(&self, trashes: Vec<TrashRevision>) -> FlowyResult<()>;
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<Vec<TrashRevision>>;
     fn delete_trash(&self, trash_ids: Option<Vec<String>>) -> FlowyResult<()>;
 }
 

+ 14 - 14
frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs

@@ -1,7 +1,6 @@
 use crate::entities::{
-    app::{App, UpdateAppParams},
+    app::UpdateAppParams,
     trash::{Trash, TrashType},
-    view::RepeatedView,
 };
 use crate::{errors::FlowyError, services::persistence::version_1::workspace_sql::WorkspaceTable};
 use flowy_database::{
@@ -9,11 +8,12 @@ use flowy_database::{
     schema::{app_table, app_table::dsl},
     SqliteConnection,
 };
+use flowy_folder_data_model::revision::AppRevision;
 
 pub struct AppTableSql();
 impl AppTableSql {
-    pub(crate) fn create_app(app: App, conn: &SqliteConnection) -> Result<(), FlowyError> {
-        let app_table = AppTable::new(app);
+    pub(crate) fn create_app(app_rev: AppRevision, conn: &SqliteConnection) -> Result<(), FlowyError> {
+        let app_table = AppTable::new(app_rev);
         match diesel_record_count!(app_table, &app_table.id, conn) {
             0 => diesel_insert_table!(app_table, &app_table, conn),
             _ => {
@@ -91,16 +91,16 @@ pub(crate) struct AppTable {
 }
 
 impl AppTable {
-    pub fn new(app: App) -> Self {
+    pub fn new(app_rev: AppRevision) -> Self {
         Self {
-            id: app.id,
-            workspace_id: app.workspace_id,
-            name: app.name,
-            desc: app.desc,
+            id: app_rev.id,
+            workspace_id: app_rev.workspace_id,
+            name: app_rev.name,
+            desc: app_rev.desc,
             color_style: Default::default(),
             last_view_id: None,
-            modified_time: app.modified_time,
-            create_time: app.create_time,
+            modified_time: app_rev.modified_time,
+            create_time: app_rev.create_time,
             version: 0,
             is_trash: false,
         }
@@ -147,14 +147,14 @@ impl AppChangeset {
         }
     }
 }
-impl std::convert::From<AppTable> for App {
+impl std::convert::From<AppTable> for AppRevision {
     fn from(table: AppTable) -> Self {
-        App {
+        AppRevision {
             id: table.id,
             workspace_id: table.workspace_id,
             name: table.name,
             desc: table.desc,
-            belongings: RepeatedView::default(),
+            belongings: vec![],
             version: table.version,
             modified_time: table.modified_time,
             create_time: table.create_time,

+ 27 - 11
frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs

@@ -1,5 +1,5 @@
 use crate::{
-    entities::trash::{RepeatedTrash, Trash, TrashType},
+    entities::trash::{Trash, TrashType},
     errors::FlowyError,
 };
 use diesel::sql_types::Integer;
@@ -8,12 +8,13 @@ use flowy_database::{
     schema::{trash_table, trash_table::dsl},
     SqliteConnection,
 };
+use flowy_folder_data_model::revision::TrashRevision;
 
 pub struct TrashTableSql();
 impl TrashTableSql {
-    pub(crate) fn create_trash(trashes: Vec<Trash>, conn: &SqliteConnection) -> Result<(), FlowyError> {
-        for trash in trashes {
-            let trash_table: TrashTable = trash.into();
+    pub(crate) fn create_trash(trashes: Vec<TrashRevision>, conn: &SqliteConnection) -> Result<(), FlowyError> {
+        for trash_rev in trashes {
+            let trash_table: TrashTable = trash_rev.into();
             match diesel_record_count!(trash_table, &trash_table.id, conn) {
                 0 => diesel_insert_table!(trash_table, &trash_table, conn),
                 _ => {
@@ -26,10 +27,13 @@ impl TrashTableSql {
         Ok(())
     }
 
-    pub(crate) fn read_all(conn: &SqliteConnection) -> Result<RepeatedTrash, FlowyError> {
+    pub(crate) fn read_all(conn: &SqliteConnection) -> Result<Vec<TrashRevision>, FlowyError> {
         let trash_tables = dsl::trash_table.load::<TrashTable>(conn)?;
-        let items = trash_tables.into_iter().map(|t| t.into()).collect::<Vec<Trash>>();
-        Ok(RepeatedTrash { items })
+        let items = trash_tables
+            .into_iter()
+            .map(TrashRevision::from)
+            .collect::<Vec<TrashRevision>>();
+        Ok(items)
     }
 
     pub(crate) fn delete_all(conn: &SqliteConnection) -> Result<(), FlowyError> {
@@ -72,12 +76,11 @@ impl std::convert::From<TrashTable> for Trash {
     }
 }
 
-impl std::convert::From<Trash> for TrashTable {
-    fn from(trash: Trash) -> Self {
-        TrashTable {
+impl std::convert::From<TrashTable> for TrashRevision {
+    fn from(trash: TrashTable) -> Self {
+        TrashRevision {
             id: trash.id,
             name: trash.name,
-            desc: "".to_owned(),
             modified_time: trash.modified_time,
             create_time: trash.create_time,
             ty: trash.ty.into(),
@@ -85,6 +88,19 @@ impl std::convert::From<Trash> for TrashTable {
     }
 }
 
+impl std::convert::From<TrashRevision> for TrashTable {
+    fn from(trash_rev: TrashRevision) -> Self {
+        TrashTable {
+            id: trash_rev.id,
+            name: trash_rev.name,
+            desc: "".to_string(),
+            modified_time: trash_rev.modified_time,
+            create_time: trash_rev.create_time,
+            ty: trash_rev.ty.into(),
+        }
+    }
+}
+
 #[derive(AsChangeset, Identifiable, Clone, Default, Debug)]
 #[table_name = "trash_table"]
 pub(crate) struct TrashChangeset {

+ 40 - 47
frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs

@@ -8,24 +8,19 @@ use crate::services::persistence::{
 };
 use flowy_database::DBConnection;
 use flowy_error::FlowyResult;
-use flowy_folder_data_model::entities::{
-    app::App,
-    trash::{RepeatedTrash, Trash},
-    view::View,
-    workspace::Workspace,
-};
+use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
 
 pub struct V1Transaction<'a>(pub &'a DBConnection);
 
 impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
-    fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> {
-        let _ = WorkspaceTableSql::create_workspace(user_id, workspace, &*self.0)?;
+    fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> {
+        let _ = WorkspaceTableSql::create_workspace(user_id, workspace_rev, &*self.0)?;
         Ok(())
     }
 
-    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
+    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<WorkspaceRevision>> {
         let tables = WorkspaceTableSql::read_workspaces(user_id, workspace_id, &*self.0)?;
-        let workspaces = tables.into_iter().map(Workspace::from).collect::<Vec<_>>();
+        let workspaces = tables.into_iter().map(WorkspaceRevision::from).collect::<Vec<_>>();
         Ok(workspaces)
     }
 
@@ -37,8 +32,8 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
         WorkspaceTableSql::delete_workspace(workspace_id, &*self.0)
     }
 
-    fn create_app(&self, app: App) -> FlowyResult<()> {
-        let _ = AppTableSql::create_app(app, &*self.0)?;
+    fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> {
+        let _ = AppTableSql::create_app(app_rev, &*self.0)?;
         Ok(())
     }
 
@@ -47,39 +42,39 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
         Ok(())
     }
 
-    fn read_app(&self, app_id: &str) -> FlowyResult<App> {
-        let table = AppTableSql::read_app(app_id, &*self.0)?;
-        Ok(App::from(table))
+    fn read_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
+        let app_revision: AppRevision = AppTableSql::read_app(app_id, &*self.0)?.into();
+        Ok(app_revision)
     }
 
-    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
+    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<AppRevision>> {
         let tables = AppTableSql::read_workspace_apps(workspace_id, &*self.0)?;
-        let apps = tables.into_iter().map(App::from).collect::<Vec<_>>();
+        let apps = tables.into_iter().map(AppRevision::from).collect::<Vec<_>>();
         Ok(apps)
     }
 
-    fn delete_app(&self, app_id: &str) -> FlowyResult<App> {
-        let table = AppTableSql::delete_app(app_id, &*self.0)?;
-        Ok(App::from(table))
+    fn delete_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
+        let app_revision: AppRevision = AppTableSql::delete_app(app_id, &*self.0)?.into();
+        Ok(app_revision)
     }
 
     fn move_app(&self, _app_id: &str, _from: usize, _to: usize) -> FlowyResult<()> {
         Ok(())
     }
 
-    fn create_view(&self, view: View) -> FlowyResult<()> {
-        let _ = ViewTableSql::create_view(view, &*self.0)?;
+    fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> {
+        let _ = ViewTableSql::create_view(view_rev, &*self.0)?;
         Ok(())
     }
 
-    fn read_view(&self, view_id: &str) -> FlowyResult<View> {
-        let table = ViewTableSql::read_view(view_id, &*self.0)?;
-        Ok(View::from(table))
+    fn read_view(&self, view_id: &str) -> FlowyResult<ViewRevision> {
+        let view_revision: ViewRevision = ViewTableSql::read_view(view_id, &*self.0)?.into();
+        Ok(view_revision)
     }
 
-    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>> {
+    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<ViewRevision>> {
         let tables = ViewTableSql::read_views(belong_to_id, &*self.0)?;
-        let views = tables.into_iter().map(View::from).collect::<Vec<_>>();
+        let views = tables.into_iter().map(ViewRevision::from).collect::<Vec<_>>();
         Ok(views)
     }
 
@@ -97,19 +92,17 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> {
         Ok(())
     }
 
-    fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()> {
+    fn create_trash(&self, trashes: Vec<TrashRevision>) -> FlowyResult<()> {
         let _ = TrashTableSql::create_trash(trashes, &*self.0)?;
         Ok(())
     }
 
-    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash> {
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<Vec<TrashRevision>> {
         match trash_id {
             None => TrashTableSql::read_all(&*self.0),
             Some(trash_id) => {
-                let table = TrashTableSql::read(&trash_id, &*self.0)?;
-                Ok(RepeatedTrash {
-                    items: vec![Trash::from(table)],
-                })
+                let trash_revision: TrashRevision = TrashTableSql::read(&trash_id, &*self.0)?.into();
+                Ok(vec![trash_revision])
             }
         }
     }
@@ -132,11 +125,11 @@ impl<T> FolderPersistenceTransaction for Box<T>
 where
     T: FolderPersistenceTransaction + ?Sized,
 {
-    fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> {
-        (**self).create_workspace(user_id, workspace)
+    fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> {
+        (**self).create_workspace(user_id, workspace_rev)
     }
 
-    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
+    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<WorkspaceRevision>> {
         (**self).read_workspaces(user_id, workspace_id)
     }
 
@@ -148,23 +141,23 @@ where
         (**self).delete_workspace(workspace_id)
     }
 
-    fn create_app(&self, app: App) -> FlowyResult<()> {
-        (**self).create_app(app)
+    fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> {
+        (**self).create_app(app_rev)
     }
 
     fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> {
         (**self).update_app(changeset)
     }
 
-    fn read_app(&self, app_id: &str) -> FlowyResult<App> {
+    fn read_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
         (**self).read_app(app_id)
     }
 
-    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
+    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<AppRevision>> {
         (**self).read_workspace_apps(workspace_id)
     }
 
-    fn delete_app(&self, app_id: &str) -> FlowyResult<App> {
+    fn delete_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
         (**self).delete_app(app_id)
     }
 
@@ -172,15 +165,15 @@ where
         Ok(())
     }
 
-    fn create_view(&self, view: View) -> FlowyResult<()> {
-        (**self).create_view(view)
+    fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> {
+        (**self).create_view(view_rev)
     }
 
-    fn read_view(&self, view_id: &str) -> FlowyResult<View> {
+    fn read_view(&self, view_id: &str) -> FlowyResult<ViewRevision> {
         (**self).read_view(view_id)
     }
 
-    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>> {
+    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<ViewRevision>> {
         (**self).read_views(belong_to_id)
     }
 
@@ -196,11 +189,11 @@ where
         Ok(())
     }
 
-    fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()> {
+    fn create_trash(&self, trashes: Vec<TrashRevision>) -> FlowyResult<()> {
         (**self).create_trash(trashes)
     }
 
-    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash> {
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<Vec<TrashRevision>> {
         (**self).read_trash(trash_id)
     }
 

+ 21 - 19
frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs

@@ -1,7 +1,7 @@
 use crate::{
     entities::{
         trash::{Trash, TrashType},
-        view::{RepeatedView, UpdateViewParams, View, ViewDataType},
+        view::{UpdateViewParams, ViewDataType},
     },
     errors::FlowyError,
     services::persistence::version_1::app_sql::AppTable,
@@ -12,12 +12,14 @@ use flowy_database::{
     schema::{view_table, view_table::dsl},
     SqliteConnection,
 };
+
+use flowy_folder_data_model::revision::ViewRevision;
 use lib_infra::util::timestamp;
 
 pub struct ViewTableSql();
 impl ViewTableSql {
-    pub(crate) fn create_view(view: View, conn: &SqliteConnection) -> Result<(), FlowyError> {
-        let view_table = ViewTable::new(view);
+    pub(crate) fn create_view(view_rev: ViewRevision, conn: &SqliteConnection) -> Result<(), FlowyError> {
+        let view_table = ViewTable::new(view_rev);
         match diesel_record_count!(view_table, &view_table.id, conn) {
             0 => diesel_insert_table!(view_table, &view_table, conn),
             _ => {
@@ -83,49 +85,49 @@ pub(crate) struct ViewTable {
 }
 
 impl ViewTable {
-    pub fn new(view: View) -> Self {
-        let data_type = match view.data_type {
+    pub fn new(view_rev: ViewRevision) -> Self {
+        let data_type = match view_rev.data_type {
             ViewDataType::TextBlock => SqlViewDataType::Block,
             ViewDataType::Grid => SqlViewDataType::Grid,
         };
 
         ViewTable {
-            id: view.id,
-            belong_to_id: view.belong_to_id,
-            name: view.name,
-            desc: view.desc,
-            modified_time: view.modified_time,
-            create_time: view.create_time,
-            thumbnail: view.thumbnail,
+            id: view_rev.id,
+            belong_to_id: view_rev.belong_to_id,
+            name: view_rev.name,
+            desc: view_rev.desc,
+            modified_time: view_rev.modified_time,
+            create_time: view_rev.create_time,
+            thumbnail: view_rev.thumbnail,
             view_type: data_type,
-            ext_data: view.ext_data,
-            version: 0,
+            ext_data: view_rev.ext_data,
+            version: view_rev.version,
             is_trash: false,
         }
     }
 }
 
-impl std::convert::From<ViewTable> for View {
+impl std::convert::From<ViewTable> for ViewRevision {
     fn from(table: ViewTable) -> Self {
         let data_type = match table.view_type {
             SqlViewDataType::Block => ViewDataType::TextBlock,
             SqlViewDataType::Grid => ViewDataType::Grid,
         };
 
-        View {
+        ViewRevision {
             id: table.id,
             belong_to_id: table.belong_to_id,
             name: table.name,
             desc: table.desc,
             data_type,
-            belongings: RepeatedView::default(),
+            belongings: vec![],
             modified_time: table.modified_time,
             version: table.version,
             create_time: table.create_time,
-            ext_data: table.ext_data,
+            ext_data: "".to_string(),
             thumbnail: table.thumbnail,
             // Store the view in ViewTable was deprecated since v0.0.2.
-            // No need worry about plugin_type.
+            // No need to worry about plugin_type.
             plugin_type: 0,
         }
     }

+ 14 - 18
frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs

@@ -1,23 +1,19 @@
-use crate::{
-    entities::{
-        app::RepeatedApp,
-        workspace::{UpdateWorkspaceParams, Workspace},
-    },
-    errors::FlowyError,
-};
+use crate::{entities::workspace::UpdateWorkspaceParams, errors::FlowyError};
 use diesel::SqliteConnection;
 use flowy_database::{
     prelude::*,
     schema::{workspace_table, workspace_table::dsl},
 };
+use flowy_folder_data_model::revision::WorkspaceRevision;
+
 pub(crate) struct WorkspaceTableSql();
 impl WorkspaceTableSql {
     pub(crate) fn create_workspace(
         user_id: &str,
-        workspace: Workspace,
+        workspace_rev: WorkspaceRevision,
         conn: &SqliteConnection,
     ) -> Result<(), FlowyError> {
-        let table = WorkspaceTable::new(workspace, user_id);
+        let table = WorkspaceTable::new(workspace_rev, user_id);
         match diesel_record_count!(workspace_table, &table.id, conn) {
             0 => diesel_insert_table!(workspace_table, &table, conn),
             _ => {
@@ -74,26 +70,26 @@ pub struct WorkspaceTable {
 
 impl WorkspaceTable {
     #[allow(dead_code)]
-    pub fn new(workspace: Workspace, user_id: &str) -> Self {
+    pub fn new(workspace_rev: WorkspaceRevision, user_id: &str) -> Self {
         WorkspaceTable {
-            id: workspace.id,
-            name: workspace.name,
-            desc: workspace.desc,
-            modified_time: workspace.modified_time,
-            create_time: workspace.create_time,
+            id: workspace_rev.id,
+            name: workspace_rev.name,
+            desc: workspace_rev.desc,
+            modified_time: workspace_rev.modified_time,
+            create_time: workspace_rev.create_time,
             user_id: user_id.to_owned(),
             version: 0,
         }
     }
 }
 
-impl std::convert::From<WorkspaceTable> for Workspace {
+impl std::convert::From<WorkspaceTable> for WorkspaceRevision {
     fn from(table: WorkspaceTable) -> Self {
-        Workspace {
+        WorkspaceRevision {
             id: table.id,
             name: table.name,
             desc: table.desc,
-            apps: RepeatedApp::default(),
+            apps: vec![],
             modified_time: table.modified_time,
             create_time: table.create_time,
         }

+ 32 - 36
frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs

@@ -3,23 +3,19 @@ use crate::services::{
     persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset},
 };
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_folder_data_model::entities::{
-    app::App,
-    trash::{RepeatedTrash, Trash},
-    view::View,
-    workspace::Workspace,
-};
+
+use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
 use std::sync::Arc;
 
 impl FolderPersistenceTransaction for FolderEditor {
-    fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> {
-        if let Some(change) = self.folder.write().create_workspace(workspace)? {
+    fn create_workspace(&self, _user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> {
+        if let Some(change) = self.folder.write().create_workspace(workspace_rev)? {
             let _ = self.apply_change(change)?;
         }
         Ok(())
     }
 
-    fn read_workspaces(&self, _user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
+    fn read_workspaces(&self, _user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<WorkspaceRevision>> {
         let workspaces = self.folder.read().read_workspaces(workspace_id)?;
         Ok(workspaces)
     }
@@ -42,8 +38,8 @@ impl FolderPersistenceTransaction for FolderEditor {
         Ok(())
     }
 
-    fn create_app(&self, app: App) -> FlowyResult<()> {
-        if let Some(change) = self.folder.write().create_app(app)? {
+    fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> {
+        if let Some(change) = self.folder.write().create_app(app_rev)? {
             let _ = self.apply_change(change)?;
         }
         Ok(())
@@ -60,22 +56,22 @@ impl FolderPersistenceTransaction for FolderEditor {
         Ok(())
     }
 
-    fn read_app(&self, app_id: &str) -> FlowyResult<App> {
+    fn read_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
         let app = self.folder.read().read_app(app_id)?;
         Ok(app)
     }
 
-    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
+    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<AppRevision>> {
         let workspaces = self.folder.read().read_workspaces(Some(workspace_id.to_owned()))?;
         match workspaces.first() {
             None => {
                 Err(FlowyError::record_not_found().context(format!("can't find workspace with id {}", workspace_id)))
             }
-            Some(workspace) => Ok(workspace.apps.clone().take_items()),
+            Some(workspace) => Ok(workspace.apps.clone()),
         }
     }
 
-    fn delete_app(&self, app_id: &str) -> FlowyResult<App> {
+    fn delete_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
         let app = self.folder.read().read_app(app_id)?;
         if let Some(change) = self.folder.write().delete_app(app_id)? {
             let _ = self.apply_change(change)?;
@@ -90,19 +86,19 @@ impl FolderPersistenceTransaction for FolderEditor {
         Ok(())
     }
 
-    fn create_view(&self, view: View) -> FlowyResult<()> {
-        if let Some(change) = self.folder.write().create_view(view)? {
+    fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> {
+        if let Some(change) = self.folder.write().create_view(view_rev)? {
             let _ = self.apply_change(change)?;
         }
         Ok(())
     }
 
-    fn read_view(&self, view_id: &str) -> FlowyResult<View> {
+    fn read_view(&self, view_id: &str) -> FlowyResult<ViewRevision> {
         let view = self.folder.read().read_view(view_id)?;
         Ok(view)
     }
 
-    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>> {
+    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<ViewRevision>> {
         let views = self.folder.read().read_views(belong_to_id)?;
         Ok(views)
     }
@@ -132,16 +128,16 @@ impl FolderPersistenceTransaction for FolderEditor {
         Ok(())
     }
 
-    fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()> {
+    fn create_trash(&self, trashes: Vec<TrashRevision>) -> FlowyResult<()> {
         if let Some(change) = self.folder.write().create_trash(trashes)? {
             let _ = self.apply_change(change)?;
         }
         Ok(())
     }
 
-    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash> {
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<Vec<TrashRevision>> {
         let trash = self.folder.read().read_trash(trash_id)?;
-        Ok(RepeatedTrash { items: trash })
+        Ok(trash)
     }
 
     fn delete_trash(&self, trash_ids: Option<Vec<String>>) -> FlowyResult<()> {
@@ -156,11 +152,11 @@ impl<T> FolderPersistenceTransaction for Arc<T>
 where
     T: FolderPersistenceTransaction + ?Sized,
 {
-    fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> {
-        (**self).create_workspace(user_id, workspace)
+    fn create_workspace(&self, user_id: &str, workspace_rev: WorkspaceRevision) -> FlowyResult<()> {
+        (**self).create_workspace(user_id, workspace_rev)
     }
 
-    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<Workspace>> {
+    fn read_workspaces(&self, user_id: &str, workspace_id: Option<String>) -> FlowyResult<Vec<WorkspaceRevision>> {
         (**self).read_workspaces(user_id, workspace_id)
     }
 
@@ -172,23 +168,23 @@ where
         (**self).delete_workspace(workspace_id)
     }
 
-    fn create_app(&self, app: App) -> FlowyResult<()> {
-        (**self).create_app(app)
+    fn create_app(&self, app_rev: AppRevision) -> FlowyResult<()> {
+        (**self).create_app(app_rev)
     }
 
     fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> {
         (**self).update_app(changeset)
     }
 
-    fn read_app(&self, app_id: &str) -> FlowyResult<App> {
+    fn read_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
         (**self).read_app(app_id)
     }
 
-    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
+    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<AppRevision>> {
         (**self).read_workspace_apps(workspace_id)
     }
 
-    fn delete_app(&self, app_id: &str) -> FlowyResult<App> {
+    fn delete_app(&self, app_id: &str) -> FlowyResult<AppRevision> {
         (**self).delete_app(app_id)
     }
 
@@ -196,15 +192,15 @@ where
         (**self).move_app(app_id, from, to)
     }
 
-    fn create_view(&self, view: View) -> FlowyResult<()> {
-        (**self).create_view(view)
+    fn create_view(&self, view_rev: ViewRevision) -> FlowyResult<()> {
+        (**self).create_view(view_rev)
     }
 
-    fn read_view(&self, view_id: &str) -> FlowyResult<View> {
+    fn read_view(&self, view_id: &str) -> FlowyResult<ViewRevision> {
         (**self).read_view(view_id)
     }
 
-    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>> {
+    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<ViewRevision>> {
         (**self).read_views(belong_to_id)
     }
 
@@ -220,11 +216,11 @@ where
         (**self).move_view(view_id, from, to)
     }
 
-    fn create_trash(&self, trashes: Vec<Trash>) -> FlowyResult<()> {
+    fn create_trash(&self, trashes: Vec<TrashRevision>) -> FlowyResult<()> {
         (**self).create_trash(trashes)
     }
 
-    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash> {
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<Vec<TrashRevision>> {
         (**self).read_trash(trash_id)
     }
 

+ 33 - 25
frontend/rust-lib/flowy-folder/src/services/trash/controller.rs

@@ -6,6 +6,7 @@ use crate::{
     services::persistence::{FolderPersistence, FolderPersistenceTransaction},
 };
 
+use flowy_folder_data_model::revision::TrashRevision;
 use std::{fmt::Formatter, sync::Arc};
 use tokio::sync::{broadcast, mpsc};
 
@@ -66,18 +67,18 @@ impl TrashController {
 
     #[tracing::instrument(level = "debug", skip(self)  err)]
     pub async fn restore_all_trash(&self) -> FlowyResult<()> {
-        let repeated_trash = self
+        let trash_identifier: RepeatedTrashId = self
             .persistence
             .begin_transaction(|transaction| {
                 let trash = transaction.read_trash(None);
                 let _ = transaction.delete_trash(None);
                 trash
             })
-            .await?;
+            .await?
+            .into();
 
-        let identifiers: RepeatedTrashId = repeated_trash.items.clone().into();
         let (tx, mut rx) = mpsc::channel::<FlowyResult<()>>(1);
-        let _ = self.notify.send(TrashEvent::Putback(identifiers, tx));
+        let _ = self.notify.send(TrashEvent::Putback(trash_identifier, tx));
         let _ = rx.recv().await;
 
         notify_trash_changed(RepeatedTrash { items: vec![] });
@@ -87,12 +88,13 @@ impl TrashController {
 
     #[tracing::instrument(level = "debug", skip(self), err)]
     pub async fn delete_all_trash(&self) -> FlowyResult<()> {
-        let repeated_trash = self
+        let all_trash_identifiers: RepeatedTrashId = self
             .persistence
             .begin_transaction(|transaction| transaction.read_trash(None))
-            .await?;
-        let trash_identifiers: RepeatedTrashId = repeated_trash.items.clone().into();
-        let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?;
+            .await?
+            .into();
+
+        let _ = self.delete_with_identifiers(all_trash_identifiers).await?;
 
         notify_trash_changed(RepeatedTrash { items: vec![] });
         let _ = self.delete_all_trash_on_server().await?;
@@ -102,11 +104,12 @@ impl TrashController {
     #[tracing::instrument(level = "debug", skip(self), err)]
     pub async fn delete(&self, trash_identifiers: RepeatedTrashId) -> FlowyResult<()> {
         let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?;
-        let repeated_trash = self
+        let trash_revs = self
             .persistence
             .begin_transaction(|transaction| transaction.read_trash(None))
             .await?;
-        notify_trash_changed(repeated_trash);
+
+        notify_trash_changed(trash_revs);
         let _ = self.delete_trash_on_server(trash_identifiers)?;
 
         Ok(())
@@ -147,10 +150,10 @@ impl TrashController {
     // CREATE and DROP tables operations because those are auto-commit in the
     // database.
     #[tracing::instrument(name = "add_trash", level = "debug", skip(self, trash), fields(trash_ids), err)]
-    pub async fn add<T: Into<Trash>>(&self, trash: Vec<T>) -> Result<(), FlowyError> {
+    pub async fn add<T: Into<TrashRevision>>(&self, trash: Vec<T>) -> Result<(), FlowyError> {
         let (tx, mut rx) = mpsc::channel::<FlowyResult<()>>(1);
-        let repeated_trash = trash.into_iter().map(|t| t.into()).collect::<Vec<Trash>>();
-        let identifiers = repeated_trash.iter().map(|t| t.into()).collect::<Vec<TrashId>>();
+        let trash_revs: Vec<TrashRevision> = trash.into_iter().map(|t| t.into()).collect();
+        let identifiers = trash_revs.iter().map(|t| t.into()).collect::<Vec<TrashId>>();
 
         tracing::Span::current().record(
             "trash_ids",
@@ -167,8 +170,9 @@ impl TrashController {
         let _ = self
             .persistence
             .begin_transaction(|transaction| {
-                let _ = transaction.create_trash(repeated_trash.clone())?;
-                let _ = self.create_trash_on_server(repeated_trash);
+                let _ = transaction.create_trash(trash_revs.clone())?;
+                let _ = self.create_trash_on_server(trash_revs);
+
                 notify_trash_changed(transaction.read_trash(None)?);
                 Ok(())
             })
@@ -184,12 +188,16 @@ impl TrashController {
     }
 
     pub async fn read_trash(&self) -> Result<RepeatedTrash, FlowyError> {
-        let repeated_trash = self
+        let items: Vec<Trash> = self
             .persistence
             .begin_transaction(|transaction| transaction.read_trash(None))
-            .await?;
+            .await?
+            .into_iter()
+            .map(|trash_rev| trash_rev.into())
+            .collect();
+
         let _ = self.read_trash_on_server()?;
-        Ok(repeated_trash)
+        Ok(RepeatedTrash { items })
     }
 
     pub fn read_trash_ids<'a>(
@@ -198,7 +206,6 @@ impl TrashController {
     ) -> Result<Vec<String>, FlowyError> {
         let ids = transaction
             .read_trash(None)?
-            .into_inner()
             .into_iter()
             .map(|item| item.id)
             .collect::<Vec<String>>();
@@ -244,18 +251,18 @@ impl TrashController {
 
         tokio::spawn(async move {
             match server.read_trash(&token).await {
-                Ok(repeated_trash) => {
-                    tracing::debug!("Remote trash count: {}", repeated_trash.items.len());
+                Ok(trash_rev) => {
+                    tracing::debug!("Remote trash count: {}", trash_rev.len());
                     let result = persistence
                         .begin_transaction(|transaction| {
-                            let _ = transaction.create_trash(repeated_trash.items.clone())?;
+                            let _ = transaction.create_trash(trash_rev.clone())?;
                             transaction.read_trash(None)
                         })
                         .await;
 
                     match result {
-                        Ok(repeated_trash) => {
-                            notify_trash_changed(repeated_trash);
+                        Ok(trash_revs) => {
+                            notify_trash_changed(trash_revs);
                         }
                         Err(e) => log::error!("Save trash failed: {:?}", e),
                     }
@@ -275,7 +282,8 @@ impl TrashController {
 }
 
 #[tracing::instrument(level = "debug", skip(repeated_trash), fields(n_trash))]
-fn notify_trash_changed(repeated_trash: RepeatedTrash) {
+fn notify_trash_changed<T: Into<RepeatedTrash>>(repeated_trash: T) {
+    let repeated_trash = repeated_trash.into();
     tracing::Span::current().record("n_trash", &repeated_trash.len());
     send_anonymous_dart_notification(FolderNotification::TrashUpdated)
         .payload(repeated_trash)

+ 65 - 55
frontend/rust-lib/flowy-folder/src/services/view/controller.rs

@@ -15,6 +15,7 @@ use crate::{
 use bytes::Bytes;
 use flowy_database::kv::KV;
 use flowy_folder_data_model::entities::view::{gen_view_id, ViewDataType};
+use flowy_folder_data_model::revision::ViewRevision;
 use flowy_sync::entities::text_block_info::TextBlockId;
 use futures::{FutureExt, StreamExt};
 use std::{collections::HashSet, sync::Arc};
@@ -52,7 +53,10 @@ impl ViewController {
     }
 
     #[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)]
-    pub(crate) async fn create_view_from_params(&self, mut params: CreateViewParams) -> Result<View, FlowyError> {
+    pub(crate) async fn create_view_from_params(
+        &self,
+        mut params: CreateViewParams,
+    ) -> Result<ViewRevision, FlowyError> {
         let processor = self.get_data_processor(&params.data_type)?;
         let user_id = self.user.user_id()?;
         if params.data.is_empty() {
@@ -67,9 +71,9 @@ impl ViewController {
                 .await?;
         };
 
-        let view = self.create_view_on_server(params).await?;
-        let _ = self.create_view_on_local(view.clone()).await?;
-        Ok(view)
+        let view_rev = self.create_view_on_server(params).await?;
+        let _ = self.create_view_on_local(view_rev.clone()).await?;
+        Ok(view_rev)
     }
 
     #[tracing::instrument(level = "debug", skip(self, view_id, delta_data), err)]
@@ -88,12 +92,12 @@ impl ViewController {
         Ok(())
     }
 
-    pub(crate) async fn create_view_on_local(&self, view: View) -> Result<(), FlowyError> {
+    pub(crate) async fn create_view_on_local(&self, view_rev: ViewRevision) -> Result<(), FlowyError> {
         let trash_controller = self.trash_controller.clone();
         self.persistence
             .begin_transaction(|transaction| {
-                let belong_to_id = view.belong_to_id.clone();
-                let _ = transaction.create_view(view)?;
+                let belong_to_id = view_rev.belong_to_id.clone();
+                let _ = transaction.create_view(view_rev)?;
                 let _ = notify_views_changed(&belong_to_id, trash_controller, &transaction)?;
                 Ok(())
             })
@@ -101,8 +105,8 @@ impl ViewController {
     }
 
     #[tracing::instrument(level = "debug", skip(self, view_id), fields(view_id = %view_id.value), err)]
-    pub(crate) async fn read_view(&self, view_id: ViewId) -> Result<View, FlowyError> {
-        let view = self
+    pub(crate) async fn read_view(&self, view_id: ViewId) -> Result<ViewRevision, FlowyError> {
+        let view_rev = self
             .persistence
             .begin_transaction(|transaction| {
                 let view = transaction.read_view(&view_id.value)?;
@@ -114,10 +118,10 @@ impl ViewController {
             })
             .await?;
         let _ = self.read_view_on_server(view_id);
-        Ok(view)
+        Ok(view_rev)
     }
 
-    pub(crate) async fn read_local_views(&self, ids: Vec<String>) -> Result<Vec<View>, FlowyError> {
+    pub(crate) async fn read_local_views(&self, ids: Vec<String>) -> Result<Vec<ViewRevision>, FlowyError> {
         self.persistence
             .begin_transaction(|transaction| {
                 let mut views = vec![];
@@ -170,22 +174,22 @@ impl ViewController {
 
     #[tracing::instrument(level = "debug", skip(self), err)]
     pub(crate) async fn duplicate_view(&self, view_id: &str) -> Result<(), FlowyError> {
-        let view = self
+        let view_rev = self
             .persistence
             .begin_transaction(|transaction| transaction.read_view(view_id))
             .await?;
 
-        let processor = self.get_data_processor(&view.data_type)?;
+        let processor = self.get_data_processor(&view_rev.data_type)?;
         let delta_bytes = processor.view_delta_data(view_id).await?;
         let duplicate_params = CreateViewParams {
-            belong_to_id: view.belong_to_id.clone(),
-            name: format!("{} (copy)", &view.name),
-            desc: view.desc,
-            thumbnail: view.thumbnail,
-            data_type: view.data_type,
+            belong_to_id: view_rev.belong_to_id.clone(),
+            name: format!("{} (copy)", &view_rev.name),
+            desc: view_rev.desc,
+            thumbnail: view_rev.thumbnail,
+            data_type: view_rev.data_type,
             data: delta_bytes.to_vec(),
             view_id: gen_view_id(),
-            plugin_type: view.plugin_type,
+            plugin_type: view_rev.plugin_type,
         };
 
         let _ = self.create_view_from_params(duplicate_params).await?;
@@ -194,7 +198,7 @@ impl ViewController {
 
     // belong_to_id will be the app_id or view_id.
     #[tracing::instrument(level = "trace", skip(self), err)]
-    pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, FlowyError> {
+    pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<Vec<ViewRevision>, FlowyError> {
         self.persistence
             .begin_transaction(|transaction| {
                 read_belonging_views_on_local(belong_to_id, self.trash_controller.clone(), &transaction)
@@ -203,35 +207,36 @@ impl ViewController {
     }
 
     #[tracing::instrument(level = "debug", skip(self, params), err)]
-    pub(crate) async fn update_view(&self, params: UpdateViewParams) -> Result<View, FlowyError> {
+    pub(crate) async fn update_view(&self, params: UpdateViewParams) -> Result<ViewRevision, FlowyError> {
         let changeset = ViewChangeset::new(params.clone());
         let view_id = changeset.id.clone();
-        let view = self
+        let view_rev = self
             .persistence
             .begin_transaction(|transaction| {
                 let _ = transaction.update_view(changeset)?;
-                let view = transaction.read_view(&view_id)?;
+                let view_rev = transaction.read_view(&view_id)?;
+                let view: View = view_rev.clone().into();
                 send_dart_notification(&view_id, FolderNotification::ViewUpdated)
-                    .payload(view.clone())
+                    .payload(view)
                     .send();
-                let _ = notify_views_changed(&view.belong_to_id, self.trash_controller.clone(), &transaction)?;
-                Ok(view)
+                let _ = notify_views_changed(&view_rev.belong_to_id, self.trash_controller.clone(), &transaction)?;
+                Ok(view_rev)
             })
             .await?;
 
         let _ = self.update_view_on_server(params);
-        Ok(view)
+        Ok(view_rev)
     }
 
-    pub(crate) async fn latest_visit_view(&self) -> FlowyResult<Option<View>> {
+    pub(crate) async fn latest_visit_view(&self) -> FlowyResult<Option<ViewRevision>> {
         match KV::get_str(LATEST_VIEW_ID) {
             None => Ok(None),
             Some(view_id) => {
-                let view = self
+                let view_rev = self
                     .persistence
                     .begin_transaction(|transaction| transaction.read_view(&view_id))
                     .await?;
-                Ok(Some(view))
+                Ok(Some(view_rev))
             }
         }
     }
@@ -239,10 +244,10 @@ impl ViewController {
 
 impl ViewController {
     #[tracing::instrument(level = "debug", skip(self, params), err)]
-    async fn create_view_on_server(&self, params: CreateViewParams) -> Result<View, FlowyError> {
+    async fn create_view_on_server(&self, params: CreateViewParams) -> Result<ViewRevision, FlowyError> {
         let token = self.user.token()?;
-        let view = self.cloud_service.create_view(&token, params).await?;
-        Ok(view)
+        let view_rev = self.cloud_service.create_view(&token, params).await?;
+        Ok(view_rev)
     }
 
     #[tracing::instrument(level = "debug", skip(self), err)]
@@ -269,14 +274,15 @@ impl ViewController {
         // TODO: Retry with RetryAction?
         tokio::spawn(async move {
             match server.read_view(&token, params).await {
-                Ok(Some(view)) => {
+                Ok(Some(view_rev)) => {
                     match persistence
-                        .begin_transaction(|transaction| transaction.create_view(view.clone()))
+                        .begin_transaction(|transaction| transaction.create_view(view_rev.clone()))
                         .await
                     {
                         Ok(_) => {
+                            let view: View = view_rev.into();
                             send_dart_notification(&view.id, FolderNotification::ViewUpdated)
-                                .payload(view.clone())
+                                .payload(view)
                                 .send();
                         }
                         Err(e) => log::error!("Save view failed: {:?}", e),
@@ -350,10 +356,10 @@ async fn handle_trash_event(
         TrashEvent::NewTrash(identifiers, ret) => {
             let result = persistence
                 .begin_transaction(|transaction| {
-                    let views = read_local_views_with_transaction(identifiers, &transaction)?;
-                    for view in views {
-                        let _ = notify_views_changed(&view.belong_to_id, trash_can.clone(), &transaction)?;
-                        notify_dart(view, FolderNotification::ViewDeleted);
+                    let view_revs = read_local_views_with_transaction(identifiers, &transaction)?;
+                    for view_rev in view_revs {
+                        let _ = notify_views_changed(&view_rev.belong_to_id, trash_can.clone(), &transaction)?;
+                        notify_dart(view_rev.into(), FolderNotification::ViewDeleted);
                     }
                     Ok(())
                 })
@@ -363,10 +369,10 @@ async fn handle_trash_event(
         TrashEvent::Putback(identifiers, ret) => {
             let result = persistence
                 .begin_transaction(|transaction| {
-                    let views = read_local_views_with_transaction(identifiers, &transaction)?;
-                    for view in views {
-                        let _ = notify_views_changed(&view.belong_to_id, trash_can.clone(), &transaction)?;
-                        notify_dart(view, FolderNotification::ViewRestored);
+                    let view_revs = read_local_views_with_transaction(identifiers, &transaction)?;
+                    for view_rev in view_revs {
+                        let _ = notify_views_changed(&view_rev.belong_to_id, trash_can.clone(), &transaction)?;
+                        notify_dart(view_rev.into(), FolderNotification::ViewRestored);
                     }
                     Ok(())
                 })
@@ -425,13 +431,12 @@ fn get_data_processor(
 fn read_local_views_with_transaction<'a>(
     identifiers: RepeatedTrashId,
     transaction: &'a (dyn FolderPersistenceTransaction + 'a),
-) -> Result<Vec<View>, FlowyError> {
-    let mut views = vec![];
+) -> Result<Vec<ViewRevision>, FlowyError> {
+    let mut view_revs = vec![];
     for identifier in identifiers.items {
-        let view = transaction.read_view(&identifier.id)?;
-        views.push(view);
+        view_revs.push(transaction.read_view(&identifier.id)?);
     }
-    Ok(views)
+    Ok(view_revs)
 }
 
 fn notify_dart(view: View, notification: FolderNotification) {
@@ -449,8 +454,13 @@ fn notify_views_changed<'a>(
     trash_controller: Arc<TrashController>,
     transaction: &'a (dyn FolderPersistenceTransaction + 'a),
 ) -> FlowyResult<()> {
-    let repeated_view = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)?;
-    tracing::Span::current().record("view_count", &format!("{}", repeated_view.len()).as_str());
+    let items: Vec<View> = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)?
+        .into_iter()
+        .map(|view_rev| view_rev.into())
+        .collect();
+    tracing::Span::current().record("view_count", &format!("{}", items.len()).as_str());
+
+    let repeated_view = RepeatedView { items };
     send_dart_notification(belong_to_id, FolderNotification::AppViewsChanged)
         .payload(repeated_view)
         .send();
@@ -461,10 +471,10 @@ fn read_belonging_views_on_local<'a>(
     belong_to_id: &str,
     trash_controller: Arc<TrashController>,
     transaction: &'a (dyn FolderPersistenceTransaction + 'a),
-) -> FlowyResult<RepeatedView> {
-    let mut views = transaction.read_views(belong_to_id)?;
+) -> FlowyResult<Vec<ViewRevision>> {
+    let mut view_revs = transaction.read_views(belong_to_id)?;
     let trash_ids = trash_controller.read_trash_ids(transaction)?;
-    views.retain(|view_table| !trash_ids.contains(&view_table.id));
+    view_revs.retain(|view_table| !trash_ids.contains(&view_table.id));
 
-    Ok(RepeatedView { items: views })
+    Ok(view_revs)
 }

+ 18 - 7
frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs

@@ -11,6 +11,8 @@ use crate::{
     services::{TrashController, ViewController},
 };
 use flowy_folder_data_model::entities::view::{MoveFolderItemParams, MoveFolderItemPayload, MoveFolderItemType};
+use flowy_folder_data_model::entities::ViewInfo;
+use flowy_folder_data_model::revision::TrashRevision;
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
 use std::{convert::TryInto, sync::Arc};
 
@@ -19,8 +21,8 @@ pub(crate) async fn create_view_handler(
     controller: AppData<Arc<ViewController>>,
 ) -> DataResult<View, FlowyError> {
     let params: CreateViewParams = data.into_inner().try_into()?;
-    let view = controller.create_view_from_params(params).await?;
-    data_result(view)
+    let view_rev = controller.create_view_from_params(params).await?;
+    data_result(view_rev.into())
 }
 
 pub(crate) async fn read_view_handler(
@@ -28,12 +30,18 @@ pub(crate) async fn read_view_handler(
     controller: AppData<Arc<ViewController>>,
 ) -> DataResult<View, FlowyError> {
     let view_id: ViewId = data.into_inner();
-    let mut view = controller.read_view(view_id.clone()).await?;
+    let view_rev = controller.read_view(view_id.clone()).await?;
+    data_result(view_rev.into())
+}
+
+pub(crate) async fn read_view_info_handler(
+    _data: Data<ViewId>,
+    _controller: AppData<Arc<ViewController>>,
+) -> DataResult<ViewInfo, FlowyError> {
     // For the moment, app and view can contains lots of views. Reading the view
     // belongings using the view_id.
-    view.belongings = controller.read_views_belong_to(&view_id.value).await?;
-
-    data_result(view)
+    // view.belongings = controller.read_views_belong_to(&view_id.value).await?;
+    todo!()
 }
 
 #[tracing::instrument(level = "debug", skip(data, controller), err)]
@@ -61,7 +69,10 @@ pub(crate) async fn delete_view_handler(
         .read_local_views(params.items)
         .await?
         .into_iter()
-        .map(|view| view.into())
+        .map(|view| {
+            let trash_rev: TrashRevision = view.into();
+            trash_rev.into()
+        })
         .collect::<Vec<Trash>>();
 
     let _ = trash_controller.add(trash).await?;

+ 27 - 15
frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs

@@ -9,7 +9,8 @@ use crate::{
     },
 };
 use flowy_database::kv::KV;
-use flowy_folder_data_model::entities::{app::RepeatedApp, workspace::*};
+use flowy_folder_data_model::entities::workspace::*;
+use flowy_folder_data_model::revision::{AppRevision, WorkspaceRevision};
 use std::sync::Arc;
 
 pub struct WorkspaceController {
@@ -37,7 +38,7 @@ impl WorkspaceController {
     pub(crate) async fn create_workspace_from_params(
         &self,
         params: CreateWorkspaceParams,
-    ) -> Result<Workspace, FlowyError> {
+    ) -> Result<WorkspaceRevision, FlowyError> {
         let workspace = self.create_workspace_on_server(params.clone()).await?;
         let user_id = self.user.user_id()?;
         let token = self.user.token()?;
@@ -47,7 +48,10 @@ impl WorkspaceController {
                 let _ = transaction.create_workspace(&user_id, workspace.clone())?;
                 transaction.read_workspaces(&user_id, None)
             })
-            .await?;
+            .await?
+            .into_iter()
+            .map(|workspace_rev| workspace_rev.into())
+            .collect();
         let repeated_workspace = RepeatedWorkspace { items: workspaces };
         send_dart_notification(&token, FolderNotification::UserCreateWorkspace)
             .payload(repeated_workspace)
@@ -109,16 +113,16 @@ impl WorkspaceController {
         }
     }
 
-    pub(crate) async fn read_current_workspace_apps(&self) -> Result<RepeatedApp, FlowyError> {
+    pub(crate) async fn read_current_workspace_apps(&self) -> Result<Vec<AppRevision>, FlowyError> {
         let workspace_id = get_current_workspace()?;
-        let repeated_app = self
+        let app_revs = self
             .persistence
             .begin_transaction(|transaction| {
                 read_local_workspace_apps(&workspace_id, self.trash_controller.clone(), &transaction)
             })
             .await?;
         // TODO: read from server
-        Ok(repeated_app)
+        Ok(app_revs)
     }
 
     #[tracing::instrument(level = "debug", skip(self, transaction), err)]
@@ -129,7 +133,11 @@ impl WorkspaceController {
         transaction: &'a (dyn FolderPersistenceTransaction + 'a),
     ) -> Result<RepeatedWorkspace, FlowyError> {
         let workspace_id = workspace_id.to_owned();
-        let workspaces = transaction.read_workspaces(user_id, workspace_id)?;
+        let workspaces = transaction
+            .read_workspaces(user_id, workspace_id)?
+            .into_iter()
+            .map(|workspace_rev| workspace_rev.into())
+            .collect();
         Ok(RepeatedWorkspace { items: workspaces })
     }
 
@@ -139,22 +147,26 @@ impl WorkspaceController {
         user_id: &str,
         transaction: &'a (dyn FolderPersistenceTransaction + 'a),
     ) -> Result<Workspace, FlowyError> {
-        let mut workspaces = transaction.read_workspaces(user_id, Some(workspace_id.clone()))?;
-        if workspaces.is_empty() {
+        let mut workspace_revs = transaction.read_workspaces(user_id, Some(workspace_id.clone()))?;
+        if workspace_revs.is_empty() {
             return Err(FlowyError::record_not_found().context(format!("{} workspace not found", workspace_id)));
         }
-        debug_assert_eq!(workspaces.len(), 1);
-        let workspace = workspaces.drain(..1).collect::<Vec<Workspace>>().pop().unwrap();
+        debug_assert_eq!(workspace_revs.len(), 1);
+        let workspace = workspace_revs
+            .drain(..1)
+            .map(|workspace_rev| workspace_rev.into())
+            .collect::<Vec<Workspace>>()
+            .pop()
+            .unwrap();
         Ok(workspace)
     }
 }
 
 impl WorkspaceController {
     #[tracing::instrument(level = "trace", skip(self), err)]
-    async fn create_workspace_on_server(&self, params: CreateWorkspaceParams) -> Result<Workspace, FlowyError> {
+    async fn create_workspace_on_server(&self, params: CreateWorkspaceParams) -> Result<WorkspaceRevision, FlowyError> {
         let token = self.user.token()?;
-        let workspace = self.cloud_service.create_workspace(&token, params).await?;
-        Ok(workspace)
+        self.cloud_service.create_workspace(&token, params).await
     }
 
     #[tracing::instrument(level = "trace", skip(self), err)]
@@ -211,7 +223,7 @@ pub async fn notify_workspace_setting_did_change(
             let setting = match transaction.read_view(view_id) {
                 Ok(latest_view) => CurrentWorkspaceSetting {
                     workspace,
-                    latest_view: Some(latest_view),
+                    latest_view: Some(latest_view.into()),
                 },
                 Err(_) => CurrentWorkspaceSetting {
                     workspace,

+ 39 - 20
frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs

@@ -9,7 +9,6 @@ use flowy_folder_data_model::entities::{
     view::View,
     workspace::{CurrentWorkspaceSetting, RepeatedWorkspace, WorkspaceId, *},
 };
-
 use lib_dispatch::prelude::{data_result, AppData, Data, DataResult};
 use std::{convert::TryInto, sync::Arc};
 
@@ -20,15 +19,21 @@ pub(crate) async fn create_workspace_handler(
 ) -> DataResult<Workspace, FlowyError> {
     let controller = controller.get_ref().clone();
     let params: CreateWorkspaceParams = data.into_inner().try_into()?;
-    let detail = controller.create_workspace_from_params(params).await?;
-    data_result(detail)
+    let workspace_rev = controller.create_workspace_from_params(params).await?;
+    data_result(workspace_rev.into())
 }
 
 #[tracing::instrument(level = "debug", skip(controller), err)]
 pub(crate) async fn read_workspace_apps_handler(
     controller: AppData<Arc<WorkspaceController>>,
 ) -> DataResult<RepeatedApp, FlowyError> {
-    let repeated_app = controller.read_current_workspace_apps().await?;
+    let items = controller
+        .read_current_workspace_apps()
+        .await?
+        .into_iter()
+        .map(|app_rev| app_rev.into())
+        .collect();
+    let repeated_app = RepeatedApp { items };
     data_result(repeated_app)
 }
 
@@ -58,8 +63,10 @@ pub(crate) async fn read_workspaces_handler(
             let mut workspaces =
                 workspace_controller.read_local_workspaces(params.value.clone(), &user_id, &transaction)?;
             for workspace in workspaces.iter_mut() {
-                let apps =
-                    read_local_workspace_apps(&workspace.id, trash_controller.clone(), &transaction)?.into_inner();
+                let apps = read_local_workspace_apps(&workspace.id, trash_controller.clone(), &transaction)?
+                    .into_iter()
+                    .map(|app_rev| app_rev.into())
+                    .collect();
                 workspace.apps.items = apps;
             }
             Ok(workspaces)
@@ -88,7 +95,12 @@ pub async fn read_cur_workspace_handler(
         })
         .await?;
 
-    let latest_view: Option<View> = folder.view_controller.latest_visit_view().await.unwrap_or(None);
+    let latest_view: Option<View> = folder
+        .view_controller
+        .latest_visit_view()
+        .await
+        .unwrap_or(None)
+        .map(|view_rev| view_rev.into());
     let setting = CurrentWorkspaceSetting { workspace, latest_view };
     let _ = read_workspaces_on_server(folder, user_id, params);
     data_result(setting)
@@ -104,25 +116,25 @@ fn read_workspaces_on_server(
     let persistence = folder_manager.persistence.clone();
 
     tokio::spawn(async move {
-        let workspaces = server.read_workspace(&token, params).await?;
+        let workspace_revs = server.read_workspace(&token, params).await?;
         let _ = persistence
             .begin_transaction(|transaction| {
-                tracing::trace!("Save {} workspace", workspaces.len());
-                for workspace in &workspaces.items {
-                    let m_workspace = workspace.clone();
-                    let apps = m_workspace.apps.clone().into_inner();
+                tracing::trace!("Save {} workspace", workspace_revs.len());
+                for workspace_rev in &workspace_revs {
+                    let m_workspace = workspace_rev.clone();
+                    let app_revs = m_workspace.apps.clone();
                     let _ = transaction.create_workspace(&user_id, m_workspace)?;
-                    tracing::trace!("Save {} apps", apps.len());
-                    for app in apps {
-                        let views = app.belongings.clone().into_inner();
-                        match transaction.create_app(app) {
+                    tracing::trace!("Save {} apps", app_revs.len());
+                    for app_rev in app_revs {
+                        let view_revs = app_rev.belongings.clone();
+                        match transaction.create_app(app_rev) {
                             Ok(_) => {}
                             Err(e) => log::error!("create app failed: {:?}", e),
                         }
 
-                        tracing::trace!("Save {} views", views.len());
-                        for view in views {
-                            match transaction.create_view(view) {
+                        tracing::trace!("Save {} views", view_revs.len());
+                        for view_rev in view_revs {
+                            match transaction.create_view(view_rev) {
                                 Ok(_) => {}
                                 Err(e) => log::error!("create view failed: {:?}", e),
                             }
@@ -133,8 +145,15 @@ fn read_workspaces_on_server(
             })
             .await?;
 
+        let repeated_workspace = RepeatedWorkspace {
+            items: workspace_revs
+                .into_iter()
+                .map(|workspace_rev| workspace_rev.into())
+                .collect(),
+        };
+
         send_dart_notification(&token, FolderNotification::WorkspaceListUpdated)
-            .payload(workspaces)
+            .payload(repeated_workspace)
             .send();
         Result::<(), FlowyError>::Ok(())
     });

+ 2 - 9
frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs

@@ -1,7 +1,7 @@
 use crate::script::{invalid_workspace_name_test_case, FolderScript::*, FolderTest};
 use flowy_folder::entities::workspace::CreateWorkspacePayload;
 use flowy_folder_data_model::entities::view::ViewDataType;
-use flowy_folder_data_model::revision::{AppRevision, WorkspaceRevision};
+
 use flowy_revision::disk::RevisionState;
 use flowy_test::{event_builder::*, FlowySDKTest};
 
@@ -38,12 +38,9 @@ async fn workspace_create() {
 async fn workspace_read() {
     let mut test = FolderTest::new().await;
     let workspace = test.workspace.clone();
-    let workspace_revision: WorkspaceRevision = workspace.clone().into();
-    let json = serde_json::to_string(&workspace_revision).unwrap();
 
     test.run_scripts(vec![
         ReadWorkspace(Some(workspace.id.clone())),
-        AssertWorkspaceRevisionJson(json),
         AssertWorkspace(workspace),
     ])
     .await;
@@ -59,10 +56,7 @@ async fn workspace_create_with_apps() {
     .await;
 
     let app = test.app.clone();
-    let app_revision: AppRevision = app.clone().into();
-    let json = serde_json::to_string(&app_revision).unwrap();
-    test.run_scripts(vec![ReadApp(app.id), AssertAppRevisionJson(json)])
-        .await;
+    test.run_scripts(vec![ReadApp(app.id)]).await;
 }
 
 #[tokio::test]
@@ -346,7 +340,6 @@ async fn folder_sync_revision_with_new_view() {
 
     let view = test.view.clone();
     assert_eq!(view.name, view_name);
-    assert_eq!(view.desc, view_desc);
     test.run_scripts(vec![ReadView(view.id.clone()), AssertView(view)])
         .await;
 }

+ 17 - 17
frontend/rust-lib/flowy-folder/tests/workspace/script.rs

@@ -14,7 +14,7 @@ use flowy_folder_data_model::entities::{
     view::{CreateViewPayload, UpdateViewPayload},
     workspace::{CreateWorkspacePayload, RepeatedWorkspace},
 };
-use flowy_folder_data_model::revision::{AppRevision, WorkspaceRevision};
+
 use flowy_revision::disk::RevisionState;
 use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
 use flowy_sync::entities::text_block_info::TextBlockInfo;
@@ -29,7 +29,7 @@ pub enum FolderScript {
         name: String,
         desc: String,
     },
-    AssertWorkspaceRevisionJson(String),
+    // AssertWorkspaceRevisionJson(String),
     AssertWorkspace(Workspace),
     ReadWorkspace(Option<String>),
 
@@ -38,7 +38,7 @@ pub enum FolderScript {
         name: String,
         desc: String,
     },
-    AssertAppRevisionJson(String),
+    // AssertAppRevisionJson(String),
     AssertApp(App),
     ReadApp(String),
     UpdateApp {
@@ -139,15 +139,15 @@ impl FolderTest {
                 let workspace = create_workspace(sdk, &name, &desc).await;
                 self.workspace = workspace;
             }
-            FolderScript::AssertWorkspaceRevisionJson(expected_json) => {
-                let workspace = read_workspace(sdk, Some(self.workspace.id.clone()))
-                    .await
-                    .pop()
-                    .unwrap();
-                let workspace_revision: WorkspaceRevision = workspace.into();
-                let json = serde_json::to_string(&workspace_revision).unwrap();
-                assert_eq!(json, expected_json);
-            }
+            // FolderScript::AssertWorkspaceRevisionJson(expected_json) => {
+            //     let workspace = read_workspace(sdk, Some(self.workspace.id.clone()))
+            //         .await
+            //         .pop()
+            //         .unwrap();
+            //     let workspace_revision: WorkspaceRevision = workspace.into();
+            //     let json = serde_json::to_string(&workspace_revision).unwrap();
+            //     assert_eq!(json, expected_json);
+            // }
             FolderScript::AssertWorkspace(workspace) => {
                 assert_eq!(self.workspace, workspace);
             }
@@ -159,11 +159,11 @@ impl FolderTest {
                 let app = create_app(sdk, &self.workspace.id, &name, &desc).await;
                 self.app = app;
             }
-            FolderScript::AssertAppRevisionJson(expected_json) => {
-                let app_revision: AppRevision = self.app.clone().into();
-                let json = serde_json::to_string(&app_revision).unwrap();
-                assert_eq!(json, expected_json);
-            }
+            // FolderScript::AssertAppRevisionJson(expected_json) => {
+            //     let app_revision: AppRevision = self.app.clone().into();
+            //     let json = serde_json::to_string(&app_revision).unwrap();
+            //     assert_eq!(json, expected_json);
+            // }
             FolderScript::AssertApp(app) => {
                 assert_eq!(self.app, app);
             }

+ 100 - 80
frontend/rust-lib/flowy-net/src/http_server/folder.rs

@@ -4,13 +4,14 @@ use crate::{
 };
 use flowy_error::FlowyError;
 use flowy_folder_data_model::entities::{
-    app::{App, AppId, CreateAppParams, UpdateAppParams},
-    trash::{RepeatedTrash, RepeatedTrashId},
-    view::{CreateViewParams, RepeatedViewId, UpdateViewParams, View, ViewId},
-    workspace::{CreateWorkspaceParams, RepeatedWorkspace, UpdateWorkspaceParams, Workspace, WorkspaceId},
+    trash::RepeatedTrashId,
+    view::{CreateViewParams, RepeatedViewId, UpdateViewParams, ViewId},
+    workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceId},
+    {AppId, CreateAppParams, UpdateAppParams},
 };
 
 use flowy_folder::event_map::FolderCouldServiceV1;
+use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
 use http_flowy::errors::ServerError;
 use http_flowy::response::FlowyResponse;
 use lazy_static::lazy_static;
@@ -31,7 +32,11 @@ impl FolderHttpCloudService {
 impl FolderCouldServiceV1 for FolderHttpCloudService {
     fn init(&self) {}
 
-    fn create_workspace(&self, token: &str, params: CreateWorkspaceParams) -> FutureResult<Workspace, FlowyError> {
+    fn create_workspace(
+        &self,
+        token: &str,
+        params: CreateWorkspaceParams,
+    ) -> FutureResult<WorkspaceRevision, FlowyError> {
         let token = token.to_owned();
         let url = self.config.workspace_url();
         FutureResult::new(async move {
@@ -40,12 +45,12 @@ impl FolderCouldServiceV1 for FolderHttpCloudService {
         })
     }
 
-    fn read_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult<RepeatedWorkspace, FlowyError> {
+    fn read_workspace(&self, token: &str, params: WorkspaceId) -> FutureResult<Vec<WorkspaceRevision>, FlowyError> {
         let token = token.to_owned();
         let url = self.config.workspace_url();
         FutureResult::new(async move {
-            let repeated_workspace = read_workspaces_request(&token, params, &url).await?;
-            Ok(repeated_workspace)
+            let workspace_revs = read_workspaces_request(&token, params, &url).await?;
+            Ok(workspace_revs)
         })
     }
 
@@ -67,7 +72,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService {
         })
     }
 
-    fn create_view(&self, token: &str, params: CreateViewParams) -> FutureResult<View, FlowyError> {
+    fn create_view(&self, token: &str, params: CreateViewParams) -> FutureResult<ViewRevision, FlowyError> {
         let token = token.to_owned();
         let url = self.config.view_url();
         FutureResult::new(async move {
@@ -76,12 +81,12 @@ impl FolderCouldServiceV1 for FolderHttpCloudService {
         })
     }
 
-    fn read_view(&self, token: &str, params: ViewId) -> FutureResult<Option<View>, FlowyError> {
+    fn read_view(&self, token: &str, params: ViewId) -> FutureResult<Option<ViewRevision>, FlowyError> {
         let token = token.to_owned();
         let url = self.config.view_url();
         FutureResult::new(async move {
-            let view = read_view_request(&token, params, &url).await?;
-            Ok(view)
+            let view_rev = read_view_request(&token, params, &url).await?;
+            Ok(view_rev)
         })
     }
 
@@ -103,7 +108,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService {
         })
     }
 
-    fn create_app(&self, token: &str, params: CreateAppParams) -> FutureResult<App, FlowyError> {
+    fn create_app(&self, token: &str, params: CreateAppParams) -> FutureResult<AppRevision, FlowyError> {
         let token = token.to_owned();
         let url = self.config.app_url();
         FutureResult::new(async move {
@@ -112,12 +117,12 @@ impl FolderCouldServiceV1 for FolderHttpCloudService {
         })
     }
 
-    fn read_app(&self, token: &str, params: AppId) -> FutureResult<Option<App>, FlowyError> {
+    fn read_app(&self, token: &str, params: AppId) -> FutureResult<Option<AppRevision>, FlowyError> {
         let token = token.to_owned();
         let url = self.config.app_url();
         FutureResult::new(async move {
-            let app = read_app_request(&token, params, &url).await?;
-            Ok(app)
+            let app_rev = read_app_request(&token, params, &url).await?;
+            Ok(app_rev)
         })
     }
 
@@ -157,7 +162,7 @@ impl FolderCouldServiceV1 for FolderHttpCloudService {
         })
     }
 
-    fn read_trash(&self, token: &str) -> FutureResult<RepeatedTrash, FlowyError> {
+    fn read_trash(&self, token: &str) -> FutureResult<Vec<TrashRevision>, FlowyError> {
         let token = token.to_owned();
         let url = self.config.trash_url();
         FutureResult::new(async move {
@@ -172,32 +177,34 @@ fn request_builder() -> HttpRequestBuilder {
 }
 
 pub async fn create_workspace_request(
-    token: &str,
-    params: CreateWorkspaceParams,
-    url: &str,
-) -> Result<Workspace, ServerError> {
-    let workspace = request_builder()
-        .post(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .protobuf(params)?
-        .response()
-        .await?;
-    Ok(workspace)
+    _token: &str,
+    _params: CreateWorkspaceParams,
+    _url: &str,
+) -> Result<WorkspaceRevision, ServerError> {
+    // let workspace = request_builder()
+    //     .post(&url.to_owned())
+    //     .header(HEADER_TOKEN, token)
+    //     .protobuf(params)?
+    //     .response()
+    //     .await?;
+    // Ok(workspace)
+    unimplemented!()
 }
 
 pub async fn read_workspaces_request(
-    token: &str,
-    params: WorkspaceId,
-    url: &str,
-) -> Result<RepeatedWorkspace, ServerError> {
-    let repeated_workspace = request_builder()
-        .get(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .protobuf(params)?
-        .response::<RepeatedWorkspace>()
-        .await?;
-
-    Ok(repeated_workspace)
+    _token: &str,
+    _params: WorkspaceId,
+    _url: &str,
+) -> Result<Vec<WorkspaceRevision>, ServerError> {
+    // let repeated_workspace = request_builder()
+    //     .get(&url.to_owned())
+    //     .header(HEADER_TOKEN, token)
+    //     .protobuf(params)?
+    //     .response::<RepeatedWorkspace>()
+    //     .await?;
+    //
+    // Ok(repeated_workspace)
+    unimplemented!()
 }
 
 pub async fn update_workspace_request(
@@ -225,25 +232,31 @@ pub async fn delete_workspace_request(token: &str, params: WorkspaceId, url: &st
 }
 
 // App
-pub async fn create_app_request(token: &str, params: CreateAppParams, url: &str) -> Result<App, ServerError> {
-    let app = request_builder()
-        .post(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .protobuf(params)?
-        .response()
-        .await?;
-    Ok(app)
+pub async fn create_app_request(
+    _token: &str,
+    _params: CreateAppParams,
+    _url: &str,
+) -> Result<AppRevision, ServerError> {
+    // let app = request_builder()
+    //     .post(&url.to_owned())
+    //     .header(HEADER_TOKEN, token)
+    //     .protobuf(params)?
+    //     .response()
+    //     .await?;
+    // Ok(app)
+    unimplemented!()
 }
 
-pub async fn read_app_request(token: &str, params: AppId, url: &str) -> Result<Option<App>, ServerError> {
-    let app = request_builder()
-        .get(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .protobuf(params)?
-        .option_response()
-        .await?;
+pub async fn read_app_request(_token: &str, _params: AppId, _url: &str) -> Result<Option<AppRevision>, ServerError> {
+    // let app = request_builder()
+    //     .get(&url.to_owned())
+    //     .header(HEADER_TOKEN, token)
+    //     .protobuf(params)?
+    //     .option_response()
+    //     .await?;
+    // Ok(app)
 
-    Ok(app)
+    unimplemented!()
 }
 
 pub async fn update_app_request(token: &str, params: UpdateAppParams, url: &str) -> Result<(), ServerError> {
@@ -267,25 +280,31 @@ pub async fn delete_app_request(token: &str, params: AppId, url: &str) -> Result
 }
 
 // View
-pub async fn create_view_request(token: &str, params: CreateViewParams, url: &str) -> Result<View, ServerError> {
-    let view = request_builder()
-        .post(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .protobuf(params)?
-        .response()
-        .await?;
-    Ok(view)
+pub async fn create_view_request(
+    _token: &str,
+    _params: CreateViewParams,
+    _url: &str,
+) -> Result<ViewRevision, ServerError> {
+    // let view = request_builder()
+    //     .post(&url.to_owned())
+    //     .header(HEADER_TOKEN, token)
+    //     .protobuf(params)?
+    //     .response()
+    //     .await?;
+    // Ok(view)
+    unimplemented!()
 }
 
-pub async fn read_view_request(token: &str, params: ViewId, url: &str) -> Result<Option<View>, ServerError> {
-    let view = request_builder()
-        .get(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .protobuf(params)?
-        .option_response()
-        .await?;
-
-    Ok(view)
+pub async fn read_view_request(_token: &str, _params: ViewId, _url: &str) -> Result<Option<ViewRevision>, ServerError> {
+    // let view = request_builder()
+    //     .get(&url.to_owned())
+    //     .header(HEADER_TOKEN, token)
+    //     .protobuf(params)?
+    //     .option_response()
+    //     .await?;
+    //
+    // Ok(view)
+    unimplemented!()
 }
 
 pub async fn update_view_request(token: &str, params: UpdateViewParams, url: &str) -> Result<(), ServerError> {
@@ -328,13 +347,14 @@ pub async fn delete_trash_request(token: &str, params: RepeatedTrashId, url: &st
     Ok(())
 }
 
-pub async fn read_trash_request(token: &str, url: &str) -> Result<RepeatedTrash, ServerError> {
-    let repeated_trash = request_builder()
-        .get(&url.to_owned())
-        .header(HEADER_TOKEN, token)
-        .response::<RepeatedTrash>()
-        .await?;
-    Ok(repeated_trash)
+pub async fn read_trash_request(_token: &str, _url: &str) -> Result<Vec<TrashRevision>, ServerError> {
+    // let repeated_trash = request_builder()
+    //     .get(&url.to_owned())
+    //     .header(HEADER_TOKEN, token)
+    //     .response::<RepeatedTrash>()
+    //     .await?;
+    // Ok(repeated_trash)
+    unimplemented!()
 }
 
 lazy_static! {

+ 26 - 27
frontend/rust-lib/flowy-net/src/local_server/server.rs

@@ -255,11 +255,12 @@ impl RevisionUser for LocalRevisionUser {
 use flowy_folder_data_model::entities::app::gen_app_id;
 use flowy_folder_data_model::entities::workspace::gen_workspace_id;
 use flowy_folder_data_model::entities::{
-    app::{App, AppId, CreateAppParams, RepeatedApp, UpdateAppParams},
-    trash::{RepeatedTrash, RepeatedTrashId},
-    view::{CreateViewParams, RepeatedView, RepeatedViewId, UpdateViewParams, View, ViewId},
-    workspace::{CreateWorkspaceParams, RepeatedWorkspace, UpdateWorkspaceParams, Workspace, WorkspaceId},
+    app::{AppId, CreateAppParams, UpdateAppParams},
+    trash::RepeatedTrashId,
+    view::{CreateViewParams, RepeatedViewId, UpdateViewParams, ViewId},
+    workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceId},
 };
+use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
 use flowy_text_block::BlockCloudService;
 use flowy_user::event_map::UserCloudService;
 use flowy_user_data_model::entities::{
@@ -270,13 +271,17 @@ use lib_infra::{future::FutureResult, util::timestamp};
 impl FolderCouldServiceV1 for LocalServer {
     fn init(&self) {}
 
-    fn create_workspace(&self, _token: &str, params: CreateWorkspaceParams) -> FutureResult<Workspace, FlowyError> {
+    fn create_workspace(
+        &self,
+        _token: &str,
+        params: CreateWorkspaceParams,
+    ) -> FutureResult<WorkspaceRevision, FlowyError> {
         let time = timestamp();
-        let workspace = Workspace {
+        let workspace = WorkspaceRevision {
             id: gen_workspace_id(),
             name: params.name,
             desc: params.desc,
-            apps: RepeatedApp::default(),
+            apps: vec![],
             modified_time: time,
             create_time: time,
         };
@@ -284,11 +289,8 @@ impl FolderCouldServiceV1 for LocalServer {
         FutureResult::new(async { Ok(workspace) })
     }
 
-    fn read_workspace(&self, _token: &str, _params: WorkspaceId) -> FutureResult<RepeatedWorkspace, FlowyError> {
-        FutureResult::new(async {
-            let repeated_workspace = RepeatedWorkspace { items: vec![] };
-            Ok(repeated_workspace)
-        })
+    fn read_workspace(&self, _token: &str, _params: WorkspaceId) -> FutureResult<Vec<WorkspaceRevision>, FlowyError> {
+        FutureResult::new(async { Ok(vec![]) })
     }
 
     fn update_workspace(&self, _token: &str, _params: UpdateWorkspaceParams) -> FutureResult<(), FlowyError> {
@@ -299,16 +301,16 @@ impl FolderCouldServiceV1 for LocalServer {
         FutureResult::new(async { Ok(()) })
     }
 
-    fn create_view(&self, _token: &str, params: CreateViewParams) -> FutureResult<View, FlowyError> {
+    fn create_view(&self, _token: &str, params: CreateViewParams) -> FutureResult<ViewRevision, FlowyError> {
         let time = timestamp();
-        let view = View {
+        let view = ViewRevision {
             id: params.view_id,
             belong_to_id: params.belong_to_id,
             name: params.name,
             desc: params.desc,
             data_type: params.data_type,
             version: 0,
-            belongings: RepeatedView::default(),
+            belongings: vec![],
             modified_time: time,
             create_time: time,
             ext_data: "".to_string(),
@@ -318,7 +320,7 @@ impl FolderCouldServiceV1 for LocalServer {
         FutureResult::new(async { Ok(view) })
     }
 
-    fn read_view(&self, _token: &str, _params: ViewId) -> FutureResult<Option<View>, FlowyError> {
+    fn read_view(&self, _token: &str, _params: ViewId) -> FutureResult<Option<ViewRevision>, FlowyError> {
         FutureResult::new(async { Ok(None) })
     }
 
@@ -330,14 +332,14 @@ impl FolderCouldServiceV1 for LocalServer {
         FutureResult::new(async { Ok(()) })
     }
 
-    fn create_app(&self, _token: &str, params: CreateAppParams) -> FutureResult<App, FlowyError> {
+    fn create_app(&self, _token: &str, params: CreateAppParams) -> FutureResult<AppRevision, FlowyError> {
         let time = timestamp();
-        let app = App {
+        let app = AppRevision {
             id: gen_app_id(),
             workspace_id: params.workspace_id,
             name: params.name,
             desc: params.desc,
-            belongings: RepeatedView::default(),
+            belongings: vec![],
             version: 0,
             modified_time: time,
             create_time: time,
@@ -345,7 +347,7 @@ impl FolderCouldServiceV1 for LocalServer {
         FutureResult::new(async { Ok(app) })
     }
 
-    fn read_app(&self, _token: &str, _params: AppId) -> FutureResult<Option<App>, FlowyError> {
+    fn read_app(&self, _token: &str, _params: AppId) -> FutureResult<Option<AppRevision>, FlowyError> {
         FutureResult::new(async { Ok(None) })
     }
 
@@ -365,17 +367,14 @@ impl FolderCouldServiceV1 for LocalServer {
         FutureResult::new(async { Ok(()) })
     }
 
-    fn read_trash(&self, _token: &str) -> FutureResult<RepeatedTrash, FlowyError> {
-        FutureResult::new(async {
-            let repeated_trash = RepeatedTrash { items: vec![] };
-            Ok(repeated_trash)
-        })
+    fn read_trash(&self, _token: &str) -> FutureResult<Vec<TrashRevision>, FlowyError> {
+        FutureResult::new(async { Ok(vec![]) })
     }
 }
 
 impl UserCloudService for LocalServer {
     fn sign_up(&self, params: SignUpParams) -> FutureResult<SignUpResponse, FlowyError> {
-        let uid = nanoid!(10);
+        let uid = nanoid!(20);
         FutureResult::new(async move {
             Ok(SignUpResponse {
                 user_id: uid.clone(),
@@ -387,7 +386,7 @@ impl UserCloudService for LocalServer {
     }
 
     fn sign_in(&self, params: SignInParams) -> FutureResult<SignInResponse, FlowyError> {
-        let user_id = nanoid!(10);
+        let user_id = nanoid!(20);
         FutureResult::new(async {
             Ok(SignInResponse {
                 user_id: user_id.clone(),

+ 3 - 2
frontend/rust-lib/flowy-test/src/helper.rs

@@ -126,7 +126,7 @@ pub fn root_dir() -> String {
 }
 
 pub fn random_email() -> String {
-    format!("{}@appflowy.io", nanoid!(10))
+    format!("{}@appflowy.io", nanoid!(20))
 }
 
 pub fn login_email() -> String {
@@ -163,8 +163,9 @@ pub fn sign_up(dispatch: Arc<EventDispatcher>) -> SignUpContext {
 
 pub async fn async_sign_up(dispatch: Arc<EventDispatcher>) -> SignUpContext {
     let password = login_password();
+    let email = random_email();
     let payload = SignUpPayload {
-        email: random_email(),
+        email,
         name: "app flowy".to_string(),
         password: password.clone(),
     }

+ 20 - 48
frontend/rust-lib/flowy-user/src/services/database.rs

@@ -3,8 +3,7 @@ use flowy_database::{schema::user_table, DBConnection, Database};
 use flowy_error::{ErrorCode, FlowyError};
 use flowy_user_data_model::entities::{SignInResponse, SignUpResponse, UpdateUserParams, UserProfile};
 use lazy_static::lazy_static;
-use once_cell::sync::Lazy;
-use parking_lot::{Mutex, RwLock};
+use parking_lot::RwLock;
 use std::{collections::HashMap, sync::Arc, time::Duration};
 
 lazy_static! {
@@ -22,32 +21,38 @@ impl UserDB {
         }
     }
 
-    fn open_user_db(&self, user_id: &str) -> Result<(), FlowyError> {
+    fn open_user_db_if_need(&self, user_id: &str) -> Result<Arc<ConnectionPool>, FlowyError> {
         if user_id.is_empty() {
             return Err(ErrorCode::UserIdIsEmpty.into());
         }
 
+        if let Some(database) = DB_MAP.read().get(user_id) {
+            return Ok(database.get_pool());
+        }
+
+        let mut write_guard = DB_MAP.write();
+        // The Write guard acquire exclusive access that will guarantee the user db only initialize once.
+        match write_guard.get(user_id) {
+            None => {}
+            Some(database) => return Ok(database.get_pool()),
+        }
+
         tracing::trace!("open user db {}", user_id);
         let dir = format!("{}/{}", self.db_dir, user_id);
         let db = flowy_database::init(&dir).map_err(|e| {
-            log::error!("init user db failed, {:?}, user_id: {}", e, user_id);
+            log::error!("open user: {} db failed, {:?}", user_id, e);
             FlowyError::internal().context(e)
         })?;
-
-        match DB_MAP.try_write_for(Duration::from_millis(300)) {
-            None => Err(FlowyError::internal().context("Acquire write lock to save user db failed")),
-            Some(mut write_guard) => {
-                write_guard.insert(user_id.to_owned(), db);
-                Ok(())
-            }
-        }
+        let pool = db.get_pool();
+        write_guard.insert(user_id.to_owned(), db);
+        drop(write_guard);
+        Ok(pool)
     }
 
     pub(crate) fn close_user_db(&self, user_id: &str) -> Result<(), FlowyError> {
         match DB_MAP.try_write_for(Duration::from_millis(300)) {
             None => Err(FlowyError::internal().context("Acquire write lock to close user db failed")),
             Some(mut write_guard) => {
-                set_user_db_init(false, user_id);
                 write_guard.remove(user_id);
                 Ok(())
             }
@@ -60,27 +65,8 @@ impl UserDB {
     }
 
     pub(crate) fn get_pool(&self, user_id: &str) -> Result<Arc<ConnectionPool>, FlowyError> {
-        // Opti: INIT_LOCK try to lock the INIT_RECORD accesses. Because the write guard
-        // can not nested in the read guard that will cause the deadlock.
-        match INIT_LOCK.try_lock_for(Duration::from_millis(300)) {
-            None => log::error!("get_pool fail"),
-            Some(_) => {
-                if !is_user_db_init(user_id) {
-                    let _ = self.open_user_db(user_id)?;
-                    set_user_db_init(true, user_id);
-                }
-            }
-        }
-
-        match DB_MAP.try_read_for(Duration::from_millis(300)) {
-            None => Err(FlowyError::internal().context("Acquire read lock to read user db failed")),
-            Some(read_guard) => match read_guard.get(user_id) {
-                None => {
-                    Err(FlowyError::internal().context("Get connection failed. The database is not initialization"))
-                }
-                Some(database) => Ok(database.get_pool()),
-            },
-        }
+        let pool = self.open_user_db_if_need(user_id)?;
+        Ok(pool)
     }
 }
 
@@ -88,20 +74,6 @@ lazy_static! {
     static ref DB_MAP: RwLock<HashMap<String, Database>> = RwLock::new(HashMap::new());
 }
 
-static INIT_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
-static INIT_RECORD: Lazy<Mutex<HashMap<String, bool>>> = Lazy::new(|| Mutex::new(HashMap::new()));
-fn set_user_db_init(is_init: bool, user_id: &str) {
-    let mut record = INIT_RECORD.lock();
-    record.insert(user_id.to_owned(), is_init);
-}
-
-fn is_user_db_init(user_id: &str) -> bool {
-    match INIT_RECORD.lock().get(user_id) {
-        None => false,
-        Some(flag) => *flag,
-    }
-}
-
 #[derive(Clone, Default, Queryable, Identifiable, Insertable)]
 #[table_name = "user_table"]
 pub struct UserTable {

+ 1 - 1
shared-lib/flowy-folder-data-model/src/entities/app.rs

@@ -15,7 +15,7 @@ use std::convert::TryInto;
 pub fn gen_app_id() -> String {
     nanoid!(10)
 }
-#[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)]
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct App {
     #[pb(index = 1)]
     pub id: String,

+ 4 - 24
shared-lib/flowy-folder-data-model/src/entities/trash.rs

@@ -1,4 +1,5 @@
-use crate::{entities::app::App, impl_def_and_def_mut};
+use crate::impl_def_and_def_mut;
+use crate::revision::TrashRevision;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use serde::{Deserialize, Serialize};
 use std::fmt::Formatter;
@@ -29,18 +30,6 @@ pub struct RepeatedTrash {
 
 impl_def_and_def_mut!(RepeatedTrash, Trash);
 
-impl std::convert::From<App> for Trash {
-    fn from(app: App) -> Self {
-        Trash {
-            id: app.id,
-            name: app.name,
-            modified_time: app.modified_time,
-            create_time: app.create_time,
-            ty: TrashType::TrashApp,
-        }
-    }
-}
-
 #[derive(Eq, PartialEq, Debug, ProtoBuf_Enum, Clone, Serialize, Deserialize)]
 pub enum TrashType {
     Unknown = 0,
@@ -103,8 +92,8 @@ impl std::convert::From<Vec<TrashId>> for RepeatedTrashId {
     }
 }
 
-impl std::convert::From<Vec<Trash>> for RepeatedTrashId {
-    fn from(trash: Vec<Trash>) -> Self {
+impl std::convert::From<Vec<TrashRevision>> for RepeatedTrashId {
+    fn from(trash: Vec<TrashRevision>) -> Self {
         let items = trash
             .into_iter()
             .map(|t| TrashId { id: t.id, ty: t.ty })
@@ -126,15 +115,6 @@ pub struct TrashId {
     pub ty: TrashType,
 }
 
-impl std::convert::From<&Trash> for TrashId {
-    fn from(trash: &Trash) -> Self {
-        TrashId {
-            id: trash.id.clone(),
-            ty: trash.ty.clone(),
-        }
-    }
-}
-
 impl std::fmt::Display for TrashId {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         f.write_str(&format!("{:?}:{}", self.ty, self.id))

+ 55 - 26
shared-lib/flowy-folder-data-model/src/entities/view.rs

@@ -1,5 +1,4 @@
 use crate::{
-    entities::trash::{Trash, TrashType},
     errors::ErrorCode,
     impl_def_and_def_mut,
     parser::{
@@ -17,7 +16,7 @@ pub fn gen_view_id() -> String {
     nanoid!(10)
 }
 
-#[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)]
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct View {
     #[pb(index = 1)]
     pub id: String,
@@ -28,6 +27,30 @@ pub struct View {
     #[pb(index = 3)]
     pub name: String,
 
+    #[pb(index = 4)]
+    pub data_type: ViewDataType,
+
+    #[pb(index = 5)]
+    pub modified_time: i64,
+
+    #[pb(index = 6)]
+    pub create_time: i64,
+
+    #[pb(index = 7)]
+    pub plugin_type: i32,
+}
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct ViewInfo {
+    #[pb(index = 1)]
+    pub id: String,
+
+    #[pb(index = 2)]
+    pub belong_to_id: String,
+
+    #[pb(index = 3)]
+    pub name: String,
+
     #[pb(index = 4)]
     pub desc: String,
 
@@ -35,25 +58,43 @@ pub struct View {
     pub data_type: ViewDataType,
 
     #[pb(index = 6)]
-    pub version: i64,
+    pub belongings: RepeatedView,
 
     #[pb(index = 7)]
-    pub belongings: RepeatedView,
+    pub ext_data: ViewExtData,
+}
 
-    #[pb(index = 8)]
-    pub modified_time: i64,
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct ViewExtData {
+    #[pb(index = 1)]
+    pub filter: ViewFilter,
 
-    #[pb(index = 9)]
-    pub create_time: i64,
+    #[pb(index = 2)]
+    pub group: ViewGroup,
 
-    #[pb(index = 10)]
-    pub ext_data: String,
+    #[pb(index = 3)]
+    pub sort: ViewSort,
+}
 
-    #[pb(index = 11)]
-    pub thumbnail: String,
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct ViewFilter {
+    #[pb(index = 1)]
+    pub field_id: String,
+}
 
-    #[pb(index = 12)]
-    pub plugin_type: i32,
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct ViewGroup {
+    #[pb(index = 1)]
+    pub group_field_id: String,
+
+    #[pb(index = 2, one_of)]
+    pub sub_group_field_id: Option<String>,
+}
+
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct ViewSort {
+    #[pb(index = 1)]
+    pub field_id: String,
 }
 
 #[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone)]
@@ -65,18 +106,6 @@ pub struct RepeatedView {
 
 impl_def_and_def_mut!(RepeatedView, View);
 
-impl std::convert::From<View> for Trash {
-    fn from(view: View) -> Self {
-        Trash {
-            id: view.id,
-            name: view.name,
-            modified_time: view.modified_time,
-            create_time: view.create_time,
-            ty: TrashType::TrashView,
-        }
-    }
-}
-
 #[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone, Serialize_repr, Deserialize_repr)]
 #[repr(u8)]
 pub enum ViewDataType {

File diff suppressed because it is too large
+ 1292 - 233
shared-lib/flowy-folder-data-model/src/protobuf/model/view.rs


+ 26 - 7
shared-lib/flowy-folder-data-model/src/protobuf/proto/view.proto

@@ -1,18 +1,37 @@
 syntax = "proto3";
 
 message View {
+    string id = 1;
+    string belong_to_id = 2;
+    string name = 3;
+    ViewDataType data_type = 4;
+    int64 modified_time = 5;
+    int64 create_time = 6;
+    int32 plugin_type = 7;
+}
+message ViewInfo {
     string id = 1;
     string belong_to_id = 2;
     string name = 3;
     string desc = 4;
     ViewDataType data_type = 5;
-    int64 version = 6;
-    RepeatedView belongings = 7;
-    int64 modified_time = 8;
-    int64 create_time = 9;
-    string ext_data = 10;
-    string thumbnail = 11;
-    int32 plugin_type = 12;
+    RepeatedView belongings = 6;
+    ViewExtData ext_data = 7;
+}
+message ViewExtData {
+    ViewFilter filter = 1;
+    ViewGroup group = 2;
+    ViewSort sort = 3;
+}
+message ViewFilter {
+    string field_id = 1;
+}
+message ViewGroup {
+    string group_field_id = 1;
+    oneof one_of_sub_group_field_id { string sub_group_field_id = 2; };
+}
+message ViewSort {
+    string field_id = 1;
 }
 message RepeatedView {
     repeated View items = 1;

+ 11 - 24
shared-lib/flowy-folder-data-model/src/revision/app.rs

@@ -1,9 +1,9 @@
 use crate::entities::app::App;
-use crate::entities::RepeatedApp;
-use crate::revision::ViewRevision;
+use crate::entities::{RepeatedApp, TrashType};
+use crate::revision::{TrashRevision, ViewRevision};
 use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
 pub struct AppRevision {
     pub id: String,
 
@@ -37,17 +37,14 @@ impl std::convert::From<AppRevision> for App {
     }
 }
 
-impl std::convert::From<App> for AppRevision {
-    fn from(app: App) -> Self {
-        AppRevision {
-            id: app.id,
-            workspace_id: app.workspace_id,
-            name: app.name,
-            desc: app.desc,
-            belongings: app.belongings.into(),
-            version: app.version,
-            modified_time: app.modified_time,
-            create_time: app.create_time,
+impl std::convert::From<AppRevision> for TrashRevision {
+    fn from(app_rev: AppRevision) -> Self {
+        TrashRevision {
+            id: app_rev.id,
+            name: app_rev.name,
+            modified_time: app_rev.modified_time,
+            create_time: app_rev.create_time,
+            ty: TrashType::TrashApp,
         }
     }
 }
@@ -58,13 +55,3 @@ impl std::convert::From<Vec<AppRevision>> for RepeatedApp {
         RepeatedApp { items }
     }
 }
-
-impl std::convert::From<RepeatedApp> for Vec<AppRevision> {
-    fn from(repeated_app: RepeatedApp) -> Self {
-        repeated_app
-            .items
-            .into_iter()
-            .map(|value| value.into())
-            .collect::<Vec<AppRevision>>()
-    }
-}

+ 24 - 7
shared-lib/flowy-folder-data-model/src/revision/trash.rs

@@ -1,7 +1,8 @@
 use crate::entities::trash::{Trash, TrashType};
+use crate::entities::{RepeatedTrash, TrashId};
 use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
 pub struct TrashRevision {
     pub id: String,
 
@@ -14,14 +15,21 @@ pub struct TrashRevision {
     pub ty: TrashType,
 }
 
+impl std::convert::From<Vec<TrashRevision>> for RepeatedTrash {
+    fn from(trash_revs: Vec<TrashRevision>) -> Self {
+        let items: Vec<Trash> = trash_revs.into_iter().map(|trash_rev| trash_rev.into()).collect();
+        RepeatedTrash { items }
+    }
+}
+
 impl std::convert::From<TrashRevision> for Trash {
-    fn from(trash_serde: TrashRevision) -> Self {
+    fn from(trash_rev: TrashRevision) -> Self {
         Trash {
-            id: trash_serde.id,
-            name: trash_serde.name,
-            modified_time: trash_serde.modified_time,
-            create_time: trash_serde.create_time,
-            ty: trash_serde.ty,
+            id: trash_rev.id,
+            name: trash_rev.name,
+            modified_time: trash_rev.modified_time,
+            create_time: trash_rev.create_time,
+            ty: trash_rev.ty,
         }
     }
 }
@@ -37,3 +45,12 @@ impl std::convert::From<Trash> for TrashRevision {
         }
     }
 }
+
+impl std::convert::From<&TrashRevision> for TrashId {
+    fn from(trash: &TrashRevision) -> Self {
+        TrashId {
+            id: trash.id.clone(),
+            ty: trash.ty.clone(),
+        }
+    }
+}

+ 77 - 32
shared-lib/flowy-folder-data-model/src/revision/view.rs

@@ -1,8 +1,9 @@
 use crate::entities::view::{View, ViewDataType};
-use crate::entities::RepeatedView;
+use crate::entities::{RepeatedView, TrashType, ViewExtData, ViewFilter, ViewGroup, ViewSort};
+use crate::revision::TrashRevision;
 use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
 pub struct ViewRevision {
     pub id: String,
 
@@ -43,51 +44,95 @@ impl std::convert::From<ViewRevision> for View {
             id: view_serde.id,
             belong_to_id: view_serde.belong_to_id,
             name: view_serde.name,
-            desc: view_serde.desc,
             data_type: view_serde.data_type,
-            version: view_serde.version,
-            belongings: view_serde.belongings.into(),
             modified_time: view_serde.modified_time,
             create_time: view_serde.create_time,
-            ext_data: view_serde.ext_data,
-            thumbnail: view_serde.thumbnail,
             plugin_type: view_serde.plugin_type,
         }
     }
 }
 
-impl std::convert::From<View> for ViewRevision {
-    fn from(view: View) -> Self {
-        ViewRevision {
-            id: view.id,
-            belong_to_id: view.belong_to_id,
-            name: view.name,
-            desc: view.desc,
-            data_type: view.data_type,
-            version: view.version,
-            belongings: view.belongings.into(),
-            modified_time: view.modified_time,
-            create_time: view.create_time,
-            ext_data: view.ext_data,
-            thumbnail: view.thumbnail,
-            plugin_type: view.plugin_type,
+impl std::convert::From<ViewRevision> for TrashRevision {
+    fn from(view_rev: ViewRevision) -> Self {
+        TrashRevision {
+            id: view_rev.id,
+            name: view_rev.name,
+            modified_time: view_rev.modified_time,
+            create_time: view_rev.create_time,
+            ty: TrashType::TrashView,
         }
     }
 }
 
+#[derive(Serialize, Deserialize)]
+pub struct ViewExtDataRevision {
+    pub filter: ViewFilterRevision,
+    pub group: ViewGroupRevision,
+    pub sort: ViewSortRevision,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct ViewFilterRevision {
+    pub field_id: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct ViewGroupRevision {
+    pub group_field_id: String,
+    pub sub_group_field_id: Option<String>,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct ViewSortRevision {
+    field_id: String,
+}
+
+impl std::convert::From<String> for ViewExtData {
+    fn from(s: String) -> Self {
+        match serde_json::from_str::<ViewExtDataRevision>(&s) {
+            Ok(data) => data.into(),
+            Err(err) => {
+                log::error!("{:?}", err);
+                ViewExtData::default()
+            }
+        }
+    }
+}
+
+impl std::convert::From<ViewExtDataRevision> for ViewExtData {
+    fn from(rev: ViewExtDataRevision) -> Self {
+        ViewExtData {
+            filter: rev.filter.into(),
+            group: rev.group.into(),
+            sort: rev.sort.into(),
+        }
+    }
+}
+
+impl std::convert::From<ViewFilterRevision> for ViewFilter {
+    fn from(rev: ViewFilterRevision) -> Self {
+        ViewFilter { field_id: rev.field_id }
+    }
+}
+
+impl std::convert::From<ViewGroupRevision> for ViewGroup {
+    fn from(rev: ViewGroupRevision) -> Self {
+        ViewGroup {
+            group_field_id: rev.group_field_id,
+            sub_group_field_id: rev.sub_group_field_id,
+        }
+    }
+}
+
+impl std::convert::From<ViewSortRevision> for ViewSort {
+    fn from(rev: ViewSortRevision) -> Self {
+        ViewSort { field_id: rev.field_id }
+    }
+}
+
 impl std::convert::From<Vec<ViewRevision>> for RepeatedView {
     fn from(values: Vec<ViewRevision>) -> Self {
         let items = values.into_iter().map(|value| value.into()).collect::<Vec<View>>();
         RepeatedView { items }
     }
 }
-
-impl std::convert::From<RepeatedView> for Vec<ViewRevision> {
-    fn from(repeated_view: RepeatedView) -> Self {
-        repeated_view
-            .items
-            .into_iter()
-            .map(|value| value.into())
-            .collect::<Vec<ViewRevision>>()
-    }
-}

+ 1 - 14
shared-lib/flowy-folder-data-model/src/revision/workspace.rs

@@ -2,7 +2,7 @@ use crate::entities::workspace::Workspace;
 use crate::revision::AppRevision;
 use serde::{Deserialize, Serialize};
 
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
 pub struct WorkspaceRevision {
     pub id: String,
 
@@ -29,16 +29,3 @@ impl std::convert::From<WorkspaceRevision> for Workspace {
         }
     }
 }
-
-impl std::convert::From<Workspace> for WorkspaceRevision {
-    fn from(workspace: Workspace) -> Self {
-        WorkspaceRevision {
-            id: workspace.id,
-            name: workspace.name,
-            desc: workspace.desc,
-            apps: workspace.apps.into(),
-            modified_time: workspace.modified_time,
-            create_time: workspace.create_time,
-        }
-    }
-}

+ 12 - 20
shared-lib/flowy-folder-data-model/src/user_default.rs

@@ -1,24 +1,19 @@
 use crate::entities::app::gen_app_id;
 use crate::entities::view::gen_view_id;
+use crate::entities::view::ViewDataType;
 use crate::entities::workspace::gen_workspace_id;
-use crate::entities::{
-    app::{App, RepeatedApp},
-    view::{RepeatedView, View, ViewDataType},
-    workspace::Workspace,
-};
+use crate::revision::{AppRevision, ViewRevision, WorkspaceRevision};
 use chrono::Utc;
 
-pub fn create_default_workspace() -> Workspace {
+pub fn create_default_workspace() -> WorkspaceRevision {
     let time = Utc::now();
     let workspace_id = gen_workspace_id();
     let name = "Workspace".to_string();
     let desc = "".to_string();
 
-    let apps = RepeatedApp {
-        items: vec![create_default_app(workspace_id.to_string(), time)],
-    };
+    let apps = vec![create_default_app(workspace_id.to_string(), time)];
 
-    Workspace {
+    WorkspaceRevision {
         id: workspace_id,
         name,
         desc,
@@ -28,16 +23,14 @@ pub fn create_default_workspace() -> Workspace {
     }
 }
 
-fn create_default_app(workspace_id: String, time: chrono::DateTime<Utc>) -> App {
+fn create_default_app(workspace_id: String, time: chrono::DateTime<Utc>) -> AppRevision {
     let app_id = gen_app_id();
     let name = "⭐️ Getting started".to_string();
     let desc = "".to_string();
 
-    let views = RepeatedView {
-        items: vec![create_default_view(app_id.to_string(), time)],
-    };
+    let views = vec![create_default_view(app_id.to_string(), time)];
 
-    App {
+    AppRevision {
         id: app_id,
         workspace_id,
         name,
@@ -49,20 +42,19 @@ fn create_default_app(workspace_id: String, time: chrono::DateTime<Utc>) -> App
     }
 }
 
-fn create_default_view(app_id: String, time: chrono::DateTime<Utc>) -> View {
+fn create_default_view(app_id: String, time: chrono::DateTime<Utc>) -> ViewRevision {
     let view_id = gen_view_id();
     let name = "Read me".to_string();
-    let desc = "".to_string();
     let data_type = ViewDataType::TextBlock;
 
-    View {
+    ViewRevision {
         id: view_id,
         belong_to_id: app_id,
         name,
-        desc,
+        desc: "".to_string(),
         data_type,
         version: 0,
-        belongings: Default::default(),
+        belongings: vec![],
         modified_time: time.timestamp(),
         create_time: time.timestamp(),
         ext_data: "".to_string(),

+ 4 - 8
shared-lib/flowy-sync/src/client_folder/builder.rs

@@ -6,7 +6,6 @@ use crate::{
     errors::{CollaborateError, CollaborateResult},
 };
 
-use flowy_folder_data_model::entities::{Trash, Workspace};
 use flowy_folder_data_model::revision::{TrashRevision, WorkspaceRevision};
 use lib_ot::core::{PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
 use serde::{Deserialize, Serialize};
@@ -26,16 +25,13 @@ impl FolderPadBuilder {
         }
     }
 
-    pub(crate) fn with_workspace(mut self, workspaces: Vec<Workspace>) -> Self {
-        self.workspaces = workspaces
-            .into_iter()
-            .map(|workspace| Arc::new(workspace.into()))
-            .collect::<Vec<_>>();
+    pub(crate) fn with_workspace(mut self, workspaces: Vec<WorkspaceRevision>) -> Self {
+        self.workspaces = workspaces.into_iter().map(Arc::new).collect();
         self
     }
 
-    pub(crate) fn with_trash(mut self, trash: Vec<Trash>) -> Self {
-        self.trash = trash.into_iter().map(|t| Arc::new(t.into())).collect::<Vec<_>>();
+    pub(crate) fn with_trash(mut self, trash: Vec<TrashRevision>) -> Self {
+        self.trash = trash.into_iter().map(Arc::new).collect::<Vec<_>>();
         self
     }
 

+ 65 - 63
shared-lib/flowy-sync/src/client_folder/folder_pad.rs

@@ -8,7 +8,7 @@ use crate::{
     },
     errors::{CollaborateError, CollaborateResult},
 };
-use flowy_folder_data_model::entities::{App, Trash, View, Workspace};
+
 use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
 use lib_infra::util::move_vec_element;
 use lib_ot::core::*;
@@ -24,7 +24,7 @@ pub struct FolderPad {
 }
 
 impl FolderPad {
-    pub fn new(workspaces: Vec<Workspace>, trash: Vec<Trash>) -> CollaborateResult<Self> {
+    pub fn new(workspaces: Vec<WorkspaceRevision>, trash: Vec<TrashRevision>) -> CollaborateResult<Self> {
         FolderPadBuilder::new()
             .with_workspace(workspaces)
             .with_trash(trash)
@@ -61,9 +61,9 @@ impl FolderPad {
         self.workspaces.is_empty() && self.trash.is_empty()
     }
 
-    #[tracing::instrument(level = "trace", skip(self, workspace), fields(workspace_name=%workspace.name), err)]
-    pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<FolderChange>> {
-        let workspace = Arc::new(workspace.into());
+    #[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)]
+    pub fn create_workspace(&mut self, workspace_rev: WorkspaceRevision) -> CollaborateResult<Option<FolderChange>> {
+        let workspace = Arc::new(workspace_rev);
         if self.workspaces.contains(&workspace) {
             tracing::warn!("[RootFolder]: Duplicate workspace");
             return Ok(None);
@@ -93,19 +93,19 @@ impl FolderPad {
         })
     }
 
-    pub fn read_workspaces(&self, workspace_id: Option<String>) -> CollaborateResult<Vec<Workspace>> {
+    pub fn read_workspaces(&self, workspace_id: Option<String>) -> CollaborateResult<Vec<WorkspaceRevision>> {
         match workspace_id {
             None => {
                 let workspaces = self
                     .workspaces
                     .iter()
-                    .map(|workspace| workspace.as_ref().clone().into())
-                    .collect::<Vec<Workspace>>();
+                    .map(|workspace| workspace.as_ref().clone())
+                    .collect::<Vec<WorkspaceRevision>>();
                 Ok(workspaces)
             }
             Some(workspace_id) => {
                 if let Some(workspace) = self.workspaces.iter().find(|workspace| workspace.id == workspace_id) {
-                    Ok(vec![workspace.as_ref().clone().into()])
+                    Ok(vec![workspace.as_ref().clone()])
                 } else {
                     Err(CollaborateError::record_not_found()
                         .context(format!("Can't find workspace with id {}", workspace_id)))
@@ -122,24 +122,23 @@ impl FolderPad {
         })
     }
 
-    #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app.name), err)]
-    pub fn create_app(&mut self, app: App) -> CollaborateResult<Option<FolderChange>> {
-        let workspace_id = app.workspace_id.clone();
-        let app_serde: AppRevision = app.into();
+    #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app_rev.name), err)]
+    pub fn create_app(&mut self, app_rev: AppRevision) -> CollaborateResult<Option<FolderChange>> {
+        let workspace_id = app_rev.workspace_id.clone();
         self.with_workspace(&workspace_id, move |workspace| {
-            if workspace.apps.contains(&app_serde) {
+            if workspace.apps.contains(&app_rev) {
                 tracing::warn!("[RootFolder]: Duplicate app");
                 return Ok(None);
             }
-            workspace.apps.push(app_serde);
+            workspace.apps.push(app_rev);
             Ok(Some(()))
         })
     }
 
-    pub fn read_app(&self, app_id: &str) -> CollaborateResult<App> {
+    pub fn read_app(&self, app_id: &str) -> CollaborateResult<AppRevision> {
         for workspace in &self.workspaces {
             if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) {
-                return Ok(app.clone().into());
+                return Ok(app.clone());
             }
         }
         Err(CollaborateError::record_not_found().context(format!("Can't find app with id {}", app_id)))
@@ -183,36 +182,35 @@ impl FolderPad {
         })
     }
 
-    #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view.name), err)]
-    pub fn create_view(&mut self, view: View) -> CollaborateResult<Option<FolderChange>> {
-        let app_id = view.belong_to_id.clone();
-        let view_serde: ViewRevision = view.into();
+    #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view_rev.name), err)]
+    pub fn create_view(&mut self, view_rev: ViewRevision) -> CollaborateResult<Option<FolderChange>> {
+        let app_id = view_rev.belong_to_id.clone();
         self.with_app(&app_id, move |app| {
-            if app.belongings.contains(&view_serde) {
+            if app.belongings.contains(&view_rev) {
                 tracing::warn!("[RootFolder]: Duplicate view");
                 return Ok(None);
             }
-            app.belongings.push(view_serde);
+            app.belongings.push(view_rev);
             Ok(Some(()))
         })
     }
 
-    pub fn read_view(&self, view_id: &str) -> CollaborateResult<View> {
+    pub fn read_view(&self, view_id: &str) -> CollaborateResult<ViewRevision> {
         for workspace in &self.workspaces {
             for app in &(*workspace.apps) {
                 if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) {
-                    return Ok(view.clone().into());
+                    return Ok(view.clone());
                 }
             }
         }
         Err(CollaborateError::record_not_found().context(format!("Can't find view with id {}", view_id)))
     }
 
-    pub fn read_views(&self, belong_to_id: &str) -> CollaborateResult<Vec<View>> {
+    pub fn read_views(&self, belong_to_id: &str) -> CollaborateResult<Vec<ViewRevision>> {
         for workspace in &self.workspaces {
             for app in &(*workspace.apps) {
                 if app.id == belong_to_id {
-                    return Ok(app.belongings.iter().map(|view| view.clone().into()).collect());
+                    return Ok(app.belongings.to_vec());
                 }
             }
         }
@@ -261,27 +259,24 @@ impl FolderPad {
         })
     }
 
-    pub fn create_trash(&mut self, trash: Vec<Trash>) -> CollaborateResult<Option<FolderChange>> {
+    pub fn create_trash(&mut self, trash: Vec<TrashRevision>) -> CollaborateResult<Option<FolderChange>> {
         self.with_trash(|t| {
-            let mut new_trash = trash
-                .into_iter()
-                .map(|t| Arc::new(t.into()))
-                .collect::<Vec<Arc<TrashRevision>>>();
+            let mut new_trash = trash.into_iter().map(Arc::new).collect::<Vec<Arc<TrashRevision>>>();
             t.append(&mut new_trash);
 
             Ok(Some(()))
         })
     }
 
-    pub fn read_trash(&self, trash_id: Option<String>) -> CollaborateResult<Vec<Trash>> {
+    pub fn read_trash(&self, trash_id: Option<String>) -> CollaborateResult<Vec<TrashRevision>> {
         match trash_id {
             None => Ok(self
                 .trash
                 .iter()
-                .map(|t| t.as_ref().clone().into())
-                .collect::<Vec<Trash>>()),
+                .map(|t| t.as_ref().clone())
+                .collect::<Vec<TrashRevision>>()),
             Some(trash_id) => match self.trash.iter().find(|t| t.id == trash_id) {
-                Some(trash) => Ok(vec![trash.as_ref().clone().into()]),
+                Some(trash) => Ok(vec![trash.as_ref().clone()]),
                 None => Ok(vec![]),
             },
         }
@@ -438,7 +433,8 @@ mod tests {
     #![allow(clippy::all)]
     use crate::{client_folder::folder_pad::FolderPad, entities::folder_info::FolderDelta};
     use chrono::Utc;
-    use flowy_folder_data_model::entities::{app::App, trash::Trash, view::View, workspace::Workspace};
+
+    use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
     use lib_ot::core::{OperationTransformable, PlainTextDelta, PlainTextDeltaBuilder};
 
     #[test]
@@ -446,11 +442,11 @@ mod tests {
         let (mut folder, initial_delta, _) = test_folder();
 
         let _time = Utc::now();
-        let mut workspace_1 = Workspace::default();
+        let mut workspace_1 = WorkspaceRevision::default();
         workspace_1.name = "My first workspace".to_owned();
         let delta_1 = folder.create_workspace(workspace_1).unwrap().unwrap().delta;
 
-        let mut workspace_2 = Workspace::default();
+        let mut workspace_2 = WorkspaceRevision::default();
         workspace_2.name = "My second workspace".to_owned();
         let delta_2 = folder.create_workspace(workspace_2).unwrap().unwrap().delta;
 
@@ -751,62 +747,68 @@ mod tests {
         );
     }
 
-    fn test_folder() -> (FolderPad, FolderDelta, Workspace) {
+    fn test_folder() -> (FolderPad, FolderDelta, WorkspaceRevision) {
         let mut folder = FolderPad::default();
         let folder_json = serde_json::to_string(&folder).unwrap();
         let mut delta = PlainTextDeltaBuilder::new().insert(&folder_json).build();
 
-        let mut workspace = Workspace::default();
-        workspace.name = "😁 my first workspace".to_owned();
-        workspace.id = "1".to_owned();
+        let mut workspace_rev = WorkspaceRevision::default();
+        workspace_rev.name = "😁 my first workspace".to_owned();
+        workspace_rev.id = "1".to_owned();
 
         delta = delta
-            .compose(&folder.create_workspace(workspace.clone()).unwrap().unwrap().delta)
+            .compose(&folder.create_workspace(workspace_rev.clone()).unwrap().unwrap().delta)
             .unwrap();
 
-        (folder, delta, workspace)
+        (folder, delta, workspace_rev)
     }
 
-    fn test_app_folder() -> (FolderPad, FolderDelta, App) {
+    fn test_app_folder() -> (FolderPad, FolderDelta, AppRevision) {
         let (mut folder, mut initial_delta, workspace) = test_folder();
-        let mut app = App::default();
-        app.workspace_id = workspace.id;
-        app.name = "😁 my first app".to_owned();
+        let mut app_rev = AppRevision::default();
+        app_rev.workspace_id = workspace.id;
+        app_rev.name = "😁 my first app".to_owned();
 
         initial_delta = initial_delta
-            .compose(&folder.create_app(app.clone()).unwrap().unwrap().delta)
+            .compose(&folder.create_app(app_rev.clone()).unwrap().unwrap().delta)
             .unwrap();
 
-        (folder, initial_delta, app)
+        (folder, initial_delta, app_rev)
     }
 
-    fn test_view_folder() -> (FolderPad, FolderDelta, View) {
+    fn test_view_folder() -> (FolderPad, FolderDelta, ViewRevision) {
         let (mut folder, mut initial_delta, app) = test_app_folder();
-        let mut view = View::default();
-        view.belong_to_id = app.id.clone();
-        view.name = "🎃 my first view".to_owned();
+        let mut view_rev = ViewRevision::default();
+        view_rev.belong_to_id = app.id.clone();
+        view_rev.name = "🎃 my first view".to_owned();
 
         initial_delta = initial_delta
-            .compose(&folder.create_view(view.clone()).unwrap().unwrap().delta)
+            .compose(&folder.create_view(view_rev.clone()).unwrap().unwrap().delta)
             .unwrap();
 
-        (folder, initial_delta, view)
+        (folder, initial_delta, view_rev)
     }
 
-    fn test_trash() -> (FolderPad, FolderDelta, Trash) {
+    fn test_trash() -> (FolderPad, FolderDelta, TrashRevision) {
         let mut folder = FolderPad::default();
         let folder_json = serde_json::to_string(&folder).unwrap();
         let mut delta = PlainTextDeltaBuilder::new().insert(&folder_json).build();
 
-        let mut trash = Trash::default();
-        trash.name = "🚽 my first trash".to_owned();
-        trash.id = "1".to_owned();
+        let mut trash_rev = TrashRevision::default();
+        trash_rev.name = "🚽 my first trash".to_owned();
+        trash_rev.id = "1".to_owned();
 
         delta = delta
-            .compose(&folder.create_trash(vec![trash.clone()]).unwrap().unwrap().delta)
+            .compose(
+                &folder
+                    .create_trash(vec![trash_rev.clone().into()])
+                    .unwrap()
+                    .unwrap()
+                    .delta,
+            )
             .unwrap();
 
-        (folder, delta, trash)
+        (folder, delta, trash_rev)
     }
 
     fn make_folder_from_delta(mut initial_delta: FolderDelta, deltas: Vec<PlainTextDelta>) -> FolderPad {

+ 1 - 1
shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

@@ -25,7 +25,7 @@ pub trait JsonDeserializer {
 
 impl GridMetaPad {
     pub async fn duplicate_grid_meta(&self) -> (Vec<FieldMeta>, Vec<GridBlockMeta>) {
-        let fields = self.grid_meta.fields.iter().cloned().collect::<Vec<FieldMeta>>();
+        let fields = self.grid_meta.fields.to_vec();
 
         let blocks = self
             .grid_meta

Some files were not shown because too many files changed in this diff