瀏覽代碼

feat: reload UI (#2999)

* chore: reload folder

* chore: reload folder

* chore: init sync

* chore: update tables

* chore: update database

* chore: load row

* chore: update

* chore: reload row

* test: fit test

* chore: retry

* chore: support batch fetch

* chore: enable sync

* chore: sync switch

* chore: sync switch

* chore: migration user data

* chore: migrate data

* chore: migrate folder

* chore: save user email

* chore: refresh user profile

* chore: fix test

* chore: delete translation files

* test: clippy format
Nathan.fooo 1 年之前
父節點
當前提交
f9e7b5ffa4
共有 100 個文件被更改,包括 1059 次插入705 次删除
  1. 0 30
      frontend/appflowy_flutter/lib/core/config/config.dart
  2. 7 0
      frontend/appflowy_flutter/lib/env/env.dart
  3. 10 10
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_cache.dart
  4. 11 8
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller.dart
  5. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart
  6. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/application/defines.dart
  7. 12 10
      frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart
  8. 59 83
      frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart
  9. 3 3
      frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_controller.dart
  10. 68 0
      frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_listener.dart
  11. 3 3
      frontend/appflowy_flutter/lib/plugins/database_view/application/view/view_cache.dart
  12. 5 2
      frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart
  13. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart
  14. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/unschedule_event_bloc.dart
  15. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart
  16. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart
  17. 30 3
      frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_bloc.dart
  18. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart
  19. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart
  20. 5 2
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart
  21. 4 4
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_bloc.dart
  22. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_cell_builder.dart
  23. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart
  24. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_action.dart
  25. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart
  26. 9 5
      frontend/appflowy_flutter/lib/plugins/document/document_page.dart
  27. 1 0
      frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
  28. 0 11
      frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart
  29. 2 3
      frontend/appflowy_flutter/lib/user/application/auth/appflowy_auth_service.dart
  30. 2 3
      frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart
  31. 4 1
      frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart
  32. 2 3
      frontend/appflowy_flutter/lib/user/application/user_service.dart
  33. 2 2
      frontend/appflowy_flutter/lib/workspace/application/menu/menu_bloc.dart
  34. 73 0
      frontend/appflowy_flutter/lib/workspace/application/settings/setting_supabase_bloc.dart
  35. 1 0
      frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart
  36. 1 1
      frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart
  37. 3 0
      frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart
  38. 30 0
      frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_view.dart
  39. 41 0
      frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_third_party_login.dart
  40. 15 0
      frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart
  41. 20 3
      frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_user_view.dart
  42. 9 2
      frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.dart
  43. 2 0
      frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.i.dart
  44. 1 1
      frontend/appflowy_flutter/test/bloc_test/board_test/util.dart
  45. 1 1
      frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart
  46. 2 10
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  47. 6 6
      frontend/appflowy_tauri/src-tauri/Cargo.toml
  48. 2 7
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts
  49. 8 0
      frontend/flowy-server-config/Cargo.toml
  50. 14 0
      frontend/flowy-server-config/src/lib.rs
  51. 2 1
      frontend/resources/translations/en.json
  52. 29 10
      frontend/rust-lib/Cargo.lock
  53. 6 5
      frontend/rust-lib/Cargo.toml
  54. 1 0
      frontend/rust-lib/dart-ffi/Cargo.toml
  55. 1 2
      frontend/rust-lib/dart-ffi/src/env_serde.rs
  56. 0 1
      frontend/rust-lib/dart-ffi/src/lib.rs
  57. 0 61
      frontend/rust-lib/flowy-config/src/entities.rs
  58. 1 10
      frontend/rust-lib/flowy-config/src/event_handler.rs
  59. 0 6
      frontend/rust-lib/flowy-config/src/event_map.rs
  60. 1 0
      frontend/rust-lib/flowy-core/Cargo.toml
  61. 3 3
      frontend/rust-lib/flowy-core/src/deps_resolve/collab_deps.rs
  62. 2 2
      frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs
  63. 2 2
      frontend/rust-lib/flowy-core/src/deps_resolve/document2_deps.rs
  64. 5 5
      frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs
  65. 1 0
      frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs
  66. 1 0
      frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs
  67. 117 46
      frontend/rust-lib/flowy-core/src/integrate/server.rs
  68. 33 8
      frontend/rust-lib/flowy-core/src/lib.rs
  69. 11 4
      frontend/rust-lib/flowy-database2/src/deps.rs
  70. 1 1
      frontend/rust-lib/flowy-database2/src/entities/macros.rs
  71. 0 9
      frontend/rust-lib/flowy-database2/src/entities/row_entities.rs
  72. 43 18
      frontend/rust-lib/flowy-database2/src/entities/view_entities.rs
  73. 187 108
      frontend/rust-lib/flowy-database2/src/manager.rs
  74. 4 0
      frontend/rust-lib/flowy-database2/src/notification.rs
  75. 27 67
      frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs
  76. 1 7
      frontend/rust-lib/flowy-database2/src/services/database/entities.rs
  77. 8 7
      frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs
  78. 13 25
      frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs
  79. 1 2
      frontend/rust-lib/flowy-database2/src/services/database_view/view_filter.rs
  80. 1 1
      frontend/rust-lib/flowy-database2/src/services/database_view/view_sort.rs
  81. 5 4
      frontend/rust-lib/flowy-database2/src/services/database_view/views.rs
  82. 1 2
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs
  83. 1 2
      frontend/rust-lib/flowy-database2/src/services/filter/controller.rs
  84. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/action.rs
  85. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/controller.rs
  86. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/controller_impls/checkbox_controller.rs
  87. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs
  88. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs
  89. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/single_select_controller.rs
  90. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/util.rs
  91. 1 2
      frontend/rust-lib/flowy-database2/src/services/group/controller_impls/url_controller.rs
  92. 1 3
      frontend/rust-lib/flowy-database2/src/services/group/entities.rs
  93. 1 1
      frontend/rust-lib/flowy-database2/src/services/group/group_builder.rs
  94. 1 2
      frontend/rust-lib/flowy-database2/src/services/sort/controller.rs
  95. 2 2
      frontend/rust-lib/flowy-database2/tests/database/database_editor.rs
  96. 4 1
      frontend/rust-lib/flowy-document2/src/deps.rs
  97. 1 1
      frontend/rust-lib/flowy-document2/src/document.rs
  98. 6 4
      frontend/rust-lib/flowy-document2/src/entities.rs
  99. 6 6
      frontend/rust-lib/flowy-document2/src/event_handler.rs
  100. 37 18
      frontend/rust-lib/flowy-document2/src/manager.rs

+ 0 - 30
frontend/appflowy_flutter/lib/core/config/config.dart

@@ -1,30 +0,0 @@
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/protobuf/flowy-config/entities.pb.dart';
-
-class Config {
-  static Future<void> setSupabaseConfig({
-    required String url,
-    required String anonKey,
-    required String key,
-    required String secret,
-    required String pgUrl,
-    required String pgUser,
-    required String pgPassword,
-    required String pgPort,
-  }) async {
-    final postgresConfig = PostgresConfigurationPB.create()
-      ..url = pgUrl
-      ..userName = pgUser
-      ..password = pgPassword
-      ..port = int.parse(pgPort);
-
-    await ConfigEventSetSupabaseConfig(
-      SupabaseConfigPB.create()
-        ..supabaseUrl = url
-        ..key = key
-        ..anonKey = anonKey
-        ..jwtSecret = secret
-        ..postgresConfig = postgresConfig,
-    ).send();
-  }
-}

+ 7 - 0
frontend/appflowy_flutter/lib/env/env.dart

@@ -68,6 +68,13 @@ abstract class Env {
     defaultValue: '5432',
   )
   static final String supabaseDbPort = _Env.supabaseDbPort;
+
+  @EnviedField(
+    obfuscate: true,
+    varName: 'ENABLE_SUPABASE_SYNC',
+    defaultValue: true,
+  )
+  static final bool enableSupabaseSync = _Env.enableSupabaseSync;
 }
 
 bool get isSupabaseEnable =>

+ 10 - 10
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_cache.dart

@@ -24,38 +24,38 @@ class CellCacheKey {
 /// We use GridCellCacheKey to index the cell in the cache.
 /// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid
 /// for more information
-class CellCache {
+class CellMemCache {
   final String viewId;
 
   /// fieldId: {cacheKey: GridCell}
-  final Map<String, Map<RowId, dynamic>> _cellDataByFieldId = {};
-  CellCache({
+  final Map<String, Map<RowId, dynamic>> _cellByFieldId = {};
+  CellMemCache({
     required this.viewId,
   });
 
   void removeCellWithFieldId(String fieldId) {
-    _cellDataByFieldId.remove(fieldId);
+    _cellByFieldId.remove(fieldId);
   }
 
   void remove(CellCacheKey key) {
-    final map = _cellDataByFieldId[key.fieldId];
+    final map = _cellByFieldId[key.fieldId];
     if (map != null) {
       map.remove(key.rowId);
     }
   }
 
   void insert<T extends DatabaseCell>(CellCacheKey key, T value) {
-    var map = _cellDataByFieldId[key.fieldId];
+    var map = _cellByFieldId[key.fieldId];
     if (map == null) {
-      _cellDataByFieldId[key.fieldId] = {};
-      map = _cellDataByFieldId[key.fieldId];
+      _cellByFieldId[key.fieldId] = {};
+      map = _cellByFieldId[key.fieldId];
     }
 
     map![key.rowId] = value.object;
   }
 
   T? get<T>(CellCacheKey key) {
-    final map = _cellDataByFieldId[key.fieldId];
+    final map = _cellByFieldId[key.fieldId];
     if (map == null) {
       return null;
     } else {
@@ -72,6 +72,6 @@ class CellCache {
   }
 
   Future<void> dispose() async {
-    _cellDataByFieldId.clear();
+    _cellByFieldId.clear();
   }
 }

+ 11 - 8
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller.dart

@@ -24,7 +24,7 @@ import 'cell_service.dart';
 // ignore: must_be_immutable
 class CellController<T, D> extends Equatable {
   DatabaseCellContext _cellContext;
-  final CellCache _cellCache;
+  final CellMemCache _cellCache;
   final CellCacheKey _cacheKey;
   final FieldBackendService _fieldBackendSvc;
   final CellDataLoader<T> _cellDataLoader;
@@ -54,7 +54,7 @@ class CellController<T, D> extends Equatable {
 
   CellController({
     required DatabaseCellContext cellContext,
-    required CellCache cellCache,
+    required CellMemCache cellCache,
     required CellDataLoader<T> cellDataLoader,
     required CellDataPersistence<D> cellDataPersistence,
   })  : _cellContext = cellContext,
@@ -103,12 +103,15 @@ class CellController<T, D> extends Equatable {
       },
     );
 
-    _rowMetaListener?.start(
-      callback: (newRowMeta) {
-        _cellContext = _cellContext.copyWith(rowMeta: newRowMeta);
-        _onRowMetaChanged?.call();
-      },
-    );
+    // Only the primary can listen on the row meta changes.
+    if (_cellContext.fieldInfo.isPrimary) {
+      _rowMetaListener?.start(
+        callback: (newRowMeta) {
+          _cellContext = _cellContext.copyWith(rowMeta: newRowMeta);
+          _onRowMetaChanged?.call();
+        },
+      );
+    }
   }
 
   /// Listen on the cell content or field changes

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_controller_builder.dart

@@ -18,11 +18,11 @@ typedef URLCellController = CellController<URLCellDataPB, String>;
 
 class CellControllerBuilder {
   final DatabaseCellContext _cellContext;
-  final CellCache _cellCache;
+  final CellMemCache _cellCache;
 
   CellControllerBuilder({
     required DatabaseCellContext cellContext,
-    required CellCache cellCache,
+    required CellMemCache cellCache,
   })  : _cellCache = cellCache,
         _cellContext = cellContext;
 

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/application/defines.dart

@@ -17,13 +17,13 @@ typedef OnDatabaseChanged = void Function(DatabasePB);
 typedef OnRowsCreated = void Function(List<RowId> ids);
 typedef OnRowsUpdated = void Function(
   List<RowId> ids,
-  RowsChangedReason reason,
+  ChangedReason reason,
 );
 typedef OnRowsDeleted = void Function(List<RowId> ids);
 typedef OnNumOfRowsChanged = void Function(
   UnmodifiableListView<RowInfo> rows,
   UnmodifiableMapView<RowId, RowInfo> rowByRowId,
-  RowsChangedReason reason,
+  ChangedReason reason,
 );
 
 typedef OnError = void Function(FlowyError);

+ 12 - 10
frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart

@@ -687,27 +687,29 @@ class FieldController {
   }
 }
 
-class RowDelegatesImpl extends RowFieldsDelegate with RowCacheDelegate {
-  final FieldController _cache;
+class RowCacheDependenciesImpl extends RowFieldsDelegate with RowLifeCycle {
+  final FieldController _fieldController;
   OnReceiveFields? _onFieldFn;
-  RowDelegatesImpl(FieldController cache) : _cache = cache;
+  RowCacheDependenciesImpl(FieldController cache) : _fieldController = cache;
 
   @override
   UnmodifiableListView<FieldInfo> get fields =>
-      UnmodifiableListView(_cache.fieldInfos);
+      UnmodifiableListView(_fieldController.fieldInfos);
 
   @override
   void onFieldsChanged(void Function(List<FieldInfo>) callback) {
-    _onFieldFn = (fieldInfos) {
-      callback(fieldInfos);
-    };
-    _cache.addListener(onReceiveFields: _onFieldFn);
+    if (_onFieldFn != null) {
+      _fieldController.removeListener(onFieldsListener: _onFieldFn!);
+    }
+
+    _onFieldFn = (fieldInfos) => callback(fieldInfos);
+    _fieldController.addListener(onReceiveFields: _onFieldFn);
   }
 
   @override
-  void onRowDispose() {
+  void onRowDisposed() {
     if (_onFieldFn != null) {
-      _cache.removeListener(onFieldsListener: _onFieldFn!);
+      _fieldController.removeListener(onFieldsListener: _onFieldFn!);
       _onFieldFn = null;
     }
   }

+ 59 - 83
frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_cache.dart

@@ -13,30 +13,25 @@ part 'row_cache.freezed.dart';
 
 typedef RowUpdateCallback = void Function();
 
+/// A delegate that provides the fields of the row.
 abstract class RowFieldsDelegate {
+  UnmodifiableListView<FieldInfo> get fields;
   void onFieldsChanged(void Function(List<FieldInfo>) callback);
 }
 
-abstract mixin class RowCacheDelegate {
-  UnmodifiableListView<FieldInfo> get fields;
-  void onRowDispose();
+abstract mixin class RowLifeCycle {
+  void onRowDisposed();
 }
 
-/// Cache the rows in memory
-/// Insert / delete / update row
-///
 /// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
 
 class RowCache {
   final String viewId;
-
-  /// _rows contains the current block's rows
-  /// Use List to reverse the order of the GridRow.
   final RowList _rowList = RowList();
-
-  final CellCache _cellCache;
-  final RowCacheDelegate _delegate;
-  final RowChangesetNotifier _rowChangeReasonNotifier;
+  final CellMemCache _cellMemCache;
+  final RowLifeCycle _rowLifeCycle;
+  final RowFieldsDelegate _fieldDelegate;
+  final RowChangesetNotifier _changedNotifier;
 
   /// Returns a unmodifiable list of RowInfo
   UnmodifiableListView<RowInfo> get rowInfos {
@@ -44,29 +39,29 @@ class RowCache {
     return UnmodifiableListView(visibleRows);
   }
 
-  /// Returns a unmodifiable map of rowId to RowInfo
+  /// Returns a unmodifiable map of RowInfo
   UnmodifiableMapView<RowId, RowInfo> get rowByRowId {
     return UnmodifiableMapView(_rowList.rowInfoByRowId);
   }
 
-  CellCache get cellCache => _cellCache;
-
-  RowsChangedReason get changeReason => _rowChangeReasonNotifier.reason;
+  CellMemCache get cellCache => _cellMemCache;
+  ChangedReason get changeReason => _changedNotifier.reason;
 
   RowCache({
     required this.viewId,
     required RowFieldsDelegate fieldsDelegate,
-    required RowCacheDelegate cacheDelegate,
-  })  : _cellCache = CellCache(viewId: viewId),
-        _rowChangeReasonNotifier = RowChangesetNotifier(),
-        _delegate = cacheDelegate {
-    //
+    required RowLifeCycle rowLifeCycle,
+  })  : _cellMemCache = CellMemCache(viewId: viewId),
+        _changedNotifier = RowChangesetNotifier(),
+        _rowLifeCycle = rowLifeCycle,
+        _fieldDelegate = fieldsDelegate {
+    // Listen on the changed of the fields. If the fields changed, we need to
+    // clear the cell cache with the given field id.
     fieldsDelegate.onFieldsChanged((fieldInfos) {
       for (final fieldInfo in fieldInfos) {
-        _cellCache.removeCellWithFieldId(fieldInfo.id);
+        _cellMemCache.removeCellWithFieldId(fieldInfo.id);
       }
-      _rowChangeReasonNotifier
-          .receive(const RowsChangedReason.fieldDidChange());
+      _changedNotifier.receive(const ChangedReason.fieldDidChange());
     });
   }
 
@@ -82,9 +77,9 @@ class RowCache {
   }
 
   Future<void> dispose() async {
-    _delegate.onRowDispose();
-    _rowChangeReasonNotifier.dispose();
-    await _cellCache.dispose();
+    _rowLifeCycle.onRowDisposed();
+    _changedNotifier.dispose();
+    await _cellMemCache.dispose();
   }
 
   void applyRowsChanged(RowsChangePB changeset) {
@@ -100,7 +95,7 @@ class RowCache {
 
   void reorderAllRows(List<String> rowIds) {
     _rowList.reorderWithRowIds(rowIds);
-    _rowChangeReasonNotifier.receive(const RowsChangedReason.reorderRows());
+    _changedNotifier.receive(const ChangedReason.reorderRows());
   }
 
   void reorderSingleRow(ReorderSingleRowPB reorderRow) {
@@ -111,8 +106,8 @@ class RowCache {
         reorderRow.oldIndex,
         reorderRow.newIndex,
       );
-      _rowChangeReasonNotifier.receive(
-        RowsChangedReason.reorderSingleRow(
+      _changedNotifier.receive(
+        ChangedReason.reorderSingleRow(
           reorderRow,
           rowInfo,
         ),
@@ -124,7 +119,7 @@ class RowCache {
     for (final rowId in deletedRowIds) {
       final deletedRow = _rowList.remove(rowId);
       if (deletedRow != null) {
-        _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
+        _changedNotifier.receive(ChangedReason.delete(deletedRow));
       }
     }
   }
@@ -134,8 +129,7 @@ class RowCache {
       final insertedIndex =
           _rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
       if (insertedIndex != null) {
-        _rowChangeReasonNotifier
-            .receive(RowsChangedReason.insert(insertedIndex));
+        _changedNotifier.receive(ChangedReason.insert(insertedIndex));
       }
     }
   }
@@ -149,7 +143,7 @@ class RowCache {
           fieldId: fieldId,
           rowId: updatedRow.rowId,
         );
-        _cellCache.remove(key);
+        _cellMemCache.remove(key);
       }
       if (updatedRow.hasRowMeta()) {
         updatedList.add(updatedRow.rowMeta);
@@ -160,7 +154,7 @@ class RowCache {
         _rowList.updateRows(updatedList, (rowId) => buildGridRow(rowId));
 
     if (updatedIndexs.isNotEmpty) {
-      _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
+      _changedNotifier.receive(ChangedReason.update(updatedIndexs));
     }
   }
 
@@ -168,7 +162,7 @@ class RowCache {
     for (final rowId in invisibleRows) {
       final deletedRow = _rowList.remove(rowId);
       if (deletedRow != null) {
-        _rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
+        _changedNotifier.receive(ChangedReason.delete(deletedRow));
       }
     }
   }
@@ -178,62 +172,45 @@ class RowCache {
       final insertedIndex =
           _rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
       if (insertedIndex != null) {
-        _rowChangeReasonNotifier
-            .receive(RowsChangedReason.insert(insertedIndex));
+        _changedNotifier.receive(ChangedReason.insert(insertedIndex));
       }
     }
   }
 
-  void onRowsChanged(void Function(RowsChangedReason) onRowChanged) {
-    _rowChangeReasonNotifier.addListener(() {
-      onRowChanged(_rowChangeReasonNotifier.reason);
+  void onRowsChanged(void Function(ChangedReason) onRowChanged) {
+    _changedNotifier.addListener(() {
+      onRowChanged(_changedNotifier.reason);
     });
   }
 
   RowUpdateCallback addListener({
     required RowId rowId,
-    void Function(CellContextByFieldId, RowsChangedReason)? onCellUpdated,
-    bool Function()? listenWhen,
+    void Function(CellContextByFieldId, ChangedReason)? onRowChanged,
   }) {
     listenerHandler() async {
-      if (listenWhen != null && listenWhen() == false) {
-        return;
-      }
-
-      notifyUpdate() {
-        if (onCellUpdated != null) {
-          final rowInfo = _rowList.get(rowId);
-          if (rowInfo != null) {
-            final CellContextByFieldId cellDataMap = _makeGridCells(
-              rowInfo.rowMeta,
-            );
-            onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
-          }
+      if (onRowChanged != null) {
+        final rowInfo = _rowList.get(rowId);
+        if (rowInfo != null) {
+          final cellDataMap = _makeCells(rowInfo.rowMeta);
+          onRowChanged(cellDataMap, _changedNotifier.reason);
         }
       }
-
-      _rowChangeReasonNotifier.reason.whenOrNull(
-        update: (indexs) {
-          if (indexs[rowId] != null) notifyUpdate();
-        },
-        fieldDidChange: () => notifyUpdate(),
-      );
     }
 
-    _rowChangeReasonNotifier.addListener(listenerHandler);
+    _changedNotifier.addListener(listenerHandler);
     return listenerHandler;
   }
 
   void removeRowListener(VoidCallback callback) {
-    _rowChangeReasonNotifier.removeListener(callback);
+    _changedNotifier.removeListener(callback);
   }
 
-  CellContextByFieldId loadGridCells(RowMetaPB rowMeta) {
+  CellContextByFieldId loadCells(RowMetaPB rowMeta) {
     final rowInfo = _rowList.get(rowMeta.id);
     if (rowInfo == null) {
       _loadRow(rowMeta.id);
     }
-    return _makeGridCells(rowMeta);
+    return _makeCells(rowMeta);
   }
 
   Future<void> _loadRow(RowId rowId) async {
@@ -257,18 +234,17 @@ class RowCache {
             rowId: rowMetaPB.id,
           );
 
-          _rowChangeReasonNotifier
-              .receive(RowsChangedReason.update(updatedIndexs));
+          _changedNotifier.receive(ChangedReason.update(updatedIndexs));
         }
       },
       (err) => Log.error(err),
     );
   }
 
-  CellContextByFieldId _makeGridCells(RowMetaPB rowMeta) {
+  CellContextByFieldId _makeCells(RowMetaPB rowMeta) {
     // ignore: prefer_collection_literals
     final cellContextMap = CellContextByFieldId();
-    for (final field in _delegate.fields) {
+    for (final field in _fieldDelegate.fields) {
       if (field.visibility) {
         cellContextMap[field.id] = DatabaseCellContext(
           rowMeta: rowMeta,
@@ -283,7 +259,7 @@ class RowCache {
   RowInfo buildGridRow(RowMetaPB rowMetaPB) {
     return RowInfo(
       viewId: viewId,
-      fields: _delegate.fields,
+      fields: _fieldDelegate.fields,
       rowId: rowMetaPB.id,
       rowMeta: rowMetaPB,
     );
@@ -291,11 +267,11 @@ class RowCache {
 }
 
 class RowChangesetNotifier extends ChangeNotifier {
-  RowsChangedReason reason = const InitialListState();
+  ChangedReason reason = const InitialListState();
 
   RowChangesetNotifier();
 
-  void receive(RowsChangedReason newReason) {
+  void receive(ChangedReason newReason) {
     reason = newReason;
     reason.map(
       insert: (_) => notifyListeners(),
@@ -326,14 +302,14 @@ typedef DeletedIndexs = List<DeletedIndex>;
 typedef UpdatedIndexMap = LinkedHashMap<RowId, UpdatedIndex>;
 
 @freezed
-class RowsChangedReason with _$RowsChangedReason {
-  const factory RowsChangedReason.insert(InsertedIndex item) = _Insert;
-  const factory RowsChangedReason.delete(DeletedIndex item) = _Delete;
-  const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
-  const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
-  const factory RowsChangedReason.initial() = InitialListState;
-  const factory RowsChangedReason.reorderRows() = _ReorderRows;
-  const factory RowsChangedReason.reorderSingleRow(
+class ChangedReason with _$ChangedReason {
+  const factory ChangedReason.insert(InsertedIndex item) = _Insert;
+  const factory ChangedReason.delete(DeletedIndex item) = _Delete;
+  const factory ChangedReason.update(UpdatedIndexMap indexs) = _Update;
+  const factory ChangedReason.fieldDidChange() = _FieldDidChange;
+  const factory ChangedReason.initial() = InitialListState;
+  const factory ChangedReason.reorderRows() = _ReorderRows;
+  const factory ChangedReason.reorderSingleRow(
     ReorderSingleRowPB reorderRow,
     RowInfo rowInfo,
   ) = _ReorderSingleRow;

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_data_controller.dart → frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_controller.dart

@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
 import '../cell/cell_service.dart';
 import 'row_cache.dart';
 
-typedef OnRowChanged = void Function(CellContextByFieldId, RowsChangedReason);
+typedef OnRowChanged = void Function(CellContextByFieldId, ChangedReason);
 
 class RowController {
   final RowMetaPB rowMeta;
@@ -24,13 +24,13 @@ class RowController {
   }) : _rowCache = rowCache;
 
   CellContextByFieldId loadData() {
-    return _rowCache.loadGridCells(rowMeta);
+    return _rowCache.loadCells(rowMeta);
   }
 
   void addListener({OnRowChanged? onRowChanged}) {
     final fn = _rowCache.addListener(
       rowId: rowMeta.id,
-      onCellUpdated: onRowChanged,
+      onRowChanged: onRowChanged,
     );
 
     // Add the listener to the list so that we can remove it later.

+ 68 - 0
frontend/appflowy_flutter/lib/plugins/database_view/application/row/row_listener.dart

@@ -0,0 +1,68 @@
+import 'dart:typed_data';
+
+import 'package:appflowy/core/notification/grid_notification.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
+import 'package:dartz/dartz.dart';
+
+typedef DidFetchRowCallback = void Function(DidFetchRowPB);
+typedef RowMetaCallback = void Function(RowMetaPB);
+
+class RowListener {
+  final String rowId;
+  DidFetchRowCallback? _onRowFetchedCallback;
+  RowMetaCallback? _onMetaChangedCallback;
+  DatabaseNotificationListener? _listener;
+  RowListener(this.rowId);
+
+  /// OnMetaChanged will be called when the row meta is changed.
+  /// OnRowFetched will be called when the row is fetched from remote storage
+  void start({
+    RowMetaCallback? onMetaChanged,
+    DidFetchRowCallback? onRowFetched,
+  }) {
+    _onMetaChangedCallback = onMetaChanged;
+    _onRowFetchedCallback = onRowFetched;
+    _listener = DatabaseNotificationListener(
+      objectId: rowId,
+      handler: _handler,
+    );
+  }
+
+  void _handler(
+    DatabaseNotification ty,
+    Either<Uint8List, FlowyError> result,
+  ) {
+    switch (ty) {
+      case DatabaseNotification.DidUpdateRowMeta:
+        result.fold(
+          (payload) {
+            if (_onMetaChangedCallback != null) {
+              _onMetaChangedCallback!(RowMetaPB.fromBuffer(payload));
+            }
+          },
+          (error) => Log.error(error),
+        );
+        break;
+      case DatabaseNotification.DidFetchRow:
+        result.fold(
+          (payload) {
+            if (_onRowFetchedCallback != null) {
+              _onRowFetchedCallback!(DidFetchRowPB.fromBuffer(payload));
+            }
+          },
+          (error) => Log.error(error),
+        );
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _listener?.stop();
+    _onMetaChangedCallback = null;
+    _onRowFetchedCallback = null;
+  }
+}

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/database_view/application/view/view_cache.dart

@@ -46,11 +46,11 @@ class DatabaseViewCache {
     required this.viewId,
     required FieldController fieldController,
   }) : _databaseViewListener = DatabaseViewListener(viewId: viewId) {
-    final delegate = RowDelegatesImpl(fieldController);
+    final depsImpl = RowCacheDependenciesImpl(fieldController);
     _rowCache = RowCache(
       viewId: viewId,
-      fieldsDelegate: delegate,
-      cacheDelegate: delegate,
+      fieldsDelegate: depsImpl,
+      rowLifeCycle: depsImpl,
     );
 
     _databaseViewListener.start(

+ 5 - 2
frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart

@@ -6,7 +6,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/database_controller.dart';
 import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
 import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
 import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
@@ -96,7 +96,10 @@ class BoardPage extends StatelessWidget {
                 (_) => BoardContent(
                   onEditStateChanged: onEditStateChanged,
                 ),
-                (err) => FlowyErrorPage.message(err.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
+                (err) => FlowyErrorPage.message(
+                  err.toString(),
+                  howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
+                ),
               );
             },
           );

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart

@@ -24,7 +24,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
   // Getters
   String get viewId => databaseController.viewId;
   FieldController get fieldController => databaseController.fieldController;
-  CellCache get cellCache => databaseController.rowCache.cellCache;
+  CellMemCache get cellCache => databaseController.rowCache.cellCache;
   RowCache get rowCache => databaseController.rowCache;
 
   CalendarBloc({required ViewPB view, required this.databaseController})

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/unschedule_event_bloc.dart

@@ -21,7 +21,7 @@ class UnscheduleEventsBloc
   // Getters
   String get viewId => databaseController.viewId;
   FieldController get fieldController => databaseController.fieldController;
-  CellCache get cellCache => databaseController.rowCache.cellCache;
+  CellMemCache get cellCache => databaseController.rowCache.cellCache;
   RowCache get rowCache => databaseController.rowCache;
 
   UnscheduleEventsBloc({

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart

@@ -14,7 +14,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 import '../../application/row/row_cache.dart';
-import '../../application/row/row_data_controller.dart';
+import '../../application/row/row_controller.dart';
 import '../../widgets/row/cell_builder.dart';
 import '../../widgets/row/row_detail.dart';
 import 'calendar_day.dart';

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/grid/application/grid_bloc.dart

@@ -153,7 +153,7 @@ class GridEvent with _$GridEvent {
   const factory GridEvent.moveRow(int from, int to) = _MoveRow;
   const factory GridEvent.didLoadRows(
     List<RowInfo> rows,
-    RowsChangedReason reason,
+    ChangedReason reason,
   ) = _DidReceiveRowUpdate;
   const factory GridEvent.didReceiveFieldUpdate(
     List<FieldInfo> fields,
@@ -179,7 +179,7 @@ class GridState with _$GridState {
     required int rowCount,
     required GridLoadingState loadingState,
     required bool reorderable,
-    required RowsChangedReason reason,
+    required ChangedReason reason,
     required List<SortInfo> sorts,
     required List<FilterInfo> filters,
   }) = _GridState;

+ 30 - 3
frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_bloc.dart

@@ -1,4 +1,6 @@
 import 'dart:collection';
+import 'package:appflowy/plugins/database_view/application/row/row_listener.dart';
+import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
 import 'package:equatable/equatable.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -7,7 +9,7 @@ import 'dart:async';
 import '../../../application/cell/cell_service.dart';
 import '../../../application/field/field_controller.dart';
 import '../../../application/row/row_cache.dart';
-import '../../../application/row/row_data_controller.dart';
+import '../../../application/row/row_controller.dart';
 import '../../../application/row/row_service.dart';
 
 part 'row_bloc.freezed.dart';
@@ -15,6 +17,7 @@ part 'row_bloc.freezed.dart';
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowBackendService _rowBackendSvc;
   final RowController _dataController;
+  final RowListener _rowListener;
   final String viewId;
   final String rowId;
 
@@ -24,6 +27,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
     required RowController dataController,
   })  : _rowBackendSvc = RowBackendService(viewId: viewId),
         _dataController = dataController,
+        _rowListener = RowListener(rowId),
         super(RowState.initial(dataController.loadData())) {
     on<RowEvent>(
       (event, emit) async {
@@ -46,6 +50,9 @@ class RowBloc extends Bloc<RowEvent, RowState> {
               ),
             );
           },
+          reloadRow: (DidFetchRowPB row) {
+            emit(state.copyWith(rowSource: RowSourece.remote(row)));
+          },
         );
       },
     );
@@ -54,6 +61,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
   @override
   Future<void> close() async {
     _dataController.dispose();
+    await _rowListener.stop();
     return super.close();
   }
 
@@ -65,6 +73,14 @@ class RowBloc extends Bloc<RowEvent, RowState> {
         }
       },
     );
+
+    _rowListener.start(
+      onRowFetched: (fetchRow) {
+        if (!isClosed) {
+          add(RowEvent.reloadRow(fetchRow));
+        }
+      },
+    );
   }
 }
 
@@ -72,9 +88,10 @@ class RowBloc extends Bloc<RowEvent, RowState> {
 class RowEvent with _$RowEvent {
   const factory RowEvent.initial() = _InitialRow;
   const factory RowEvent.createRow() = _CreateRow;
+  const factory RowEvent.reloadRow(DidFetchRowPB row) = _ReloadRow;
   const factory RowEvent.didReceiveCells(
     CellContextByFieldId cellsByFieldId,
-    RowsChangedReason reason,
+    ChangedReason reason,
   ) = _DidReceiveCells;
 }
 
@@ -83,7 +100,8 @@ class RowState with _$RowState {
   const factory RowState({
     required CellContextByFieldId cellByFieldId,
     required UnmodifiableListView<GridCellEquatable> cells,
-    RowsChangedReason? changeReason,
+    required RowSourece rowSource,
+    ChangedReason? changeReason,
   }) = _RowState;
 
   factory RowState.initial(
@@ -96,6 +114,7 @@ class RowState with _$RowState {
               .map((e) => GridCellEquatable(e.fieldInfo))
               .toList(),
         ),
+        rowSource: const RowSourece.disk(),
       );
 }
 
@@ -112,3 +131,11 @@ class GridCellEquatable extends Equatable {
         _fieldContext.width,
       ];
 }
+
+@freezed
+class RowSourece with _$RowSourece {
+  const factory RowSourece.disk() = _Disk;
+  const factory RowSourece.remote(
+    DidFetchRowPB row,
+  ) = _Remote;
+}

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/grid/application/row/row_detail_bloc.dart

@@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import '../../../application/cell/cell_service.dart';
 import '../../../application/field/field_service.dart';
-import '../../../application/row/row_data_controller.dart';
+import '../../../application/row/row_controller.dart';
 part 'row_detail_bloc.freezed.dart';
 
 class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart

@@ -18,7 +18,7 @@ import 'package:flutter/material.dart';
 import 'package:linked_scroll_controller/linked_scroll_controller.dart';
 import '../../application/field/field_controller.dart';
 import '../../application/row/row_cache.dart';
-import '../../application/row/row_data_controller.dart';
+import '../../application/row/row_controller.dart';
 import '../application/grid_bloc.dart';
 import '../../application/database_controller.dart';
 import 'grid_scroll.dart';

+ 5 - 2
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart

@@ -1,5 +1,5 @@
 import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
 import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
 import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
@@ -65,7 +65,9 @@ class _GridRowState extends State<GridRow> {
       child: _RowEnterRegion(
         child: BlocBuilder<RowBloc, RowState>(
           // The row need to rebuild when the cell count changes.
-          buildWhen: (p, c) => p.cellByFieldId.length != c.cellByFieldId.length,
+          buildWhen: (p, c) =>
+              p.cellByFieldId.length != c.cellByFieldId.length ||
+              p.rowSource != c.rowSource,
           builder: (context, state) {
             final content = Expanded(
               child: RowContent(
@@ -78,6 +80,7 @@ class _GridRowState extends State<GridRow> {
             );
 
             return Row(
+              key: ValueKey(state.rowSource),
               children: [
                 _RowLeading(
                   index: widget.index,

+ 4 - 4
frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_bloc.dart

@@ -29,7 +29,7 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
         _rowCache = rowCache,
         super(
           RowCardState.initial(
-            _makeCells(groupFieldId, rowCache.loadGridCells(rowMeta)),
+            _makeCells(groupFieldId, rowCache.loadCells(rowMeta)),
             isEditing,
           ),
         ) {
@@ -78,7 +78,7 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
   Future<void> _startListening() async {
     _rowCallback = _rowCache.addListener(
       rowId: rowMeta.id,
-      onCellUpdated: (cellMap, reason) {
+      onRowChanged: (cellMap, reason) {
         if (!isClosed) {
           final cells = _makeCells(groupFieldId, cellMap);
           add(RowCardEvent.didReceiveCells(cells, reason));
@@ -112,7 +112,7 @@ class RowCardEvent with _$RowCardEvent {
   const factory RowCardEvent.setIsEditing(bool isEditing) = _IsEditing;
   const factory RowCardEvent.didReceiveCells(
     List<DatabaseCellContext> cells,
-    RowsChangedReason reason,
+    ChangedReason reason,
   ) = _DidReceiveCells;
 }
 
@@ -121,7 +121,7 @@ class RowCardState with _$RowCardState {
   const factory RowCardState({
     required List<DatabaseCellContext> cells,
     required bool isEditing,
-    RowsChangedReason? changeReason,
+    ChangedReason? changeReason,
   }) = _RowCardState;
 
   factory RowCardState.initial(

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/card_cell_builder.dart

@@ -14,7 +14,7 @@ import 'cells/url_card_cell.dart';
 
 // T represents as the Generic card data
 class CardCellBuilder<CustomCardData> {
-  final CellCache cellCache;
+  final CellMemCache cellCache;
   final Map<FieldType, CardCellStyle>? styles;
 
   CardCellBuilder(this.cellCache, {this.styles});

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart

@@ -16,7 +16,7 @@ import 'cells/url_cell/url_cell.dart';
 
 /// Build the cell widget in Grid style.
 class GridCellBuilder {
-  final CellCache cellCache;
+  final CellMemCache cellCache;
   GridCellBuilder({
     required this.cellCache,
   });

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_action.dart

@@ -1,7 +1,7 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
 import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
 import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_detail.dart

@@ -1,6 +1,6 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
 import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
 import 'package:collection/collection.dart';

+ 9 - 5
frontend/appflowy_flutter/lib/plugins/document/document_page.dart

@@ -10,10 +10,11 @@ import 'package:appflowy/plugins/document/presentation/editor_style.dart';
 import 'package:appflowy/plugins/document/presentation/export_page_widget.dart';
 import 'package:appflowy/startup/startup.dart';
 import 'package:appflowy/util/base64_string.dart';
+import 'package:appflowy_backend/log.dart';
 import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart'
     hide DocumentEvent;
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
-import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/file_picker/file_picker_service.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -66,10 +67,13 @@ class _DocumentPageState extends State<DocumentPage> {
           return state.loadingState.when(
             loading: () => const SizedBox.shrink(),
             finish: (result) => result.fold(
-              (error) => FlowyErrorPage.message(
-                error.toString(),
-                howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
-              ),
+              (error) {
+                Log.error(error);
+                return FlowyErrorPage.message(
+                  error.toString(),
+                  howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
+                );
+              },
               (data) {
                 if (state.forceClose) {
                   widget.onDeleted();

+ 1 - 0
frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart

@@ -38,6 +38,7 @@ AppFlowyEnv getAppFlowyEnv() {
   );
 
   final supabaseConfig = SupabaseConfiguration(
+    enable_sync: Env.enableSupabaseSync,
     url: Env.supabaseUrl,
     key: Env.supabaseKey,
     jwt_secret: Env.supabaseJwtSecret,

+ 0 - 11
frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart

@@ -1,4 +1,3 @@
-import 'package:appflowy/core/config/config.dart';
 import 'package:appflowy/env/env.dart';
 import 'package:supabase_flutter/supabase_flutter.dart';
 
@@ -22,16 +21,6 @@ class InitSupabaseTask extends LaunchTask {
       debug: false,
     );
 
-    await Config.setSupabaseConfig(
-      url: Env.supabaseUrl,
-      key: Env.supabaseKey,
-      secret: Env.supabaseJwtSecret,
-      anonKey: Env.supabaseAnonKey,
-      pgPassword: Env.supabaseDbPassword,
-      pgPort: Env.supabaseDbPort,
-      pgUrl: Env.supabaseDb,
-      pgUser: Env.supabaseDbUser,
-    );
     isSupabaseInitialized = true;
   }
 }

+ 2 - 3
frontend/appflowy_flutter/lib/user/application/auth/appflowy_auth_service.dart

@@ -51,8 +51,7 @@ class AppFlowyAuthService implements AuthService {
     AuthTypePB authType = AuthTypePB.Local,
     Map<String, String> map = const {},
   }) async {
-    final payload = SignOutPB()..authType = authType;
-    await UserEventSignOut(payload).send();
+    await UserEventSignOut().send();
     return;
   }
 
@@ -61,7 +60,7 @@ class AppFlowyAuthService implements AuthService {
     AuthTypePB authType = AuthTypePB.Local,
     Map<String, String> map = const {},
   }) {
-    const password = "AppFlowy123@";
+    const password = "Guest!@123456";
     final uid = uuid();
     final userEmail = "[email protected]";
     return signUp(

+ 2 - 3
frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart

@@ -8,6 +8,7 @@ class AuthServiceMapKeys {
 
   // for supabase auth use only.
   static const String uuid = 'uuid';
+  static const String email = 'email';
 }
 
 abstract class AuthService {
@@ -42,9 +43,7 @@ abstract class AuthService {
   });
 
   ///
-  Future<void> signOut({
-    AuthTypePB authType,
-  });
+  Future<void> signOut();
 
   /// Returns [UserProfilePB] if the user has sign in, otherwise returns null.
   Future<Either<FlowyError, UserProfilePB>> getUser();

+ 4 - 1
frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart

@@ -115,7 +115,10 @@ class SupabaseAuthService implements AuthService {
         completer.complete(left(AuthError.supabaseSignInWithOauthError));
       } else {
         final Either<FlowyError, UserProfilePB> response = await setupAuth(
-          map: {AuthServiceMapKeys.uuid: user.id},
+          map: {
+            AuthServiceMapKeys.uuid: user.id,
+            AuthServiceMapKeys.email: user.email ?? user.newEmail ?? ''
+          },
         );
         completer.complete(response);
       }

+ 2 - 3
frontend/appflowy_flutter/lib/user/application/user_service.dart

@@ -58,9 +58,8 @@ class UserBackendService {
     throw UnimplementedError();
   }
 
-  Future<Either<Unit, FlowyError>> signOut(AuthTypePB authType) {
-    final payload = SignOutPB()..authType = authType;
-    return UserEventSignOut(payload).send();
+  Future<Either<Unit, FlowyError>> signOut() {
+    return UserEventSignOut().send();
   }
 
   Future<Either<Unit, FlowyError>> initUser() async {

+ 2 - 2
frontend/appflowy_flutter/lib/workspace/application/menu/menu_bloc.dart

@@ -85,9 +85,9 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
 
   // ignore: unused_element
   Future<void> _fetchApps(Emitter<MenuState> emit) async {
-    final appsOrFail = await _workspaceService.getViews();
+    final viewsOrError = await _workspaceService.getViews();
     emit(
-      appsOrFail.fold(
+      viewsOrError.fold(
         (views) => state.copyWith(views: views),
         (error) {
           Log.error(error);

+ 73 - 0
frontend/appflowy_flutter/lib/workspace/application/settings/setting_supabase_bloc.dart

@@ -0,0 +1,73 @@
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:dartz/dartz.dart';
+import 'package:protobuf/protobuf.dart';
+
+part 'setting_supabase_bloc.freezed.dart';
+
+class SettingSupabaseBloc
+    extends Bloc<SettingSupabaseEvent, SettingSupabaseState> {
+  SettingSupabaseBloc() : super(SettingSupabaseState.initial()) {
+    on<SettingSupabaseEvent>((event, emit) async {
+      await event.when(
+        initial: () async {
+          await getSupabaseConfig();
+        },
+        enableSync: (bool enable) async {
+          final oldConfig = state.config;
+          if (oldConfig != null) {
+            oldConfig.freeze();
+            final newConfig = oldConfig.rebuild((config) {
+              config.enableSync = enable;
+            });
+            updateSupabaseConfig(newConfig);
+            emit(state.copyWith(config: newConfig));
+          }
+        },
+        didReceiveSupabseConfig: (SupabaseConfigPB config) {
+          emit(state.copyWith(config: config));
+        },
+      );
+    });
+  }
+
+  Future<void> updateSupabaseConfig(SupabaseConfigPB config) async {
+    await UserEventSetSupabaseConfig(config).send();
+  }
+
+  Future<void> getSupabaseConfig() async {
+    final result = await UserEventGetSupabaseConfig().send();
+    result.fold(
+      (config) {
+        if (!isClosed) {
+          add(SettingSupabaseEvent.didReceiveSupabseConfig(config));
+        }
+      },
+      (r) => Log.error(r),
+    );
+  }
+}
+
+@freezed
+class SettingSupabaseEvent with _$SettingSupabaseEvent {
+  const factory SettingSupabaseEvent.initial() = _Initial;
+  const factory SettingSupabaseEvent.didReceiveSupabseConfig(
+    SupabaseConfigPB config,
+  ) = _DidReceiveSupabaseConfig;
+  const factory SettingSupabaseEvent.enableSync(bool enable) = _EnableSync;
+}
+
+@freezed
+class SettingSupabaseState with _$SettingSupabaseState {
+  const factory SettingSupabaseState({
+    SupabaseConfigPB? config,
+    required Either<Unit, String> successOrFailure,
+  }) = _SettingSupabaseState;
+
+  factory SettingSupabaseState.initial() => SettingSupabaseState(
+        successOrFailure: left(unit),
+      );
+}

+ 1 - 0
frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart

@@ -13,6 +13,7 @@ enum SettingsPage {
   language,
   files,
   user,
+  supabaseSetting,
 }
 
 class SettingsDialogBloc

+ 1 - 1
frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart

@@ -53,7 +53,7 @@ class WorkspaceService {
     final payload = WorkspaceIdPB.create()..value = workspaceId;
     return FolderEventReadWorkspaceViews(payload).send().then((result) {
       return result.fold(
-        (apps) => left(apps.items),
+        (views) => left(views.items),
         (error) => right(error),
       );
     });

+ 3 - 0
frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart

@@ -1,5 +1,6 @@
 import 'package:appflowy/startup/startup.dart';
 import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_view.dart';
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_system_view.dart';
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
@@ -85,6 +86,8 @@ class SettingsDialog extends StatelessWidget {
         return const SettingsFileSystemView();
       case SettingsPage.user:
         return SettingsUserView(user);
+      case SettingsPage.supabaseSetting:
+        return const SupabaseSettingView();
       default:
         return Container();
     }

+ 30 - 0
frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_supabase_view.dart

@@ -0,0 +1,30 @@
+import 'package:appflowy/workspace/application/settings/setting_supabase_bloc.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class SupabaseSettingView extends StatelessWidget {
+  const SupabaseSettingView({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) =>
+          SettingSupabaseBloc()..add(const SettingSupabaseEvent.initial()),
+      child: BlocBuilder<SettingSupabaseBloc, SettingSupabaseState>(
+        builder: (context, state) {
+          return Align(
+            alignment: Alignment.topRight,
+            child: Switch(
+              onChanged: (bool value) {
+                context.read<SettingSupabaseBloc>().add(
+                      SettingSupabaseEvent.enableSync(value),
+                    );
+              },
+              value: state.config?.enableSync ?? false,
+            ),
+          );
+        },
+      ),
+    );
+  }
+}

+ 41 - 0
frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_third_party_login.dart

@@ -0,0 +1,41 @@
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy/user/application/sign_in_bloc.dart';
+import 'package:appflowy/user/presentation/sign_in_screen.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class SettingThirdPartyLogin extends StatelessWidget {
+  const SettingThirdPartyLogin({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) => getIt<SignInBloc>(),
+      child: BlocConsumer<SignInBloc, SignInState>(
+        listener: (context, state) {
+          state.successOrFail.fold(
+            () => null,
+            (result) => _handleSuccessOrFail(result, context),
+          );
+        },
+        builder: (_, __) => const ThirdPartySignInButtons(),
+      ),
+    );
+  }
+
+  void _handleSuccessOrFail(
+    Either<UserProfilePB, FlowyError> result,
+    BuildContext context,
+  ) {
+    result.fold(
+      (user) {
+        // TODO(Lucas): push to home screen
+      },
+      (error) => showSnapBar(context, error.msg),
+    );
+  }
+}

+ 15 - 0
frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart

@@ -1,8 +1,11 @@
+import 'package:appflowy/env/env.dart';
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
 class SettingsMenu extends StatelessWidget {
   const SettingsMenu({
@@ -55,6 +58,18 @@ class SettingsMenu extends StatelessWidget {
           icon: Icons.account_box_outlined,
           changeSelectedPage: changeSelectedPage,
         ),
+
+        // Only show supabase setting if supabase is enabled and the current auth type is not local
+        if (isSupabaseEnable &&
+            context.read<SettingsDialogBloc>().state.userProfile.authType !=
+                AuthTypePB.Local)
+          SettingsMenuElement(
+            page: SettingsPage.supabaseSetting,
+            selectedPage: currentPage,
+            label: LocaleKeys.settings_menu_supabaseSetting.tr(),
+            icon: Icons.sync,
+            changeSelectedPage: changeSelectedPage,
+          ),
       ],
     );
   }

+ 20 - 3
frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_user_view.dart

@@ -1,6 +1,7 @@
 import 'dart:convert';
 import 'dart:async';
 
+import 'package:appflowy/env/env.dart';
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/startup/entry_point.dart';
 import 'package:appflowy/startup/startup.dart';
@@ -15,6 +16,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import 'setting_third_party_login.dart';
+
 const defaultUserAvatar = '1F600';
 const _iconSize = Size(60, 60);
 
@@ -38,7 +41,7 @@ class SettingsUserView extends StatelessWidget {
             const VSpace(20),
             _renderCurrentOpenaiKey(context),
             const Spacer(),
-            _renderLogoutButton(context),
+            _renderLoginOrLogoutButton(context, state),
             const VSpace(20),
           ],
         ),
@@ -46,6 +49,21 @@ class SettingsUserView extends StatelessWidget {
     );
   }
 
+  Widget _renderLoginOrLogoutButton(
+    BuildContext context,
+    SettingsUserState state,
+  ) {
+    if (!isSupabaseEnable) {
+      return _renderLogoutButton(context);
+    }
+
+    if (state.userProfile.authType == AuthTypePB.Local) {
+      return const SettingThirdPartyLogin();
+    } else {
+      return _renderLogoutButton(context);
+    }
+  }
+
   Widget _renderUserNameInput(BuildContext context) {
     final String name =
         context.read<SettingsUserViewBloc>().state.userProfile.name;
@@ -74,8 +92,7 @@ class SettingsUserView extends StatelessWidget {
         'Logout',
       ),
       onTap: () async {
-        await getIt<AuthService>().signOut(authType: AuthTypePB.Supabase);
-        await getIt<AuthService>().signOut(authType: AuthTypePB.Local);
+        await getIt<AuthService>().signOut();
         await FlowyRunner.run(
           FlowyApp(),
           integrationEnv(),

+ 9 - 2
frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.dart

@@ -1,6 +1,8 @@
 import 'package:json_annotation/json_annotation.dart';
 
-// Run `dart run build_runner build` to generate the json serialization
+// Run `dart run build_runner build` to generate the json serialization If the
+// file `env_serde.i.dart` is existed, delete it first.
+//
 // the file `env_serde.g.dart` will be generated in the same directory. Rename
 // the file to `env_serde.i.dart` because the file is ignored by default.
 part 'env_serde.i.dart';
@@ -9,7 +11,9 @@ part 'env_serde.i.dart';
 class AppFlowyEnv {
   final SupabaseConfiguration supabase_config;
 
-  AppFlowyEnv({required this.supabase_config});
+  AppFlowyEnv({
+    required this.supabase_config,
+  });
 
   factory AppFlowyEnv.fromJson(Map<String, dynamic> json) =>
       _$AppFlowyEnvFromJson(json);
@@ -19,12 +23,15 @@ class AppFlowyEnv {
 
 @JsonSerializable()
 class SupabaseConfiguration {
+  /// Indicates whether the sync feature is enabled.
+  final bool enable_sync;
   final String url;
   final String key;
   final String jwt_secret;
   final PostgresConfiguration postgres_config;
 
   SupabaseConfiguration({
+    this.enable_sync = true,
     required this.url,
     required this.key,
     required this.jwt_secret,

+ 2 - 0
frontend/appflowy_flutter/packages/appflowy_backend/lib/env_serde.i.dart

@@ -19,6 +19,7 @@ Map<String, dynamic> _$AppFlowyEnvToJson(AppFlowyEnv instance) =>
 SupabaseConfiguration _$SupabaseConfigurationFromJson(
         Map<String, dynamic> json) =>
     SupabaseConfiguration(
+      enable_sync: json['enable_sync'] as bool? ?? true,
       url: json['url'] as String,
       key: json['key'] as String,
       jwt_secret: json['jwt_secret'] as String,
@@ -29,6 +30,7 @@ SupabaseConfiguration _$SupabaseConfigurationFromJson(
 Map<String, dynamic> _$SupabaseConfigurationToJson(
         SupabaseConfiguration instance) =>
     <String, dynamic>{
+      'enable_sync': instance.enable_sync,
       'url': instance.url,
       'key': instance.key,
       'jwt_secret': instance.jwt_secret,

+ 1 - 1
frontend/appflowy_flutter/test/bloc_test/board_test/util.dart

@@ -5,7 +5,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_editor_bl
 import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
 import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
 import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
 import 'package:appflowy/plugins/database_view/board/board.dart';
 import 'package:appflowy/plugins/database_view/application/database_controller.dart';
 import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';

+ 1 - 1
frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart

@@ -6,7 +6,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
 import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
 import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
 import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
 import 'package:appflowy/plugins/database_view/application/database_controller.dart';
 import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
 import 'package:appflowy/workspace/application/view/view_service.dart';

+ 2 - 10
frontend/appflowy_tauri/src-tauri/Cargo.lock

@@ -105,7 +105,6 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "anyhow",
  "collab",
@@ -1030,7 +1029,6 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "anyhow",
  "bytes",
@@ -1048,7 +1046,6 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -1066,7 +1063,6 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1093,7 +1089,6 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1105,7 +1100,6 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "anyhow",
  "collab",
@@ -1124,7 +1118,6 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "anyhow",
  "chrono",
@@ -1144,7 +1137,6 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "bincode",
  "chrono",
@@ -1164,7 +1156,6 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1198,7 +1189,6 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
 dependencies = [
  "bytes",
  "collab",
@@ -2072,6 +2062,8 @@ dependencies = [
  "async-stream",
  "bytes",
  "chrono",
+ "collab-document",
+ "collab-folder",
  "config",
  "deadpool-postgres",
  "flowy-database2",

+ 6 - 6
frontend/appflowy_tauri/src-tauri/Cargo.toml

@@ -34,12 +34,12 @@ default = ["custom-protocol"]
 custom-protocol = ["tauri/custom-protocol"]
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
 
 #collab = { path = "../../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

+ 2 - 7
frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts

@@ -2,7 +2,6 @@ import { nanoid } from '@reduxjs/toolkit';
 import {
   AppearanceSettingsPB,
   AuthTypePB,
-  SignOutPB,
   ThemeModePB,
   UserEventGetAppearanceSetting,
   UserEventGetUserProfile,
@@ -91,9 +90,7 @@ export class UserBackendService {
   };
 
   signOut = () => {
-    const payload = SignOutPB.fromObject({ auth_type: AuthTypePB.Local });
-
-    return UserEventSignOut(payload);
+    return UserEventSignOut();
   };
 
   setAppearanceSettings = (params: ReturnType<typeof AppearanceSettingsPB.prototype.toObject>) => {
@@ -125,9 +122,7 @@ export class AuthBackendService {
   };
 
   signOut = () => {
-    const payload = SignOutPB.fromObject({ auth_type: AuthTypePB.Local });
-
-    return UserEventSignOut(payload);
+    return UserEventSignOut();
   };
 
   autoSignUp = () => {

+ 8 - 0
frontend/flowy-server-config/Cargo.toml

@@ -0,0 +1,8 @@
+[package]
+name = "flowy-server-config"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]

+ 14 - 0
frontend/flowy-server-config/src/lib.rs

@@ -0,0 +1,14 @@
+pub fn add(left: usize, right: usize) -> usize {
+    left + right
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn it_works() {
+        let result = add(2, 2);
+        assert_eq!(result, 4);
+    }
+}

+ 2 - 1
frontend/resources/translations/en.json

@@ -198,7 +198,8 @@
       "language": "Language",
       "user": "User",
       "files": "Files",
-      "open": "Open Settings"
+      "open": "Open Settings",
+      "supabaseSetting": "Supabase Setting"
     },
     "appearance": {
       "fontFamily": {

+ 29 - 10
frontend/rust-lib/Cargo.lock

@@ -85,7 +85,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "anyhow",
  "collab",
@@ -897,7 +897,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "anyhow",
  "bytes",
@@ -915,7 +915,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -933,7 +933,7 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -960,7 +960,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -972,7 +972,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "anyhow",
  "collab",
@@ -991,7 +991,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "anyhow",
  "chrono",
@@ -1011,7 +1011,7 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "bincode",
  "chrono",
@@ -1031,7 +1031,7 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1065,7 +1065,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2134c0#2134c0f27b8a9f3077e25ae928f2420c926506cc"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=2eb044#2eb044356ba49b26382c4aa9f1fd03d7663e84d3"
 dependencies = [
  "bytes",
  "collab",
@@ -1314,6 +1314,7 @@ dependencies = [
  "flowy-net",
  "flowy-notification",
  "flowy-server",
+ "flowy-server-config",
  "lazy_static",
  "lib-dispatch",
  "log",
@@ -1670,6 +1671,7 @@ dependencies = [
  "flowy-folder2",
  "flowy-net",
  "flowy-server",
+ "flowy-server-config",
  "flowy-sqlite",
  "flowy-task",
  "flowy-user",
@@ -1868,6 +1870,8 @@ dependencies = [
  "async-stream",
  "bytes",
  "chrono",
+ "collab-document",
+ "collab-folder",
  "config",
  "deadpool-postgres",
  "dotenv",
@@ -1875,6 +1879,7 @@ dependencies = [
  "flowy-document2",
  "flowy-error",
  "flowy-folder2",
+ "flowy-server-config",
  "flowy-user",
  "futures",
  "futures-util",
@@ -1898,6 +1903,14 @@ dependencies = [
  "uuid",
 ]
 
+[[package]]
+name = "flowy-server-config"
+version = "0.1.0"
+dependencies = [
+ "flowy-error",
+ "serde",
+]
+
 [[package]]
 name = "flowy-sqlite"
 version = "0.1.0"
@@ -1952,6 +1965,7 @@ dependencies = [
  "flowy-net",
  "flowy-notification",
  "flowy-server",
+ "flowy-server-config",
  "flowy-user",
  "futures-util",
  "lib-dispatch",
@@ -1965,6 +1979,7 @@ dependencies = [
  "tempdir",
  "thread-id",
  "tokio",
+ "tokio-postgres",
  "tracing",
  "uuid",
 ]
@@ -1975,6 +1990,8 @@ version = "0.1.0"
 dependencies = [
  "appflowy-integrate",
  "bytes",
+ "collab",
+ "collab-folder",
  "diesel",
  "diesel_derives",
  "fake",
@@ -1983,6 +2000,7 @@ dependencies = [
  "flowy-derive",
  "flowy-error",
  "flowy-notification",
+ "flowy-server-config",
  "flowy-sqlite",
  "lazy_static",
  "lib-dispatch",
@@ -2004,6 +2022,7 @@ dependencies = [
  "tokio",
  "tracing",
  "unicode-segmentation",
+ "uuid",
  "validator",
 ]
 

+ 6 - 5
frontend/rust-lib/Cargo.toml

@@ -15,6 +15,7 @@ members = [
   "flowy-database2",
   "flowy-task",
   "flowy-server",
+  "flowy-server-config",
   "flowy-config",
 ]
 
@@ -33,11 +34,11 @@ opt-level = 3
 incremental = false
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2134c0" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2eb044" }
 
 #collab = { path = "../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }

+ 1 - 0
frontend/rust-lib/dart-ffi/Cargo.toml

@@ -32,6 +32,7 @@ flowy-notification = { path = "../flowy-notification" }
 flowy-net = { path = "../flowy-net" }
 flowy-derive = { path = "../../../shared-lib/flowy-derive" }
 flowy-server = { path = "../flowy-server" }
+flowy-server-config = { path = "../flowy-server-config" }
 
 [features]
 default = ["dart", "rev-sqlite"]

+ 1 - 2
frontend/rust-lib/dart-ffi/src/env_serde.rs

@@ -1,6 +1,6 @@
 use serde::Deserialize;
 
-use flowy_server::supabase::SupabaseConfiguration;
+use flowy_server_config::supabase_config::SupabaseConfiguration;
 
 #[derive(Deserialize, Debug)]
 pub struct AppFlowyEnv {
@@ -10,7 +10,6 @@ pub struct AppFlowyEnv {
 impl AppFlowyEnv {
   pub fn parser(env_str: &str) {
     if let Ok(env) = serde_json::from_str::<AppFlowyEnv>(env_str) {
-      tracing::trace!("{:?}", env);
       env.supabase_config.write_env();
     }
   }

+ 0 - 1
frontend/rust-lib/dart-ffi/src/lib.rs

@@ -37,7 +37,6 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
   let config =
     AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string()).log_filter("info", log_crates);
   *APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
-
   0
 }
 

+ 0 - 61
frontend/rust-lib/flowy-config/src/entities.rs

@@ -2,7 +2,6 @@ use appflowy_integrate::config::AWSDynamoDBConfig;
 
 use flowy_derive::ProtoBuf;
 use flowy_error::FlowyError;
-use flowy_server::supabase::{PostgresConfiguration, SupabaseConfiguration};
 
 #[derive(Default, ProtoBuf)]
 pub struct KeyValuePB {
@@ -19,38 +18,6 @@ pub struct KeyPB {
   pub key: String,
 }
 
-#[derive(Default, ProtoBuf)]
-pub struct SupabaseConfigPB {
-  #[pb(index = 1)]
-  supabase_url: String,
-
-  #[pb(index = 2)]
-  anon_key: String,
-
-  #[pb(index = 3)]
-  key: String,
-
-  #[pb(index = 4)]
-  jwt_secret: String,
-
-  #[pb(index = 5)]
-  pub postgres_config: PostgresConfigurationPB,
-}
-
-impl TryFrom<SupabaseConfigPB> for SupabaseConfiguration {
-  type Error = FlowyError;
-
-  fn try_from(config: SupabaseConfigPB) -> Result<Self, Self::Error> {
-    let postgres_config = PostgresConfiguration::try_from(config.postgres_config)?;
-    Ok(SupabaseConfiguration {
-      url: config.supabase_url,
-      key: config.key,
-      jwt_secret: config.jwt_secret,
-      postgres_config,
-    })
-  }
-}
-
 #[derive(Default, ProtoBuf)]
 pub struct CollabPluginConfigPB {
   #[pb(index = 1, one_of)]
@@ -81,31 +48,3 @@ impl TryFrom<AWSDynamoDBConfigPB> for AWSDynamoDBConfig {
     })
   }
 }
-
-#[derive(Default, ProtoBuf)]
-pub struct PostgresConfigurationPB {
-  #[pb(index = 1)]
-  pub url: String,
-
-  #[pb(index = 2)]
-  pub user_name: String,
-
-  #[pb(index = 3)]
-  pub password: String,
-
-  #[pb(index = 4)]
-  pub port: u32,
-}
-
-impl TryFrom<PostgresConfigurationPB> for PostgresConfiguration {
-  type Error = FlowyError;
-
-  fn try_from(config: PostgresConfigurationPB) -> Result<Self, Self::Error> {
-    Ok(Self {
-      url: config.url,
-      user_name: config.user_name,
-      password: config.password,
-      port: config.port as u16,
-    })
-  }
-}

+ 1 - 10
frontend/rust-lib/flowy-config/src/event_handler.rs

@@ -1,11 +1,10 @@
 use appflowy_integrate::config::AWSDynamoDBConfig;
 
 use flowy_error::{FlowyError, FlowyResult};
-use flowy_server::supabase::SupabaseConfiguration;
 use flowy_sqlite::kv::KV;
 use lib_dispatch::prelude::{data_result_ok, AFPluginData, DataResult};
 
-use crate::entities::{CollabPluginConfigPB, KeyPB, KeyValuePB, SupabaseConfigPB};
+use crate::entities::{CollabPluginConfigPB, KeyPB, KeyValuePB};
 
 pub(crate) async fn set_key_value_handler(data: AFPluginData<KeyValuePB>) -> FlowyResult<()> {
   let data = data.into_inner();
@@ -35,14 +34,6 @@ pub(crate) async fn remove_key_value_handler(data: AFPluginData<KeyPB>) -> Flowy
   Ok(())
 }
 
-pub(crate) async fn set_supabase_config_handler(
-  data: AFPluginData<SupabaseConfigPB>,
-) -> FlowyResult<()> {
-  let config = SupabaseConfiguration::try_from(data.into_inner())?;
-  config.write_env();
-  Ok(())
-}
-
 pub(crate) async fn set_collab_plugin_config_handler(
   data: AFPluginData<CollabPluginConfigPB>,
 ) -> FlowyResult<()> {

+ 0 - 6
frontend/rust-lib/flowy-config/src/event_map.rs

@@ -11,7 +11,6 @@ pub fn init() -> AFPlugin {
     .event(ConfigEvent::SetKeyValue, set_key_value_handler)
     .event(ConfigEvent::GetKeyValue, get_key_value_handler)
     .event(ConfigEvent::RemoveKeyValue, remove_key_value_handler)
-    .event(ConfigEvent::SetSupabaseConfig, set_supabase_config_handler)
     .event(
       ConfigEvent::SetCollabPluginConfig,
       set_collab_plugin_config_handler,
@@ -30,11 +29,6 @@ pub enum ConfigEvent {
   #[event(input = "KeyPB")]
   RemoveKeyValue = 2,
 
-  /// Set the supabase config. It will be written to the environment variables.
-  /// Check out the `write_to_env` of [SupabaseConfigPB].
-  #[event(input = "SupabaseConfigPB")]
-  SetSupabaseConfig = 3,
-
   #[event(input = "CollabPluginConfigPB")]
   SetCollabPluginConfig = 4,
 }

+ 1 - 0
frontend/rust-lib/flowy-core/Cargo.toml

@@ -17,6 +17,7 @@ flowy-document2 = { path = "../flowy-document2" }
 flowy-error = { path = "../flowy-error" }
 flowy-task = { path = "../flowy-task" }
 flowy-server = { path = "../flowy-server" }
+flowy-server-config = { path = "../flowy-server-config" }
 flowy-config = { path = "../flowy-config" }
 appflowy-integrate = { version = "0.1.0" }
 diesel = { version = "1.4.8", features = ["sqlite"] }

+ 3 - 3
frontend/rust-lib/flowy-core/src/deps_resolve/collab_deps.rs

@@ -17,11 +17,11 @@ use lib_infra::util::timestamp;
 pub struct SnapshotDBImpl(pub Weak<UserSession>);
 
 impl SnapshotPersistence for SnapshotDBImpl {
-  fn get_snapshots(&self, _uid: i64, object_id: &str) -> Vec<CollabSnapshot> {
+  fn get_snapshots(&self, uid: i64, object_id: &str) -> Vec<CollabSnapshot> {
     match self.0.upgrade() {
       None => vec![],
       Some(user_session) => user_session
-        .db_pool()
+        .db_pool(uid)
         .and_then(|pool| Ok(pool.get()?))
         .and_then(|conn| {
           CollabSnapshotTableSql::get_all_snapshots(object_id, &conn)
@@ -43,7 +43,7 @@ impl SnapshotPersistence for SnapshotDBImpl {
     tokio::task::spawn_blocking(move || {
       if let Some(pool) = weak_user_session
         .upgrade()
-        .and_then(|user_session| user_session.db_pool().ok())
+        .and_then(|user_session| user_session.db_pool(uid).ok())
       {
         let conn = pool
           .get()

+ 2 - 2
frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs

@@ -47,11 +47,11 @@ impl DatabaseUser2 for DatabaseUserImpl {
       .token()
   }
 
-  fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError> {
+  fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError> {
     self
       .0
       .upgrade()
       .ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
-      .get_collab_db()
+      .get_collab_db(uid)
   }
 }

+ 2 - 2
frontend/rust-lib/flowy-core/src/deps_resolve/document2_deps.rs

@@ -44,11 +44,11 @@ impl DocumentUser for DocumentUserImpl {
       .token()
   }
 
-  fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError> {
+  fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError> {
     self
       .0
       .upgrade()
       .ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
-      .get_collab_db()
+      .get_collab_db(uid)
   }
 }

+ 5 - 5
frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs

@@ -81,12 +81,12 @@ impl FolderUser for FolderUserImpl {
       .token()
   }
 
-  fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError> {
+  fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError> {
     self
       .0
       .upgrade()
       .ok_or(FlowyError::internal().context("Unexpected error: UserSession is None"))?
-      .get_collab_db()
+      .get_collab_db(uid)
   }
 }
 
@@ -139,7 +139,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
     FutureResult::new(async move {
       match manager.delete_document(&view_id) {
         Ok(_) => tracing::trace!("Delete document: {}", view_id),
-        Err(e) => tracing::error!("Failed to delete document: {}", e),
+        Err(e) => tracing::error!("🔴delete document failed: {}", e),
       }
       Ok(())
     })
@@ -149,7 +149,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
     let manager = self.0.clone();
     let view_id = view_id.to_string();
     FutureResult::new(async move {
-      let data: DocumentDataPB = manager.get_document_data(&view_id)?.into();
+      let data: DocumentDataPB = manager.get_document_data(&view_id).await?.into();
       let data_bytes = data.into_bytes().map_err(|_| FlowyError::invalid_data())?;
       Ok(data_bytes)
     })
@@ -235,7 +235,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
     FutureResult::new(async move {
       match database_manager.delete_database_view(&view_id).await {
         Ok(_) => tracing::trace!("Delete database view: {}", view_id),
-        Err(e) => tracing::error!("Failed to delete database: {}", e),
+        Err(e) => tracing::error!("🔴delete database failed: {}", e),
       }
       Ok(())
     })

+ 1 - 0
frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs

@@ -9,3 +9,4 @@ mod folder2_deps;
 mod util;
 
 mod database_deps;
+mod user_deps;

+ 1 - 0
frontend/rust-lib/flowy-core/src/deps_resolve/user_deps.rs

@@ -0,0 +1 @@
+

+ 117 - 46
frontend/rust-lib/flowy-core/src/integrate/server.rs

@@ -6,20 +6,25 @@ use appflowy_integrate::RemoteCollabStorage;
 use parking_lot::RwLock;
 use serde_repr::*;
 
-use flowy_database2::deps::{DatabaseCloudService, DatabaseSnapshot};
-use flowy_document2::deps::{DocumentCloudService, DocumentSnapshot};
+use flowy_database2::deps::{
+  CollabObjectUpdate, CollabObjectUpdateByOid, DatabaseCloudService, DatabaseSnapshot,
+};
+use flowy_document2::deps::{DocumentCloudService, DocumentData, DocumentSnapshot};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
-use flowy_folder2::deps::{FolderCloudService, FolderSnapshot, Workspace};
+use flowy_folder2::deps::{FolderCloudService, FolderData, FolderSnapshot, Workspace};
 use flowy_server::local_server::LocalServer;
 use flowy_server::self_host::configuration::self_host_server_configuration;
 use flowy_server::self_host::SelfHostServer;
-use flowy_server::supabase::{SupabaseConfiguration, SupabaseServer};
+use flowy_server::supabase::SupabaseServer;
 use flowy_server::AppFlowyServer;
+use flowy_server_config::supabase_config::SupabaseConfiguration;
 use flowy_sqlite::kv::KV;
 use flowy_user::event_map::{UserAuthService, UserCloudServiceProvider};
 use flowy_user::services::AuthType;
 use lib_infra::future::FutureResult;
 
+use crate::AppFlowyCoreConfig;
+
 const SERVER_PROVIDER_TYPE_KEY: &str = "server_provider_type";
 
 #[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize_repr, Deserialize_repr)]
@@ -42,13 +47,20 @@ pub enum ServerProviderType {
 /// exist.
 /// Each server implements the [AppFlowyServer] trait, which provides the [UserAuthService], etc.
 pub struct AppFlowyServerProvider {
+  config: AppFlowyCoreConfig,
   provider_type: RwLock<ServerProviderType>,
   providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
+  supabase_config: RwLock<Option<SupabaseConfiguration>>,
 }
 
 impl AppFlowyServerProvider {
-  pub fn new() -> Self {
-    Self::default()
+  pub fn new(config: AppFlowyCoreConfig, supabase_config: Option<SupabaseConfiguration>) -> Self {
+    Self {
+      config,
+      provider_type: RwLock::new(current_server_provider()),
+      providers: RwLock::new(HashMap::new()),
+      supabase_config: RwLock::new(supabase_config),
+    }
   }
 
   pub fn provider_type(&self) -> ServerProviderType {
@@ -64,7 +76,33 @@ impl AppFlowyServerProvider {
       return Ok(provider.clone());
     }
 
-    let server = server_from_auth_type(provider_type)?;
+    let server = match provider_type {
+      ServerProviderType::Local => {
+        let server = Arc::new(LocalServer::new(&self.config.storage_path));
+        Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
+      },
+      ServerProviderType::SelfHosted => {
+        let config = self_host_server_configuration().map_err(|e| {
+          FlowyError::new(
+            ErrorCode::InvalidAuthConfig,
+            format!(
+              "Missing self host config: {:?}. Error: {:?}",
+              provider_type, e
+            ),
+          )
+        })?;
+        let server = Arc::new(SelfHostServer::new(config));
+        Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
+      },
+      ServerProviderType::Supabase => {
+        let config = self.supabase_config.read().clone().ok_or(FlowyError::new(
+          ErrorCode::InvalidAuthConfig,
+          "Missing supabase config".to_string(),
+        ))?;
+        Ok::<Arc<dyn AppFlowyServer>, FlowyError>(Arc::new(SupabaseServer::new(config)))
+      },
+    }?;
+
     self
       .providers
       .write()
@@ -73,16 +111,19 @@ impl AppFlowyServerProvider {
   }
 }
 
-impl Default for AppFlowyServerProvider {
-  fn default() -> Self {
-    Self {
-      provider_type: RwLock::new(current_server_provider()),
-      providers: RwLock::new(HashMap::new()),
+impl UserCloudServiceProvider for AppFlowyServerProvider {
+  fn update_supabase_config(&self, supabase_config: &SupabaseConfiguration) {
+    self
+      .supabase_config
+      .write()
+      .replace(supabase_config.clone());
+
+    supabase_config.write_env();
+    if let Ok(provider) = self.get_provider(&self.provider_type.read()) {
+      provider.enable_sync(supabase_config.enable_sync);
     }
   }
-}
 
-impl UserCloudServiceProvider for AppFlowyServerProvider {
   /// When user login, the provider type is set by the [AuthType] and save to disk for next use.
   ///
   /// Each [AuthType] has a corresponding [ServerProviderType]. The [ServerProviderType] is used
@@ -119,6 +160,17 @@ impl FolderCloudService for AppFlowyServerProvider {
     FutureResult::new(async move { server?.folder_service().create_workspace(uid, &name).await })
   }
 
+  fn get_folder_data(&self, workspace_id: &str) -> FutureResult<Option<FolderData>, FlowyError> {
+    let server = self.get_provider(&self.provider_type.read());
+    let workspace_id = workspace_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .folder_service()
+        .get_folder_data(&workspace_id)
+        .await
+    })
+  }
+
   fn get_folder_latest_snapshot(
     &self,
     workspace_id: &str,
@@ -133,40 +185,64 @@ impl FolderCloudService for AppFlowyServerProvider {
     })
   }
 
-  fn get_folder_updates(&self, workspace_id: &str) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
+  fn get_folder_updates(
+    &self,
+    workspace_id: &str,
+    uid: i64,
+  ) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
     let workspace_id = workspace_id.to_string();
     let server = self.get_provider(&self.provider_type.read());
     FutureResult::new(async move {
       server?
         .folder_service()
-        .get_folder_updates(&workspace_id)
+        .get_folder_updates(&workspace_id, uid)
         .await
     })
   }
+
+  fn service_name(&self) -> String {
+    self
+      .get_provider(&self.provider_type.read())
+      .map(|provider| provider.folder_service().service_name())
+      .unwrap_or_default()
+  }
 }
 
 impl DatabaseCloudService for AppFlowyServerProvider {
-  fn get_database_updates(&self, database_id: &str) -> FutureResult<Vec<Vec<u8>>, FlowyError> {
+  fn get_collab_update(&self, object_id: &str) -> FutureResult<CollabObjectUpdate, FlowyError> {
     let server = self.get_provider(&self.provider_type.read());
-    let database_id = database_id.to_string();
+    let database_id = object_id.to_string();
     FutureResult::new(async move {
       server?
         .database_service()
-        .get_database_updates(&database_id)
+        .get_collab_update(&database_id)
         .await
     })
   }
 
-  fn get_database_latest_snapshot(
+  fn batch_get_collab_updates(
     &self,
-    database_id: &str,
+    object_ids: Vec<String>,
+  ) -> FutureResult<CollabObjectUpdateByOid, FlowyError> {
+    let server = self.get_provider(&self.provider_type.read());
+    FutureResult::new(async move {
+      server?
+        .database_service()
+        .batch_get_collab_updates(object_ids)
+        .await
+    })
+  }
+
+  fn get_collab_latest_snapshot(
+    &self,
+    object_id: &str,
   ) -> FutureResult<Option<DatabaseSnapshot>, FlowyError> {
     let server = self.get_provider(&self.provider_type.read());
-    let database_id = database_id.to_string();
+    let database_id = object_id.to_string();
     FutureResult::new(async move {
       server?
         .database_service()
-        .get_database_latest_snapshot(&database_id)
+        .get_collab_latest_snapshot(&database_id)
         .await
     })
   }
@@ -197,6 +273,17 @@ impl DocumentCloudService for AppFlowyServerProvider {
         .await
     })
   }
+
+  fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, FlowyError> {
+    let server = self.get_provider(&self.provider_type.read());
+    let document_id = document_id.to_string();
+    FutureResult::new(async move {
+      server?
+        .document_service()
+        .get_document_data(&document_id)
+        .await
+    })
+  }
 }
 
 impl CollabStorageProvider for AppFlowyServerProvider {
@@ -214,30 +301,14 @@ impl CollabStorageProvider for AppFlowyServerProvider {
         .and_then(|provider| provider.collab_storage()),
     }
   }
-}
 
-fn server_from_auth_type(
-  provider: &ServerProviderType,
-) -> Result<Arc<dyn AppFlowyServer>, FlowyError> {
-  match provider {
-    ServerProviderType::Local => {
-      let server = Arc::new(LocalServer::new());
-      Ok(server)
-    },
-    ServerProviderType::SelfHosted => {
-      let config = self_host_server_configuration().map_err(|e| {
-        FlowyError::new(
-          ErrorCode::InvalidAuthConfig,
-          format!("Missing self host config: {:?}. Error: {:?}", provider, e),
-        )
-      })?;
-      let server = Arc::new(SelfHostServer::new(config));
-      Ok(server)
-    },
-    ServerProviderType::Supabase => {
-      let config = SupabaseConfiguration::from_env()?;
-      Ok(Arc::new(SupabaseServer::new(config)))
-    },
+  fn is_sync_enabled(&self) -> bool {
+    self
+      .supabase_config
+      .read()
+      .as_ref()
+      .map(|config| config.enable_sync)
+      .unwrap_or(false)
   }
 }
 

+ 33 - 8
frontend/rust-lib/flowy-core/src/lib.rs

@@ -16,12 +16,12 @@ use tracing::debug;
 use flowy_database2::DatabaseManager2;
 use flowy_document2::manager::DocumentManager as DocumentManager2;
 use flowy_error::FlowyResult;
-use flowy_folder2::manager::FolderManager;
+use flowy_folder2::manager::{FolderInitializeData, FolderManager};
 use flowy_sqlite::kv::KV;
 use flowy_task::{TaskDispatcher, TaskRunner};
 use flowy_user::entities::UserProfile;
-use flowy_user::event_map::{UserCloudServiceProvider, UserStatusCallback};
-use flowy_user::services::{AuthType, UserSession, UserSessionConfig};
+use flowy_user::event_map::{SignUpContext, UserCloudServiceProvider, UserStatusCallback};
+use flowy_user::services::{get_supabase_config, AuthType, UserSession, UserSessionConfig};
 use lib_dispatch::prelude::*;
 use lib_dispatch::runtime::tokio_default_runtime;
 use lib_infra::future::{to_fut, Fut};
@@ -141,7 +141,10 @@ impl AppFlowyCore {
     let task_dispatcher = Arc::new(RwLock::new(task_scheduler));
     runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
 
-    let server_provider = Arc::new(AppFlowyServerProvider::new());
+    let server_provider = Arc::new(AppFlowyServerProvider::new(
+      config.clone(),
+      get_supabase_config(),
+    ));
 
     let (user_session, folder_manager, server_provider, database_manager, document_manager2) =
       runtime.block_on(async {
@@ -260,6 +263,21 @@ struct UserStatusCallbackImpl {
 impl UserStatusCallback for UserStatusCallbackImpl {
   fn auth_type_did_changed(&self, _auth_type: AuthType) {}
 
+  fn did_init(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>> {
+    let user_id = user_id.to_owned();
+    let workspace_id = workspace_id.to_owned();
+    let folder_manager = self.folder_manager.clone();
+    let database_manager = self.database_manager.clone();
+
+    to_fut(async move {
+      folder_manager
+        .initialize(user_id, &workspace_id, FolderInitializeData::Empty)
+        .await?;
+      database_manager.initialize(user_id).await?;
+      Ok(())
+    })
+  }
+
   fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>> {
     let user_id = user_id.to_owned();
     let workspace_id = workspace_id.to_owned();
@@ -267,22 +285,29 @@ impl UserStatusCallback for UserStatusCallbackImpl {
     let database_manager = self.database_manager.clone();
 
     to_fut(async move {
-      folder_manager.initialize(user_id, &workspace_id).await?;
+      folder_manager
+        .initialize_when_sign_in(user_id, &workspace_id)
+        .await?;
       database_manager.initialize(user_id).await?;
       Ok(())
     })
   }
 
-  fn did_sign_up(&self, is_new: bool, user_profile: &UserProfile) -> Fut<FlowyResult<()>> {
+  fn did_sign_up(
+    &self,
+    context: SignUpContext,
+    user_profile: &UserProfile,
+  ) -> Fut<FlowyResult<()>> {
     let user_profile = user_profile.clone();
     let folder_manager = self.folder_manager.clone();
     let database_manager = self.database_manager.clone();
     to_fut(async move {
       folder_manager
-        .initialize_with_new_user(
+        .initialize_when_sign_up(
           user_profile.id,
           &user_profile.token,
-          is_new,
+          context.is_new,
+          context.local_folder,
           &user_profile.workspace_id,
         )
         .await?;

+ 11 - 4
frontend/rust-lib/flowy-database2/src/deps.rs

@@ -1,6 +1,8 @@
 use std::sync::Arc;
 
 use appflowy_integrate::RocksCollabDB;
+pub use collab_database::user::CollabObjectUpdate;
+pub use collab_database::user::CollabObjectUpdateByOid;
 
 use flowy_error::FlowyError;
 use lib_infra::future::FutureResult;
@@ -8,18 +10,23 @@ use lib_infra::future::FutureResult;
 pub trait DatabaseUser2: Send + Sync {
   fn user_id(&self) -> Result<i64, FlowyError>;
   fn token(&self) -> Result<Option<String>, FlowyError>;
-  fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
+  fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError>;
 }
 
 /// A trait for database cloud service.
 /// Each kind of server should implement this trait. Check out the [AppFlowyServerProvider] of
 /// [flowy-server] crate for more information.
 pub trait DatabaseCloudService: Send + Sync {
-  fn get_database_updates(&self, database_id: &str) -> FutureResult<Vec<Vec<u8>>, FlowyError>;
+  fn get_collab_update(&self, object_id: &str) -> FutureResult<CollabObjectUpdate, FlowyError>;
 
-  fn get_database_latest_snapshot(
+  fn batch_get_collab_updates(
     &self,
-    database_id: &str,
+    object_ids: Vec<String>,
+  ) -> FutureResult<CollabObjectUpdateByOid, FlowyError>;
+
+  fn get_collab_latest_snapshot(
+    &self,
+    object_id: &str,
   ) -> FutureResult<Option<DatabaseSnapshot>, FlowyError>;
 }
 

+ 1 - 1
frontend/rust-lib/flowy-database2/src/entities/macros.rs

@@ -15,7 +15,7 @@ macro_rules! impl_into_field_type {
           8 => FieldType::LastEditedTime,
           9 => FieldType::CreatedTime,
           _ => {
-            tracing::error!("Can't parser FieldType from value: {}", ty);
+            tracing::error!("🔴Can't parser FieldType from value: {}", ty);
             FieldType::RichText
           },
         }

+ 0 - 9
frontend/rust-lib/flowy-database2/src/entities/row_entities.rs

@@ -324,15 +324,6 @@ impl TryInto<RowIdParams> for RowIdPB {
   }
 }
 
-#[derive(Debug, Default, Clone, ProtoBuf)]
-pub struct BlockRowIdPB {
-  #[pb(index = 1)]
-  pub block_id: String,
-
-  #[pb(index = 2)]
-  pub row_id: String,
-}
-
 #[derive(ProtoBuf, Default)]
 pub struct CreateRowPayloadPB {
   #[pb(index = 1)]

+ 43 - 18
frontend/rust-lib/flowy-database2/src/entities/view_entities.rs

@@ -1,6 +1,8 @@
+use collab_database::rows::RowDetail;
+
 use flowy_derive::ProtoBuf;
 
-use crate::entities::{InsertedRowPB, UpdatedRowPB};
+use crate::entities::{InsertedRowPB, RowMetaPB, UpdatedRowPB};
 
 #[derive(Debug, Default, Clone, ProtoBuf)]
 pub struct RowsVisibilityChangePB {
@@ -17,53 +19,76 @@ pub struct RowsVisibilityChangePB {
 #[derive(Debug, Default, Clone, ProtoBuf)]
 pub struct RowsChangePB {
   #[pb(index = 1)]
-  pub view_id: String,
-
-  #[pb(index = 2)]
   pub inserted_rows: Vec<InsertedRowPB>,
 
-  #[pb(index = 3)]
+  #[pb(index = 2)]
   pub deleted_rows: Vec<String>,
 
-  #[pb(index = 4)]
+  #[pb(index = 3)]
   pub updated_rows: Vec<UpdatedRowPB>,
 }
 
 impl RowsChangePB {
-  pub fn from_insert(view_id: String, inserted_row: InsertedRowPB) -> Self {
+  pub fn from_insert(inserted_row: InsertedRowPB) -> Self {
     Self {
-      view_id,
       inserted_rows: vec![inserted_row],
       ..Default::default()
     }
   }
 
-  pub fn from_delete(view_id: String, deleted_row: String) -> Self {
+  pub fn from_delete(deleted_row: String) -> Self {
     Self {
-      view_id,
       deleted_rows: vec![deleted_row],
       ..Default::default()
     }
   }
 
-  pub fn from_update(view_id: String, updated_row: UpdatedRowPB) -> Self {
+  pub fn from_update(updated_row: UpdatedRowPB) -> Self {
     Self {
-      view_id,
       updated_rows: vec![updated_row],
       ..Default::default()
     }
   }
 
-  pub fn from_move(
-    view_id: String,
-    deleted_rows: Vec<String>,
-    inserted_rows: Vec<InsertedRowPB>,
-  ) -> Self {
+  pub fn from_move(deleted_rows: Vec<String>, inserted_rows: Vec<InsertedRowPB>) -> Self {
     Self {
-      view_id,
       inserted_rows,
       deleted_rows,
       ..Default::default()
     }
   }
 }
+
+#[derive(Debug, Default, ProtoBuf)]
+pub struct DidFetchRowPB {
+  #[pb(index = 1)]
+  pub row_id: String,
+
+  #[pb(index = 2)]
+  pub height: i32,
+
+  #[pb(index = 3)]
+  pub visibility: bool,
+
+  #[pb(index = 4)]
+  pub created_at: i64,
+
+  #[pb(index = 5)]
+  pub modified_at: i64,
+
+  #[pb(index = 6)]
+  pub meta: RowMetaPB,
+}
+
+impl From<RowDetail> for DidFetchRowPB {
+  fn from(value: RowDetail) -> Self {
+    Self {
+      row_id: value.row.id.to_string(),
+      height: value.row.height,
+      visibility: value.row.visibility,
+      created_at: value.row.created_at,
+      modified_at: value.row.modified_at,
+      meta: RowMetaPB::from(value.meta),
+    }
+  }
+}

+ 187 - 108
frontend/rust-lib/flowy-database2/src/manager.rs

@@ -1,14 +1,17 @@
 use std::collections::HashMap;
-use std::ops::Deref;
 use std::sync::Arc;
 
 use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
 use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
-use collab::core::collab::MutexCollab;
-use collab_database::database::DatabaseData;
-use collab_database::user::{DatabaseCollabBuilder, UserDatabase as InnerUserDatabase};
+use collab::core::collab::{CollabRawData, MutexCollab};
+use collab_database::blocks::BlockEvent;
+use collab_database::database::{DatabaseData, YrsDocAction};
+use collab_database::error::DatabaseError;
+use collab_database::user::{
+  make_workspace_database_id, CollabFuture, CollabObjectUpdate, CollabObjectUpdateByOid,
+  DatabaseCollabService, WorkspaceDatabase,
+};
 use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
-use parking_lot::Mutex;
 use tokio::sync::RwLock;
 
 use flowy_error::{internal_error, FlowyError, FlowyResult};
@@ -16,15 +19,17 @@ use flowy_task::TaskDispatcher;
 
 use crate::deps::{DatabaseCloudService, DatabaseUser2};
 use crate::entities::{
-  DatabaseDescriptionPB, DatabaseLayoutPB, DatabaseSnapshotPB, RepeatedDatabaseDescriptionPB,
+  DatabaseDescriptionPB, DatabaseLayoutPB, DatabaseSnapshotPB, DidFetchRowPB,
+  RepeatedDatabaseDescriptionPB,
 };
-use crate::services::database::{DatabaseEditor, MutexDatabase};
+use crate::notification::{send_notification, DatabaseNotification};
+use crate::services::database::DatabaseEditor;
 use crate::services::database_view::DatabaseLayoutDepsResolver;
 use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
 
 pub struct DatabaseManager2 {
   user: Arc<dyn DatabaseUser2>,
-  user_database: UserDatabase,
+  workspace_database: Arc<RwLock<Option<Arc<WorkspaceDatabase>>>>,
   task_scheduler: Arc<RwLock<TaskDispatcher>>,
   editors: RwLock<HashMap<String, Arc<DatabaseEditor>>>,
   collab_builder: Arc<AppFlowyCollabBuilder>,
@@ -40,7 +45,7 @@ impl DatabaseManager2 {
   ) -> Self {
     Self {
       user: database_user,
-      user_database: UserDatabase::default(),
+      workspace_database: Default::default(),
       task_scheduler,
       editors: Default::default(),
       collab_builder,
@@ -48,16 +53,53 @@ impl DatabaseManager2 {
     }
   }
 
-  pub async fn initialize(&self, user_id: i64) -> FlowyResult<()> {
+  fn is_collab_exist(&self, uid: i64, collab_db: &Arc<RocksCollabDB>, object_id: &str) -> bool {
+    let read_txn = collab_db.read_txn();
+    read_txn.is_exist(uid, object_id)
+  }
+
+  pub async fn initialize(&self, uid: i64) -> FlowyResult<()> {
+    let collab_db = self.user.collab_db(uid)?;
+    let workspace_database_id = make_workspace_database_id(uid);
+    let collab_builder = UserDatabaseCollabServiceImpl {
+      collab_builder: self.collab_builder.clone(),
+      cloud_service: self.cloud_service.clone(),
+    };
     let config = CollabPersistenceConfig::new().snapshot_per_update(10);
-    let db = self.user.collab_db()?;
-    *self.user_database.lock() = Some(InnerUserDatabase::new(
-      user_id,
-      db,
-      config,
-      UserDatabaseCollabBuilderImpl(self.collab_builder.clone()),
-    ));
-    // do nothing
+    let mut collab_raw_data = CollabRawData::default();
+
+    // If the workspace database not exist in disk, try to fetch from remote.
+    if !self.is_collab_exist(uid, &collab_db, &workspace_database_id) {
+      tracing::trace!("workspace database not exist, try to fetch from remote");
+      match self
+        .cloud_service
+        .get_collab_update(&workspace_database_id)
+        .await
+      {
+        Ok(updates) => collab_raw_data = updates,
+        Err(err) => {
+          return Err(FlowyError::record_not_found().context(format!(
+            "get workspace database :{} failed: {}",
+            workspace_database_id, err,
+          )));
+        },
+      }
+    }
+
+    // Construct the workspace database.
+    tracing::trace!("open workspace database: {}", &workspace_database_id);
+    let collab = collab_builder.build_collab_with_config(
+      uid,
+      &workspace_database_id,
+      "databases",
+      collab_db.clone(),
+      collab_raw_data,
+      &config,
+    );
+    let workspace_database =
+      WorkspaceDatabase::open(uid, collab, collab_db, config, collab_builder);
+    subscribe_block_event(&workspace_database);
+    *self.workspace_database.write().await = Some(Arc::new(workspace_database));
     Ok(())
   }
 
@@ -67,17 +109,15 @@ impl DatabaseManager2 {
   }
 
   pub async fn get_all_databases_description(&self) -> RepeatedDatabaseDescriptionPB {
-    let databases_description = self.with_user_database(vec![], |database| {
-      database
+    let mut items = vec![];
+    if let Ok(wdb) = self.get_workspace_database().await {
+      items = wdb
         .get_all_databases()
         .into_iter()
         .map(DatabaseDescriptionPB::from)
-        .collect()
-    });
-
-    RepeatedDatabaseDescriptionPB {
-      items: databases_description,
+        .collect();
     }
+    RepeatedDatabaseDescriptionPB { items }
   }
 
   pub async fn get_database_with_view_id(&self, view_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
@@ -86,12 +126,11 @@ impl DatabaseManager2 {
   }
 
   pub async fn get_database_id_with_view_id(&self, view_id: &str) -> FlowyResult<String> {
-    let database_id = self.with_user_database(Err(FlowyError::internal()), |database| {
-      database
-        .get_database_id_with_view_id(view_id)
-        .ok_or_else(FlowyError::record_not_found)
-    })?;
-    Ok(database_id)
+    let wdb = self.get_workspace_database().await?;
+    wdb.get_database_id_with_view_id(view_id).ok_or_else(|| {
+      FlowyError::record_not_found()
+        .context(format!("The database for view id: {} not found", view_id))
+    })
   }
 
   pub async fn get_database(&self, database_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
@@ -104,14 +143,12 @@ impl DatabaseManager2 {
   pub async fn open_database(&self, database_id: &str) -> FlowyResult<Arc<DatabaseEditor>> {
     tracing::trace!("create new editor for database {}", database_id);
     let mut editors = self.editors.write().await;
-    let database = MutexDatabase::new(self.with_user_database(
-      Err(FlowyError::record_not_found()),
-      |database| {
-        database
-          .get_database(database_id)
-          .ok_or_else(FlowyError::record_not_found)
-      },
-    )?);
+
+    let wdb = self.get_workspace_database().await?;
+    let database = wdb
+      .get_database(database_id)
+      .await
+      .ok_or_else(FlowyError::record_not_found)?;
 
     let editor = Arc::new(DatabaseEditor::new(database, self.task_scheduler.clone()).await?);
     editors.insert(database_id.to_string(), editor.clone());
@@ -122,13 +159,11 @@ impl DatabaseManager2 {
   pub async fn close_database_view<T: AsRef<str>>(&self, view_id: T) -> FlowyResult<()> {
     // TODO(natan): defer closing the database if the sync is not finished
     let view_id = view_id.as_ref();
-    let database_id = self.with_user_database(None, |databases| {
-      let database_id = databases.get_database_id_with_view_id(view_id);
-      if database_id.is_some() {
-        databases.close_database(database_id.as_ref().unwrap());
-      }
-      database_id
-    });
+    let wdb = self.get_workspace_database().await?;
+    let database_id = wdb.get_database_id_with_view_id(view_id);
+    if database_id.is_some() {
+      wdb.close_database(database_id.as_ref().unwrap());
+    }
 
     if let Some(database_id) = database_id {
       let mut editors = self.editors.write().await;
@@ -150,13 +185,10 @@ impl DatabaseManager2 {
   }
 
   pub async fn duplicate_database(&self, view_id: &str) -> FlowyResult<Vec<u8>> {
-    let database_data = self.with_user_database(Err(FlowyError::internal()), |database| {
-      let data = database.get_database_duplicated_data(view_id)?;
-      let json_bytes = data.to_json_bytes()?;
-      Ok(json_bytes)
-    })?;
-
-    Ok(database_data)
+    let wdb = self.get_workspace_database().await?;
+    let data = wdb.get_database_duplicated_data(view_id).await?;
+    let json_bytes = data.to_json_bytes()?;
+    Ok(json_bytes)
   }
 
   /// Create a new database with the given data that can be deserialized to [DatabaseData].
@@ -168,24 +200,15 @@ impl DatabaseManager2 {
   ) -> FlowyResult<()> {
     let mut database_data = DatabaseData::from_json_bytes(data)?;
     database_data.view.id = view_id.to_string();
-    self.with_user_database(
-      Err(FlowyError::internal().context("Create database with data failed")),
-      |database| {
-        let database = database.create_database_with_data(database_data)?;
-        Ok(database)
-      },
-    )?;
+
+    let wdb = self.get_workspace_database().await?;
+    let _ = wdb.create_database_with_data(database_data)?;
     Ok(())
   }
 
   pub async fn create_database_with_params(&self, params: CreateDatabaseParams) -> FlowyResult<()> {
-    let _ = self.with_user_database(
-      Err(FlowyError::internal().context("Create database with params failed")),
-      |user_database| {
-        let database = user_database.create_database(params)?;
-        Ok(database)
-      },
-    )?;
+    let wdb = self.get_workspace_database().await?;
+    let _ = wdb.create_database(params)?;
     Ok(())
   }
 
@@ -198,23 +221,18 @@ impl DatabaseManager2 {
     database_id: String,
     database_view_id: String,
   ) -> FlowyResult<()> {
-    self.with_user_database(
-      Err(FlowyError::internal().context("Create database view failed")),
-      |user_database| {
-        let mut params = CreateViewParams::new(database_id.clone(), database_view_id, name, layout);
-        if let Some(database) = user_database.get_database(&database_id) {
-          if let Some((field, layout_setting)) = DatabaseLayoutDepsResolver::new(database, layout)
-            .resolve_deps_when_create_database_linked_view()
-          {
-            params = params
-              .with_deps_fields(vec![field])
-              .with_layout_setting(layout_setting);
-          }
-        };
-        user_database.create_database_linked_view(params)?;
-        Ok(())
-      },
-    )?;
+    let wdb = self.get_workspace_database().await?;
+    let mut params = CreateViewParams::new(database_id.clone(), database_view_id, name, layout);
+    if let Some(database) = wdb.get_database(&database_id).await {
+      if let Some((field, layout_setting)) = DatabaseLayoutDepsResolver::new(database, layout)
+        .resolve_deps_when_create_database_linked_view()
+      {
+        params = params
+          .with_deps_fields(vec![field])
+          .with_layout_setting(layout_setting);
+      }
+    };
+    wdb.create_database_linked_view(params).await?;
     Ok(())
   }
 
@@ -268,7 +286,7 @@ impl DatabaseManager2 {
     let mut snapshots = vec![];
     if let Some(snapshot) = self
       .cloud_service
-      .get_database_latest_snapshot(&database_id)
+      .get_collab_latest_snapshot(&database_id)
       .await?
       .map(|snapshot| DatabaseSnapshotPB {
         snapshot_id: snapshot.snapshot_id,
@@ -283,14 +301,11 @@ impl DatabaseManager2 {
     Ok(snapshots)
   }
 
-  fn with_user_database<F, Output>(&self, default_value: Output, f: F) -> Output
-  where
-    F: FnOnce(&InnerUserDatabase) -> Output,
-  {
-    let database = self.user_database.lock();
+  async fn get_workspace_database(&self) -> FlowyResult<Arc<WorkspaceDatabase>> {
+    let database = self.workspace_database.read().await;
     match &*database {
-      None => default_value,
-      Some(folder) => f(folder),
+      None => Err(FlowyError::internal().context("Workspace database not initialized")),
+      Some(user_database) => Ok(user_database.clone()),
     }
   }
 
@@ -301,33 +316,97 @@ impl DatabaseManager2 {
   }
 }
 
-#[derive(Clone, Default)]
-pub struct UserDatabase(Arc<Mutex<Option<InnerUserDatabase>>>);
-
-impl Deref for UserDatabase {
-  type Target = Arc<Mutex<Option<InnerUserDatabase>>>;
-  fn deref(&self) -> &Self::Target {
-    &self.0
-  }
+/// Send notification to all clients that are listening to the given object.
+fn subscribe_block_event(workspace_database: &WorkspaceDatabase) {
+  let mut block_event_rx = workspace_database.subscribe_block_event();
+  tokio::spawn(async move {
+    while let Ok(event) = block_event_rx.recv().await {
+      match event {
+        BlockEvent::DidFetchRow(row_details) => {
+          for row_detail in row_details {
+            tracing::trace!("Did fetch row: {:?}", row_detail.row.id);
+            let row_id = row_detail.row.id.clone();
+            let pb = DidFetchRowPB::from(row_detail);
+            send_notification(&row_id, DatabaseNotification::DidFetchRow)
+              .payload(pb)
+              .send();
+          }
+        },
+      }
+    }
+  });
 }
 
-unsafe impl Sync for UserDatabase {}
+struct UserDatabaseCollabServiceImpl {
+  collab_builder: Arc<AppFlowyCollabBuilder>,
+  cloud_service: Arc<dyn DatabaseCloudService>,
+}
 
-unsafe impl Send for UserDatabase {}
+impl DatabaseCollabService for UserDatabaseCollabServiceImpl {
+  fn get_collab_update(
+    &self,
+    object_id: &str,
+  ) -> CollabFuture<Result<CollabObjectUpdate, DatabaseError>> {
+    let object_id = object_id.to_string();
+    let weak_cloud_service = Arc::downgrade(&self.cloud_service);
+    Box::pin(async move {
+      match weak_cloud_service.upgrade() {
+        None => {
+          tracing::warn!("Cloud service is dropped");
+          Ok(vec![])
+        },
+        Some(cloud_service) => {
+          let updates = cloud_service
+            .get_collab_update(&object_id)
+            .await
+            .map_err(|e| DatabaseError::Internal(Box::new(e)))?;
+          Ok(updates)
+        },
+      }
+    })
+  }
 
-struct UserDatabaseCollabBuilderImpl(Arc<AppFlowyCollabBuilder>);
+  fn batch_get_collab_update(
+    &self,
+    object_ids: Vec<String>,
+  ) -> CollabFuture<Result<CollabObjectUpdateByOid, DatabaseError>> {
+    let weak_cloud_service = Arc::downgrade(&self.cloud_service);
+    Box::pin(async move {
+      match weak_cloud_service.upgrade() {
+        None => {
+          tracing::warn!("Cloud service is dropped");
+          Ok(CollabObjectUpdateByOid::default())
+        },
+        Some(cloud_service) => {
+          let updates = cloud_service
+            .batch_get_collab_updates(object_ids)
+            .await
+            .map_err(|e| DatabaseError::Internal(Box::new(e)))?;
+          Ok(updates)
+        },
+      }
+    })
+  }
 
-impl DatabaseCollabBuilder for UserDatabaseCollabBuilderImpl {
-  fn build_with_config(
+  fn build_collab_with_config(
     &self,
     uid: i64,
     object_id: &str,
     object_name: &str,
-    db: Arc<RocksCollabDB>,
+    collab_db: Arc<RocksCollabDB>,
+    collab_raw_data: CollabRawData,
     config: &CollabPersistenceConfig,
   ) -> Arc<MutexCollab> {
     self
-      .0
-      .build_with_config(uid, object_id, object_name, db, config)
+      .collab_builder
+      .build_with_config(
+        uid,
+        object_id,
+        object_name,
+        collab_db,
+        collab_raw_data,
+        config,
+      )
+      .unwrap()
   }
 }

+ 4 - 0
frontend/rust-lib/flowy-database2/src/notification.rs

@@ -7,6 +7,9 @@ const DATABASE_OBSERVABLE_SOURCE: &str = "Database";
 pub enum DatabaseNotification {
   #[default]
   Unknown = 0,
+  /// Fetch row data from the remote server. It will be triggered if the backend support remote
+  /// storage.
+  DidFetchRow = 19,
   /// Trigger after inserting/deleting/updating a row
   DidUpdateViewRows = 20,
   /// Trigger when the visibility of the row was changed. For example, updating the filter will trigger the notification
@@ -58,6 +61,7 @@ impl std::convert::From<DatabaseNotification> for i32 {
 impl std::convert::From<i32> for DatabaseNotification {
   fn from(notification: i32) -> Self {
     match notification {
+      19 => DatabaseNotification::DidFetchRow,
       20 => DatabaseNotification::DidUpdateViewRows,
       21 => DatabaseNotification::DidUpdateViewRowsVisibility,
       22 => DatabaseNotification::DidUpdateFields,

+ 27 - 67
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs

@@ -1,14 +1,12 @@
 use std::collections::HashMap;
-use std::ops::Deref;
 use std::sync::Arc;
 
 use bytes::Bytes;
-use collab_database::database::Database as InnerDatabase;
+use collab_database::database::MutexDatabase;
 use collab_database::fields::{Field, TypeOptionData};
-use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowId};
+use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
 use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
 use futures::StreamExt;
-use parking_lot::Mutex;
 use tokio::sync::{broadcast, RwLock};
 
 use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
@@ -21,7 +19,7 @@ use crate::services::cell::{
   apply_cell_changeset, get_cell_protobuf, AnyTypeCache, CellCache, ToCellChangeset,
 };
 use crate::services::database::util::database_view_setting_pb_from_view;
-use crate::services::database::{RowDetail, UpdatedRow};
+use crate::services::database::UpdatedRow;
 use crate::services::database_view::{DatabaseViewChanged, DatabaseViewData, DatabaseViews};
 use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
 use crate::services::field::{
@@ -38,14 +36,14 @@ use crate::services::sort::Sort;
 
 #[derive(Clone)]
 pub struct DatabaseEditor {
-  database: MutexDatabase,
+  database: Arc<MutexDatabase>,
   pub cell_cache: CellCache,
   database_views: Arc<DatabaseViews>,
 }
 
 impl DatabaseEditor {
   pub async fn new(
-    database: MutexDatabase,
+    database: Arc<MutexDatabase>,
     task_scheduler: Arc<RwLock<TaskDispatcher>>,
   ) -> FlowyResult<Self> {
     let cell_cache = AnyTypeCache::<u64>::new();
@@ -76,7 +74,7 @@ impl DatabaseEditor {
     tokio::spawn(async move {
       while let Some(snapshot_state) = snapshot_state.next().await {
         if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
-          tracing::debug!("Did create database snapshot: {}", new_snapshot_id);
+          tracing::debug!("Did create database remote snapshot: {}", new_snapshot_id);
           send_notification(
             &database_id,
             DatabaseNotification::DidUpdateDatabaseSnapshotState,
@@ -415,8 +413,7 @@ impl DatabaseEditor {
 
       let delete_row_id = from.into_inner();
       let insert_row = InsertedRowPB::new(RowMetaPB::from(&row_meta)).with_index(to_index as i32);
-      let changes =
-        RowsChangePB::from_move(view_id.to_string(), vec![delete_row_id], vec![insert_row]);
+      let changes = RowsChangePB::from_move(vec![delete_row_id], vec![insert_row]);
       send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
         .payload(changes)
         .send();
@@ -437,7 +434,7 @@ impl DatabaseEditor {
       tracing::trace!("create row: {:?} at {}", row_order, index);
       let row = self.database.lock().get_row(&row_order.id);
       let row_meta = self.database.lock().get_row_meta(&row_order.id);
-      if let (Some(row), Some(meta)) = (row, row_meta) {
+      if let Some(meta) = row_meta {
         let row_detail = RowDetail { row, meta };
         for view in self.database_views.editors().await {
           view.v_did_create_row(&row_detail, &group_id, index).await;
@@ -527,7 +524,7 @@ impl DatabaseEditor {
 
   pub fn get_row(&self, view_id: &str, row_id: &RowId) -> Option<Row> {
     if self.database.lock().views.is_row_exist(view_id, row_id) {
-      self.database.lock().get_row(row_id)
+      Some(self.database.lock().get_row(row_id))
     } else {
       None
     }
@@ -551,7 +548,7 @@ impl DatabaseEditor {
   pub fn get_row_detail(&self, view_id: &str, row_id: &RowId) -> Option<RowDetail> {
     if self.database.lock().views.is_row_exist(view_id, row_id) {
       let meta = self.database.lock().get_row_meta(row_id)?;
-      let row = self.database.lock().get_row(row_id)?;
+      let row = self.database.lock().get_row(row_id);
       Some(RowDetail { row, meta })
     } else {
       tracing::warn!("the row:{} is exist in view:{}", row_id.as_str(), view_id);
@@ -597,40 +594,23 @@ impl DatabaseEditor {
     let field_type = FieldType::from(field.field_type);
     // If the cell data is referenced, return the reference data. Otherwise, return an empty cell.
     match field_type {
-      FieldType::LastEditedTime | FieldType::CreatedTime => database
-        .get_row(row_id)
-        .map(|row| {
-          if field_type.is_created_time() {
-            DateCellData::new(row.created_at, true)
-          } else {
-            DateCellData::new(row.modified_at, true)
-          }
-        })
-        .map(Cell::from),
+      FieldType::LastEditedTime | FieldType::CreatedTime => {
+        let row = database.get_row(row_id);
+        let cell_data = if field_type.is_created_time() {
+          DateCellData::new(row.created_at, true)
+        } else {
+          DateCellData::new(row.modified_at, true)
+        };
+        Some(Cell::from(cell_data))
+      },
       _ => database.get_cell(field_id, row_id).cell,
     }
   }
 
   pub async fn get_cell_pb(&self, field_id: &str, row_id: &RowId) -> Option<CellPB> {
     let (field, cell) = {
-      let database = self.database.lock();
-      let field = database.fields.get_field(field_id)?;
-      let field_type = FieldType::from(field.field_type);
-      // If the cell data is referenced, return the reference data. Otherwise, return an empty cell.
-      let cell = match field_type {
-        FieldType::LastEditedTime | FieldType::CreatedTime => database
-          .get_row(row_id)
-          .map(|row| {
-            if field_type.is_created_time() {
-              DateCellData::new(row.created_at, true)
-            } else {
-              DateCellData::new(row.modified_at, true)
-            }
-          })
-          .map(Cell::from),
-        _ => database.get_cell(field_id, row_id).cell,
-      }?;
-
+      let cell = self.get_cell(field_id, row_id).await?;
+      let field = self.database.lock().fields.get_field(field_id)?;
       (field, cell)
     };
 
@@ -723,7 +703,7 @@ impl DatabaseEditor {
     if let Some(new_row_detail) = option_row {
       let updated_row =
         UpdatedRow::new(&new_row_detail.row.id).with_field_ids(vec![field_id.to_string()]);
-      let changes = RowsChangePB::from_update(view_id.to_string(), updated_row.into());
+      let changes = RowsChangePB::from_update(updated_row.into());
       send_notification(view_id, DatabaseNotification::DidUpdateViewRows)
         .payload(changes)
         .send();
@@ -1154,35 +1134,15 @@ fn cell_changesets_from_cell_by_field_id(
     .collect()
 }
 
-#[derive(Clone)]
-pub struct MutexDatabase(Arc<Mutex<Arc<InnerDatabase>>>);
-
-impl MutexDatabase {
-  pub(crate) fn new(database: Arc<InnerDatabase>) -> Self {
-    Self(Arc::new(Mutex::new(database)))
-  }
-}
-
-impl Deref for MutexDatabase {
-  type Target = Arc<Mutex<Arc<InnerDatabase>>>;
-  fn deref(&self) -> &Self::Target {
-    &self.0
-  }
-}
-
-unsafe impl Sync for MutexDatabase {}
-
-unsafe impl Send for MutexDatabase {}
-
 struct DatabaseViewDataImpl {
-  database: MutexDatabase,
+  database: Arc<MutexDatabase>,
   task_scheduler: Arc<RwLock<TaskDispatcher>>,
   cell_cache: CellCache,
 }
 
 impl DatabaseViewData for DatabaseViewDataImpl {
-  fn get_database(&self) -> Arc<InnerDatabase> {
-    self.database.lock().clone()
+  fn get_database(&self) -> Arc<MutexDatabase> {
+    self.database.clone()
   }
 
   fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
@@ -1245,8 +1205,8 @@ impl DatabaseViewData for DatabaseViewDataImpl {
     let row = self.database.lock().get_row(row_id);
     let row_meta = self.database.lock().get_row_meta(row_id);
     to_fut(async move {
-      match (index, row, row_meta) {
-        (Some(index), Some(row), Some(row_meta)) => {
+      match (index, row_meta) {
+        (Some(index), Some(row_meta)) => {
           let row_detail = RowDetail {
             row,
             meta: row_meta,

+ 1 - 7
frontend/rust-lib/flowy-database2/src/services/database/entities.rs

@@ -1,4 +1,4 @@
-use collab_database::rows::{Row, RowId, RowMeta};
+use collab_database::rows::{RowId, RowMeta};
 use collab_database::views::DatabaseLayout;
 
 #[derive(Debug, Clone)]
@@ -64,9 +64,3 @@ pub struct CreateDatabaseViewParams {
   pub view_id: String,
   pub layout_type: DatabaseLayout,
 }
-
-#[derive(Debug, Clone)]
-pub struct RowDetail {
-  pub row: Row,
-  pub meta: RowMeta,
-}

+ 8 - 7
frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs

@@ -1,8 +1,7 @@
-use std::sync::Arc;
-
-use collab_database::database::{gen_field_id, Database};
+use collab_database::database::{gen_field_id, MutexDatabase};
 use collab_database::fields::Field;
 use collab_database::views::{DatabaseLayout, LayoutSetting};
+use std::sync::Arc;
 
 use crate::entities::FieldType;
 use crate::services::field::DateTypeOption;
@@ -11,13 +10,13 @@ use crate::services::setting::CalendarLayoutSetting;
 /// When creating a database, we need to resolve the dependencies of the views. Different database
 /// view has different dependencies. For example, a calendar view depends on a date field.
 pub struct DatabaseLayoutDepsResolver {
-  pub database: Arc<Database>,
+  pub database: Arc<MutexDatabase>,
   /// The new database layout.
   pub database_layout: DatabaseLayout,
 }
 
 impl DatabaseLayoutDepsResolver {
-  pub fn new(database: Arc<Database>, database_layout: DatabaseLayout) -> Self {
+  pub fn new(database: Arc<MutexDatabase>, database_layout: DatabaseLayout) -> Self {
     Self {
       database,
       database_layout,
@@ -39,7 +38,7 @@ impl DatabaseLayoutDepsResolver {
   /// If the new layout type is a calendar and there is not date field in the database, it will add
   /// a new date field to the database and create the corresponding layout setting.
   pub fn resolve_deps_when_update_layout_type(&self, view_id: &str) {
-    let fields = self.database.get_fields(None);
+    let fields = self.database.lock().get_fields(None);
     // Insert the layout setting if it's not exist
     match &self.database_layout {
       DatabaseLayout::Grid => {},
@@ -53,7 +52,7 @@ impl DatabaseLayoutDepsResolver {
             tracing::trace!("Create a new date field after layout type change");
             let field = self.create_date_field();
             let field_id = field.id.clone();
-            self.database.create_field(field);
+            self.database.lock().create_field(field);
             field_id
           },
           Some(date_field) => date_field.id,
@@ -66,12 +65,14 @@ impl DatabaseLayoutDepsResolver {
   fn create_calendar_layout_setting_if_need(&self, view_id: &str, field_id: &str) {
     if self
       .database
+      .lock()
       .get_layout_setting::<CalendarLayoutSetting>(view_id, &self.database_layout)
       .is_none()
     {
       let layout_setting = CalendarLayoutSetting::new(field_id.to_string());
       self
         .database
+        .lock()
         .insert_layout_setting(view_id, &self.database_layout, layout_setting);
     }
   }

+ 13 - 25
frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs

@@ -2,9 +2,9 @@ use std::borrow::Cow;
 use std::collections::HashMap;
 use std::sync::Arc;
 
-use collab_database::database::{gen_database_filter_id, gen_database_sort_id, Database};
+use collab_database::database::{gen_database_filter_id, gen_database_sort_id, MutexDatabase};
 use collab_database::fields::{Field, TypeOptionData};
-use collab_database::rows::{Cells, Row, RowCell, RowId, RowMeta};
+use collab_database::rows::{Cells, Row, RowCell, RowDetail, RowId, RowMeta};
 use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
 use tokio::sync::{broadcast, RwLock};
 
@@ -20,9 +20,7 @@ use crate::entities::{
 };
 use crate::notification::{send_notification, DatabaseNotification};
 use crate::services::cell::CellCache;
-use crate::services::database::{
-  database_view_setting_pb_from_view, DatabaseRowEvent, RowDetail, UpdatedRow,
-};
+use crate::services::database::{database_view_setting_pb_from_view, DatabaseRowEvent, UpdatedRow};
 use crate::services::database_view::view_filter::make_filter_controller;
 use crate::services::database_view::view_group::{
   get_cell_for_row, get_cells_for_field, new_group_controller, new_group_controller_with_field,
@@ -44,7 +42,7 @@ use crate::services::setting::CalendarLayoutSetting;
 use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
 
 pub trait DatabaseViewData: Send + Sync + 'static {
-  fn get_database(&self) -> Arc<Database>;
+  fn get_database(&self) -> Arc<MutexDatabase>;
 
   fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
   /// If the field_ids is None, then it will return all the field revisions
@@ -204,7 +202,7 @@ impl DatabaseViewEditor {
 
   pub async fn v_did_update_row_meta(&self, row_id: &RowId, row_meta: &RowMeta) {
     let update_row = UpdatedRow::new(row_id.as_str()).with_row_meta(row_meta.clone());
-    let changeset = RowsChangePB::from_update(self.view_id.clone(), update_row.into());
+    let changeset = RowsChangePB::from_update(update_row.into());
     send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
       .payload(changeset)
       .send();
@@ -221,7 +219,7 @@ impl DatabaseViewEditor {
     match group_id.as_ref() {
       None => {
         let row = InsertedRowPB::new(RowMetaPB::from(&row_detail.meta)).with_index(index as i32);
-        changes = RowsChangePB::from_insert(self.view_id.clone(), row);
+        changes = RowsChangePB::from_insert(row);
       },
       Some(group_id) => {
         self
@@ -239,7 +237,7 @@ impl DatabaseViewEditor {
         let changeset =
           GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row.clone()]);
         notify_did_update_group_rows(changeset).await;
-        changes = RowsChangePB::from_insert(self.view_id.clone(), inserted_row);
+        changes = RowsChangePB::from_insert(inserted_row);
       },
     }
 
@@ -263,7 +261,7 @@ impl DatabaseViewEditor {
         notify_did_update_group_rows(changeset).await;
       }
     }
-    let changes = RowsChangePB::from_delete(self.view_id.clone(), row.id.clone().into_inner());
+    let changes = RowsChangePB::from_delete(row.id.clone().into_inner());
     send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
       .payload(changes)
       .send();
@@ -311,7 +309,7 @@ impl DatabaseViewEditor {
     } else {
       let update_row =
         UpdatedRow::new(&row_detail.row.id).with_field_ids(vec![field_id.to_string()]);
-      let changeset = RowsChangePB::from_update(self.view_id.clone(), update_row.into());
+      let changeset = RowsChangePB::from_update(update_row.into());
       send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)
         .payload(changeset)
         .send();
@@ -880,23 +878,13 @@ impl DatabaseViewEditor {
 
   pub async fn handle_row_event(&self, event: Cow<'_, DatabaseRowEvent>) {
     let changeset = match event.into_owned() {
-      DatabaseRowEvent::InsertRow(row) => {
-        RowsChangePB::from_insert(self.view_id.clone(), row.into())
-      },
-      DatabaseRowEvent::UpdateRow(row) => {
-        RowsChangePB::from_update(self.view_id.clone(), row.into())
-      },
-      DatabaseRowEvent::DeleteRow(row_id) => {
-        RowsChangePB::from_delete(self.view_id.clone(), row_id.into_inner())
-      },
+      DatabaseRowEvent::InsertRow(row) => RowsChangePB::from_insert(row.into()),
+      DatabaseRowEvent::UpdateRow(row) => RowsChangePB::from_update(row.into()),
+      DatabaseRowEvent::DeleteRow(row_id) => RowsChangePB::from_delete(row_id.into_inner()),
       DatabaseRowEvent::Move {
         deleted_row_id,
         inserted_row,
-      } => RowsChangePB::from_move(
-        self.view_id.clone(),
-        vec![deleted_row_id.into_inner()],
-        vec![inserted_row.into()],
-      ),
+      } => RowsChangePB::from_move(vec![deleted_row_id.into_inner()], vec![inserted_row.into()]),
     };
 
     send_notification(&self.view_id, DatabaseNotification::DidUpdateViewRows)

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/database_view/view_filter.rs

@@ -1,12 +1,11 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::RowId;
+use collab_database::rows::{RowDetail, RowId};
 
 use lib_infra::future::{to_fut, Fut};
 
 use crate::services::cell::CellCache;
-use crate::services::database::RowDetail;
 use crate::services::database_view::{
   gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
 };

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/database_view/view_sort.rs

@@ -1,12 +1,12 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
+use collab_database::rows::RowDetail;
 use tokio::sync::RwLock;
 
 use lib_infra::future::{to_fut, Fut};
 
 use crate::services::cell::CellCache;
-use crate::services::database::RowDetail;
 use crate::services::database_view::{
   gen_handler_id, DatabaseViewChangedNotifier, DatabaseViewData,
 };

+ 5 - 4
frontend/rust-lib/flowy-database2/src/services/database_view/views.rs

@@ -1,8 +1,9 @@
 use std::collections::HashMap;
 use std::sync::Arc;
 
+use collab_database::database::MutexDatabase;
 use collab_database::fields::Field;
-use collab_database::rows::RowId;
+use collab_database::rows::{RowDetail, RowId};
 use nanoid::nanoid;
 use tokio::sync::{broadcast, RwLock};
 
@@ -10,7 +11,7 @@ use flowy_error::FlowyResult;
 use lib_infra::future::Fut;
 
 use crate::services::cell::CellCache;
-use crate::services::database::{DatabaseRowEvent, MutexDatabase, RowDetail};
+use crate::services::database::DatabaseRowEvent;
 use crate::services::database_view::{DatabaseViewData, DatabaseViewEditor};
 use crate::services::group::RowChangeset;
 
@@ -19,7 +20,7 @@ pub type RowEventReceiver = broadcast::Receiver<DatabaseRowEvent>;
 
 pub struct DatabaseViews {
   #[allow(dead_code)]
-  database: MutexDatabase,
+  database: Arc<MutexDatabase>,
   cell_cache: CellCache,
   database_view_data: Arc<dyn DatabaseViewData>,
   editor_map: Arc<RwLock<HashMap<String, Arc<DatabaseViewEditor>>>>,
@@ -27,7 +28,7 @@ pub struct DatabaseViews {
 
 impl DatabaseViews {
   pub async fn new(
-    database: MutexDatabase,
+    database: Arc<MutexDatabase>,
     cell_cache: CellCache,
     database_view_data: Arc<dyn DatabaseViewData>,
   ) -> FlowyResult<Self> {

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs

@@ -9,7 +9,6 @@ use crate::entities::{FieldType, SelectOptionCellDataPB};
 use crate::services::cell::{
   CellDataDecoder, CellProtobufBlobParser, DecodedCellData, FromCellChangeset, ToCellChangeset,
 };
-
 use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformHelper;
 use crate::services::field::{
   make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption,
@@ -168,7 +167,7 @@ pub fn select_type_option_from_field(
       Ok(Box::new(type_option))
     },
     ty => {
-      tracing::error!("Unsupported field type: {:?} for this handler", ty);
+      tracing::error!("🔴Unsupported field type: {:?} for this handler", ty);
       Err(ErrorCode::FieldInvalidOperation.into())
     },
   }

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/filter/controller.rs

@@ -3,7 +3,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::{Cell, Row, RowId};
+use collab_database::rows::{Cell, Row, RowDetail, RowId};
 use dashmap::DashMap;
 use serde::{Deserialize, Serialize};
 use tokio::sync::RwLock;
@@ -15,7 +15,6 @@ use lib_infra::future::Fut;
 use crate::entities::filter_entities::*;
 use crate::entities::{FieldType, InsertedRowPB, RowMetaPB};
 use crate::services::cell::{AnyTypeCache, CellCache, CellFilterCache};
-use crate::services::database::RowDetail;
 use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier};
 use crate::services::field::*;
 use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResultNotification};

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/action.rs

@@ -1,11 +1,10 @@
 use collab_database::fields::Field;
-use collab_database::rows::{Cell, Row};
+use collab_database::rows::{Cell, Row, RowDetail};
 
 use flowy_error::FlowyResult;
 
 use crate::entities::{GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB};
 use crate::services::cell::DecodedCellData;
-use crate::services::database::RowDetail;
 use crate::services::group::controller::MoveGroupRowContext;
 use crate::services::group::{GroupData, GroupSettingChangeset};
 

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/controller.rs

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
 use std::sync::Arc;
 
 use collab_database::fields::{Field, TypeOptionData};
-use collab_database::rows::{Cell, Cells, Row, RowId};
+use collab_database::rows::{Cell, Cells, Row, RowDetail, RowId};
 use serde::de::DeserializeOwned;
 use serde::Serialize;
 
@@ -13,7 +13,6 @@ use crate::entities::{
   FieldType, GroupChangesPB, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB,
 };
 use crate::services::cell::{get_cell_protobuf, CellProtobufBlobParser, DecodedCellData};
-use crate::services::database::RowDetail;
 use crate::services::group::action::{
   DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation, GroupCustomize,
 };

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/controller_impls/checkbox_controller.rs

@@ -1,12 +1,11 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
+use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
 use serde::{Deserialize, Serialize};
 
 use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB};
 use crate::services::cell::insert_checkbox_cell;
-use crate::services::database::RowDetail;
 use crate::services::field::{
   CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOption, CHECK, UNCHECK,
 };

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs

@@ -1,12 +1,11 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::{Cells, Row};
+use collab_database::rows::{Cells, Row, RowDetail};
 
 use flowy_error::FlowyResult;
 
 use crate::entities::GroupChangesPB;
-use crate::services::database::RowDetail;
 use crate::services::group::action::{
   DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerOperation,
 };

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs

@@ -1,12 +1,11 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
+use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
 use serde::{Deserialize, Serialize};
 
 use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
 use crate::services::cell::insert_select_option_cell;
-use crate::services::database::RowDetail;
 use crate::services::field::{MultiSelectTypeOption, SelectOptionCellDataParser};
 use crate::services::group::action::GroupCustomize;
 use crate::services::group::controller::{

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/single_select_controller.rs

@@ -1,12 +1,11 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
+use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
 use serde::{Deserialize, Serialize};
 
 use crate::entities::{FieldType, GroupRowsNotificationPB, SelectOptionCellDataPB};
 use crate::services::cell::insert_select_option_cell;
-use crate::services::database::RowDetail;
 use crate::services::field::{SelectOptionCellDataParser, SingleSelectTypeOption};
 use crate::services::group::action::GroupCustomize;
 use crate::services::group::controller::{

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/controller_impls/select_option_controller/util.rs

@@ -1,11 +1,10 @@
 use collab_database::fields::Field;
-use collab_database::rows::{Cell, Row};
+use collab_database::rows::{Cell, Row, RowDetail};
 
 use crate::entities::{
   FieldType, GroupRowsNotificationPB, InsertedRowPB, RowMetaPB, SelectOptionCellDataPB,
 };
 use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell, insert_url_cell};
-use crate::services::database::RowDetail;
 use crate::services::field::{SelectOption, CHECK};
 use crate::services::group::controller::MoveGroupRowContext;
 use crate::services::group::{GeneratedGroupConfig, Group, GroupData};

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/group/controller_impls/url_controller.rs

@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::{new_cell_builder, Cell, Cells, Row};
+use collab_database::rows::{new_cell_builder, Cell, Cells, Row, RowDetail};
 use serde::{Deserialize, Serialize};
 
 use flowy_error::FlowyResult;
@@ -11,7 +11,6 @@ use crate::entities::{
   URLCellDataPB,
 };
 use crate::services::cell::insert_url_cell;
-use crate::services::database::RowDetail;
 use crate::services::field::{URLCellData, URLCellDataParser, URLTypeOption};
 use crate::services::group::action::GroupCustomize;
 use crate::services::group::configuration::GroupContext;

+ 1 - 3
frontend/rust-lib/flowy-database2/src/services/group/entities.rs

@@ -1,12 +1,10 @@
 use anyhow::bail;
 use collab::core::any_map::AnyMapExtension;
 use collab_database::database::gen_database_group_id;
-use collab_database::rows::RowId;
+use collab_database::rows::{RowDetail, RowId};
 use collab_database::views::{GroupMap, GroupMapBuilder, GroupSettingBuilder, GroupSettingMap};
 use serde::{Deserialize, Serialize};
 
-use crate::services::database::RowDetail;
-
 #[derive(Debug, Clone, Default)]
 pub struct GroupSetting {
   pub id: String,

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/group/group_builder.rs

@@ -1,12 +1,12 @@
 use std::sync::Arc;
 
 use collab_database::fields::Field;
+use collab_database::rows::RowDetail;
 use collab_database::views::DatabaseLayout;
 
 use flowy_error::FlowyResult;
 
 use crate::entities::FieldType;
-use crate::services::database::RowDetail;
 use crate::services::group::configuration::GroupSettingReader;
 use crate::services::group::controller::GroupController;
 use crate::services::group::{

+ 1 - 2
frontend/rust-lib/flowy-database2/src/services/sort/controller.rs

@@ -4,7 +4,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use collab_database::fields::Field;
-use collab_database::rows::{Cell, Row, RowId};
+use collab_database::rows::{Cell, Row, RowDetail, RowId};
 use rayon::prelude::ParallelSliceMut;
 use serde::{Deserialize, Serialize};
 use tokio::sync::RwLock;
@@ -16,7 +16,6 @@ use lib_infra::future::Fut;
 use crate::entities::FieldType;
 use crate::entities::SortChangesetNotificationPB;
 use crate::services::cell::CellCache;
-use crate::services::database::RowDetail;
 use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier};
 use crate::services::field::{default_order, TypeOptionCellExt};
 use crate::services::sort::{

+ 2 - 2
frontend/rust-lib/flowy-database2/tests/database/database_editor.rs

@@ -3,12 +3,12 @@ use std::sync::Arc;
 
 use collab_database::database::{gen_database_view_id, timestamp};
 use collab_database::fields::Field;
-use collab_database::rows::{CreateRowParams, RowId};
+use collab_database::rows::{CreateRowParams, RowDetail, RowId};
 use strum::EnumCount;
 
 use flowy_database2::entities::{FieldType, FilterPB, RowMetaPB, SelectOptionPB};
 use flowy_database2::services::cell::{CellBuilder, ToCellChangeset};
-use flowy_database2::services::database::{DatabaseEditor, RowDetail};
+use flowy_database2::services::database::DatabaseEditor;
 use flowy_database2::services::field::checklist_type_option::{
   ChecklistCellChangeset, ChecklistTypeOption,
 };

+ 4 - 1
frontend/rust-lib/flowy-document2/src/deps.rs

@@ -1,6 +1,7 @@
 use std::sync::Arc;
 
 use appflowy_integrate::RocksCollabDB;
+pub use collab_document::blocks::DocumentData;
 
 use flowy_error::FlowyError;
 use lib_infra::future::FutureResult;
@@ -8,7 +9,7 @@ use lib_infra::future::FutureResult;
 pub trait DocumentUser: Send + Sync {
   fn user_id(&self) -> Result<i64, FlowyError>;
   fn token(&self) -> Result<Option<String>, FlowyError>; // unused now.
-  fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
+  fn collab_db(&self, uid: i64) -> Result<Arc<RocksCollabDB>, FlowyError>;
 }
 
 /// A trait for document cloud service.
@@ -21,6 +22,8 @@ pub trait DocumentCloudService: Send + Sync + 'static {
     &self,
     document_id: &str,
   ) -> FutureResult<Option<DocumentSnapshot>, FlowyError>;
+
+  fn get_document_data(&self, document_id: &str) -> FutureResult<Option<DocumentData>, FlowyError>;
 }
 
 pub struct DocumentSnapshot {

+ 1 - 1
frontend/rust-lib/flowy-document2/src/document.rs

@@ -70,7 +70,7 @@ fn subscribe_document_snapshot_state(collab: &Arc<MutexCollab>) {
   tokio::spawn(async move {
     while let Some(snapshot_state) = snapshot_state.next().await {
       if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
-        tracing::debug!("Did create document snapshot: {}", new_snapshot_id);
+        tracing::debug!("Did create document remote snapshot: {}", new_snapshot_id);
         send_notification(
           &document_id,
           DocumentNotification::DidUpdateDocumentSnapshotState,

+ 6 - 4
frontend/rust-lib/flowy-document2/src/entities.rs

@@ -1,11 +1,13 @@
+use std::collections::HashMap;
+
 use collab::core::collab_state::SyncState;
 use collab_document::blocks::{BlockAction, DocumentData};
-use std::collections::HashMap;
 
-use crate::parse::{NotEmptyStr, NotEmptyVec};
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
 
+use crate::parse::{NotEmptyStr, NotEmptyVec};
+
 #[derive(Default, ProtoBuf)]
 pub struct OpenDocumentPayloadPB {
   #[pb(index = 1)]
@@ -270,7 +272,7 @@ impl From<i32> for ExportType {
       1 => ExportType::Markdown,
       2 => ExportType::Link,
       _ => {
-        tracing::error!("Invalid export type: {}", val);
+        tracing::error!("🔴Invalid export type: {}", val);
         ExportType::Text
       },
     }
@@ -306,7 +308,7 @@ impl From<i32> for ConvertType {
     match val {
       0 => ConvertType::Json,
       _ => {
-        tracing::error!("Invalid export type: {}", val);
+        tracing::error!("🔴Invalid export type: {}", val);
         ConvertType::Json
       },
     }

+ 6 - 6
frontend/rust-lib/flowy-document2/src/event_handler.rs

@@ -34,7 +34,7 @@ pub(crate) async fn open_document_handler(
 ) -> DataResult<DocumentDataPB, FlowyError> {
   let params: OpenDocumentParams = data.into_inner().try_into()?;
   let doc_id = params.document_id;
-  let document = manager.get_document(&doc_id)?;
+  let document = manager.get_document(&doc_id).await?;
   let document_data = document.lock().get_document_data()?;
   data_result_ok(DocumentDataPB::from(document_data))
 }
@@ -57,7 +57,7 @@ pub(crate) async fn get_document_data_handler(
 ) -> DataResult<DocumentDataPB, FlowyError> {
   let params: OpenDocumentParams = data.into_inner().try_into()?;
   let doc_id = params.document_id;
-  let document_data = manager.get_document_data(&doc_id)?;
+  let document_data = manager.get_document_data(&doc_id).await?;
   data_result_ok(DocumentDataPB::from(document_data))
 }
 
@@ -68,7 +68,7 @@ pub(crate) async fn apply_action_handler(
 ) -> FlowyResult<()> {
   let params: ApplyActionParams = data.into_inner().try_into()?;
   let doc_id = params.document_id;
-  let document = manager.get_document(&doc_id)?;
+  let document = manager.get_document(&doc_id).await?;
   let actions = params.actions;
   document.lock().apply_action(actions);
   Ok(())
@@ -104,7 +104,7 @@ pub(crate) async fn redo_handler(
 ) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
   let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
   let doc_id = params.document_id;
-  let document = manager.get_document(&doc_id)?;
+  let document = manager.get_document(&doc_id).await?;
   let document = document.lock();
   let redo = document.redo();
   let can_redo = document.can_redo();
@@ -122,7 +122,7 @@ pub(crate) async fn undo_handler(
 ) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
   let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
   let doc_id = params.document_id;
-  let document = manager.get_document(&doc_id)?;
+  let document = manager.get_document(&doc_id).await?;
   let document = document.lock();
   let undo = document.undo();
   let can_redo = document.can_redo();
@@ -140,7 +140,7 @@ pub(crate) async fn can_undo_redo_handler(
 ) -> DataResult<DocumentRedoUndoResponsePB, FlowyError> {
   let params: DocumentRedoUndoParams = data.into_inner().try_into()?;
   let doc_id = params.document_id;
-  let document = manager.get_document(&doc_id)?;
+  let document = manager.get_document(&doc_id).await?;
   let document = document.lock();
   let can_redo = document.can_redo();
   let can_undo = document.can_undo();

+ 37 - 18
frontend/rust-lib/flowy-document2/src/manager.rs

@@ -45,28 +45,35 @@ impl DocumentManager {
     data: Option<DocumentData>,
   ) -> FlowyResult<Arc<MutexDocument>> {
     tracing::trace!("create a document: {:?}", doc_id);
-    let collab = self.collab_for_document(doc_id)?;
+    let collab = self.collab_for_document(doc_id, vec![])?;
     let data = data.unwrap_or_else(default_document_data);
     let document = Arc::new(MutexDocument::create_with_data(collab, data)?);
     Ok(document)
   }
 
   /// Return the document
-  pub fn get_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexDocument>> {
+  pub async fn get_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexDocument>> {
     if let Some(doc) = self.documents.read().get(doc_id) {
       return Ok(doc.clone());
     }
-    // Check if the document exists. If not, return error.
+    let mut updates = vec![];
     if !self.is_doc_exist(doc_id)? {
-      return Err(
-        FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
-      );
+      // Try to get the document from the cloud service
+      if let Ok(document_updates) = self.cloud_service.get_document_updates(doc_id).await {
+        updates = document_updates;
+      } else {
+        return Err(
+          FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
+        );
+      };
     }
 
     tracing::debug!("open_document: {:?}", doc_id);
     let uid = self.user.user_id()?;
-    let db = self.user.collab_db()?;
-    let collab = self.collab_builder.build(uid, doc_id, "document", db);
+    let db = self.user.collab_db(uid)?;
+    let collab = self
+      .collab_builder
+      .build(uid, doc_id, "document", updates, db)?;
     let document = Arc::new(MutexDocument::open(doc_id, collab)?);
 
     // save the document to the memory and read it from the memory if we open the same document again.
@@ -78,14 +85,19 @@ impl DocumentManager {
     Ok(document)
   }
 
-  pub fn get_document_data(&self, doc_id: &str) -> FlowyResult<DocumentData> {
+  pub async fn get_document_data(&self, doc_id: &str) -> FlowyResult<DocumentData> {
+    let mut updates = vec![];
     if !self.is_doc_exist(doc_id)? {
-      return Err(
-        FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
-      );
+      if let Ok(document_updates) = self.cloud_service.get_document_updates(doc_id).await {
+        updates = document_updates;
+      } else {
+        return Err(
+          FlowyError::record_not_found().context(format!("document: {} is not exist", doc_id)),
+        );
+      }
     }
 
-    let collab = self.collab_for_document(doc_id)?;
+    let collab = self.collab_for_document(doc_id, updates)?;
     Document::open(collab)?
       .get_document_data()
       .map_err(internal_error)
@@ -98,7 +110,7 @@ impl DocumentManager {
 
   pub fn delete_document(&self, doc_id: &str) -> FlowyResult<()> {
     let uid = self.user.user_id()?;
-    let db = self.user.collab_db()?;
+    let db = self.user.collab_db(uid)?;
     let _ = db.with_write_txn(|txn| {
       txn.delete_doc(uid, &doc_id)?;
       Ok(())
@@ -130,15 +142,22 @@ impl DocumentManager {
     Ok(snapshots)
   }
 
-  fn collab_for_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexCollab>> {
+  fn collab_for_document(
+    &self,
+    doc_id: &str,
+    updates: Vec<Vec<u8>>,
+  ) -> FlowyResult<Arc<MutexCollab>> {
     let uid = self.user.user_id()?;
-    let db = self.user.collab_db()?;
-    Ok(self.collab_builder.build(uid, doc_id, "document", db))
+    let db = self.user.collab_db(uid)?;
+    let collab = self
+      .collab_builder
+      .build(uid, doc_id, "document", updates, db)?;
+    Ok(collab)
   }
 
   fn is_doc_exist(&self, doc_id: &str) -> FlowyResult<bool> {
     let uid = self.user.user_id()?;
-    let db = self.user.collab_db()?;
+    let db = self.user.collab_db(uid)?;
     let read_txn = db.read_txn();
     Ok(read_txn.is_exist(uid, doc_id))
   }

部分文件因文件數量過多而無法顯示