Explorar o código

feat: cloud storage test (#2663)

* chore: show default user name

* chore: update

* feat: change collab storage type after auth type changed

* chore: reload folder

* chore: initial the group controller if need

* chore: update patch

* chore: update patch ref
Nathan.fooo hai 1 ano
pai
achega
012b6c0066
Modificáronse 27 ficheiros con 397 adicións e 284 borrados
  1. 12 14
      frontend/appflowy_flutter/lib/workspace/presentation/home/menu/menu_user.dart
  2. 15 10
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  3. 6 6
      frontend/appflowy_tauri/src-tauri/Cargo.toml
  4. 15 10
      frontend/rust-lib/Cargo.lock
  5. 5 5
      frontend/rust-lib/Cargo.toml
  6. 1 1
      frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs
  7. 11 5
      frontend/rust-lib/flowy-core/src/integrate/server.rs
  8. 52 63
      frontend/rust-lib/flowy-core/src/lib.rs
  9. 15 6
      frontend/rust-lib/flowy-database2/src/manager.rs
  10. 1 1
      frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs
  11. 17 7
      frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs
  12. 51 31
      frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs
  13. 18 15
      frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs
  14. 1 1
      frontend/rust-lib/flowy-database2/src/services/database_view/views.rs
  15. 1 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs
  16. 1 1
      frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs
  17. 1 1
      frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs
  18. 3 3
      frontend/rust-lib/flowy-document2/src/manager.rs
  19. 1 1
      frontend/rust-lib/flowy-folder2/Cargo.toml
  20. 117 71
      frontend/rust-lib/flowy-folder2/src/manager.rs
  21. 4 8
      frontend/rust-lib/flowy-folder2/src/user_default.rs
  22. 1 2
      frontend/rust-lib/flowy-folder2/src/view_ext.rs
  23. 1 1
      frontend/rust-lib/flowy-server/src/local_server/impls/folder.rs
  24. 1 1
      frontend/rust-lib/flowy-server/src/self_host/impls/folder.rs
  25. 2 2
      frontend/rust-lib/flowy-server/src/supabase/request.rs
  26. 22 4
      frontend/rust-lib/flowy-user/src/event_map.rs
  27. 22 13
      frontend/rust-lib/flowy-user/src/services/user_session.rs

+ 12 - 14
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/menu_user.dart

@@ -46,7 +46,8 @@ class MenuUser extends StatelessWidget {
     String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl;
     if (iconUrl.isEmpty) {
       iconUrl = defaultUserAvatar;
-      final String name = context.read<MenuUserBloc>().state.userProfile.name;
+      final String name =
+          userName(context.read<MenuUserBloc>().state.userProfile);
       final Color color = ColorGenerator().generateColorFromString(name);
       const initialsCount = 2;
       // Taking the first letters of the name components and limiting to 2 elements
@@ -85,10 +86,7 @@ class MenuUser extends StatelessWidget {
   }
 
   Widget _renderUserName(BuildContext context) {
-    String name = context.read<MenuUserBloc>().state.userProfile.name;
-    if (name.isEmpty) {
-      name = context.read<MenuUserBloc>().state.userProfile.email;
-    }
+    String name = userName(context.read<MenuUserBloc>().state.userProfile);
     return FlowyText.medium(
       name,
       overflow: TextOverflow.ellipsis,
@@ -119,13 +117,13 @@ class MenuUser extends StatelessWidget {
       ),
     );
   }
-  //ToDo: when the user is allowed to create another workspace,
-  //we get the below block back
-  // Widget _renderDropButton(BuildContext context) {
-  //   return FlowyDropdownButton(
-  //     onPressed: () {
-  //       debugPrint('show user profile');
-  //     },
-  //   );
-  // }
+
+  /// Return the user name, if the user name is empty, return the default user name.
+  String userName(UserProfilePB userProfile) {
+    String name = userProfile.name;
+    if (name.isEmpty) {
+      name = LocaleKeys.defaultUsername.tr();
+    }
+    return name;
+  }
 }

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

@@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "anyhow",
  "collab",
@@ -108,6 +108,7 @@ dependencies = [
  "collab-folder",
  "collab-persistence",
  "collab-plugins",
+ "parking_lot 0.12.1",
  "serde",
  "serde_json",
  "tracing",
@@ -1023,7 +1024,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "anyhow",
  "bytes",
@@ -1032,6 +1033,7 @@ dependencies = [
  "serde",
  "serde_json",
  "thiserror",
+ "tokio",
  "tracing",
  "y-sync",
  "yrs",
@@ -1040,7 +1042,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -1058,10 +1060,11 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "anyhow",
  "async-trait",
+ "base64 0.21.0",
  "chrono",
  "collab",
  "collab-derive",
@@ -1083,7 +1086,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1095,7 +1098,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "anyhow",
  "collab",
@@ -1112,7 +1115,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "anyhow",
  "collab",
@@ -1124,13 +1127,14 @@ dependencies = [
  "serde_repr",
  "thiserror",
  "tokio",
+ "tokio-stream",
  "tracing",
 ]
 
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "bincode",
  "chrono",
@@ -1150,7 +1154,7 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1180,7 +1184,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=bb6ab1#bb6ab1bada7b045e0edb2652017cd95795eb1309"
 dependencies = [
  "bytes",
  "collab",
@@ -1963,6 +1967,7 @@ dependencies = [
  "strum",
  "strum_macros",
  "tokio",
+ "tokio-stream",
  "tracing",
  "unicode-segmentation",
  "uuid",

+ 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 = "173661" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
-collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
 
 #collab = { path = "../../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

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

@@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "anyhow",
  "collab",
@@ -94,6 +94,7 @@ dependencies = [
  "collab-folder",
  "collab-persistence",
  "collab-plugins",
+ "parking_lot 0.12.1",
  "serde",
  "serde_json",
  "tracing",
@@ -886,7 +887,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "anyhow",
  "bytes",
@@ -895,6 +896,7 @@ dependencies = [
  "serde",
  "serde_json",
  "thiserror",
+ "tokio",
  "tracing",
  "y-sync",
  "yrs",
@@ -903,7 +905,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -921,10 +923,11 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "anyhow",
  "async-trait",
+ "base64 0.21.0",
  "chrono",
  "collab",
  "collab-derive",
@@ -946,7 +949,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -958,7 +961,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "anyhow",
  "collab",
@@ -975,7 +978,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "anyhow",
  "collab",
@@ -987,13 +990,14 @@ dependencies = [
  "serde_repr",
  "thiserror",
  "tokio",
+ "tokio-stream",
  "tracing",
 ]
 
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "bincode",
  "chrono",
@@ -1013,7 +1017,7 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1043,7 +1047,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=173661#173661cc07cc78f4251983fcf7594533ba63bb5d"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=26438c#26438c77de5d4cad723370380da19763da536ac1"
 dependencies = [
  "bytes",
  "collab",
@@ -1748,6 +1752,7 @@ dependencies = [
  "strum",
  "strum_macros",
  "tokio",
+ "tokio-stream",
  "tracing",
  "unicode-segmentation",
  "uuid",

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

@@ -33,11 +33,11 @@ opt-level = 3
 incremental = false
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661"  }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661"  }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c"  }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c"  }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
 
 #collab = { path = "../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }

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

@@ -99,7 +99,7 @@ impl FolderOperationHandler for DocumentFolderOperation {
       let document = manager.get_document(view_id)?;
       let data: DocumentDataPB = DocumentDataWrapper(document.lock().get_document()?).into();
       let data_bytes = data.into_bytes().map_err(|_| FlowyError::invalid_data())?;
-      Ok(Bytes::from(data_bytes))
+      Ok(data_bytes)
     })
   }
 

+ 11 - 5
frontend/rust-lib/flowy-core/src/integrate/server.rs

@@ -80,12 +80,16 @@ impl Default for AppFlowyServerProvider {
 }
 
 impl UserCloudServiceProvider for AppFlowyServerProvider {
-  /// When user login, the provider type is set by the [AuthType].
+  /// 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
   /// to create a new [AppFlowyServer] if it doesn't exist. Once the [ServerProviderType] is set,
   /// it will be used when user open the app again.
+  ///
   fn set_auth_type(&self, auth_type: AuthType) {
     let provider_type: ServerProviderType = auth_type.into();
+    *self.provider_type.write() = provider_type.clone();
+
     match KV::set_object(SERVER_PROVIDER_TYPE_KEY, provider_type.clone()) {
       Ok(_) => tracing::trace!("Update server provider type to: {:?}", provider_type),
       Err(e) => {
@@ -96,9 +100,12 @@ impl UserCloudServiceProvider for AppFlowyServerProvider {
 
   /// Returns the [UserAuthService] base on the current [ServerProviderType].
   /// Creates a new [AppFlowyServer] if it doesn't exist.
-  fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
-    let provider_type: ServerProviderType = auth_type.into();
-    Ok(self.get_provider(&provider_type)?.user_service())
+  fn get_auth_service(&self) -> Result<Arc<dyn UserAuthService>, FlowyError> {
+    Ok(
+      self
+        .get_provider(&self.provider_type.read())?
+        .user_service(),
+    )
   }
 }
 
@@ -129,7 +136,6 @@ fn server_from_auth_type(
       Ok(server)
     },
     ServerProviderType::Supabase => {
-      // init the SupabaseServerConfiguration from the environment variables.
       let config = SupabaseConfiguration::from_env()?;
       let server = Arc::new(SupabaseServer::new(config));
       Ok(server)

+ 52 - 63
frontend/rust-lib/flowy-core/src/lib.rs

@@ -21,7 +21,7 @@ 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::{UserSession, UserSessionConfig};
+use flowy_user::services::{AuthType, UserSession, UserSessionConfig};
 use lib_dispatch::prelude::*;
 use lib_dispatch::runtime::tokio_default_runtime;
 use lib_infra::future::{to_fut, Fut};
@@ -143,9 +143,9 @@ impl AppFlowyCore {
     let server_provider = Arc::new(AppFlowyServerProvider::new());
     /// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
     /// on demand based on the [CollabPluginConfig].
-    let cloud_storage_type =
-      collab_storage_type_from_server_provider_type(&server_provider.provider_type());
-    let collab_builder = Arc::new(AppFlowyCollabBuilder::new(cloud_storage_type));
+    let collab_builder = Arc::new(AppFlowyCollabBuilder::new(
+      server_provider.provider_type().into(),
+    ));
 
     let (user_session, folder_manager, server_provider, database_manager, document_manager2) =
       runtime.block_on(async {
@@ -181,19 +181,16 @@ impl AppFlowyCore {
         )
       });
 
-    let user_status_listener = UserStatusListener {
+    let user_status_listener = UserStatusCallbackImpl {
+      collab_builder,
       folder_manager: folder_manager.clone(),
       database_manager: database_manager.clone(),
       config: config.clone(),
     };
 
-    let user_status_callback = UserStatusCallbackImpl {
-      listener: Arc::new(user_status_listener),
-    };
-
     let cloned_user_session = user_session.clone();
     runtime.block_on(async move {
-      cloned_user_session.clone().init(user_status_callback).await;
+      cloned_user_session.clone().init(user_status_listener).await;
     });
 
     let event_dispatcher = Arc::new(AFPluginDispatcher::construct(runtime, || {
@@ -247,79 +244,71 @@ fn mk_user_session(
   Arc::new(UserSession::new(user_config, user_cloud_service_provider))
 }
 
-struct UserStatusListener {
+struct UserStatusCallbackImpl {
+  collab_builder: Arc<AppFlowyCollabBuilder>,
   folder_manager: Arc<Folder2Manager>,
   database_manager: Arc<DatabaseManager2>,
   #[allow(dead_code)]
   config: AppFlowyCoreConfig,
 }
 
-impl UserStatusListener {
-  async fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> FlowyResult<()> {
-    self
-      .folder_manager
-      .initialize(user_id, workspace_id)
-      .await?;
-    self.database_manager.initialize(user_id).await?;
-    Ok(())
-  }
-
-  async fn did_sign_up(&self, user_profile: &UserProfile) -> FlowyResult<()> {
-    self
-      .folder_manager
-      .initialize_with_new_user(
-        user_profile.id,
-        &user_profile.token,
-        &user_profile.workspace_id,
-      )
-      .await?;
-
+impl UserStatusCallback for UserStatusCallbackImpl {
+  fn auth_type_did_changed(&self, auth_type: AuthType) {
+    let provider_type: ServerProviderType = auth_type.into();
     self
-      .database_manager
-      .initialize_with_new_user(user_profile.id, &user_profile.token)
-      .await?;
-
-    Ok(())
+      .collab_builder
+      .set_cloud_storage_type(provider_type.into());
   }
 
-  async fn did_expired(&self, _token: &str, user_id: i64) -> FlowyResult<()> {
-    self.folder_manager.clear(user_id).await;
-    Ok(())
-  }
-}
-
-struct UserStatusCallbackImpl {
-  listener: Arc<UserStatusListener>,
-}
-
-impl UserStatusCallback for UserStatusCallbackImpl {
   fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>> {
-    let listener = self.listener.clone();
     let user_id = user_id.to_owned();
     let workspace_id = workspace_id.to_owned();
-    to_fut(async move { listener.did_sign_in(user_id, &workspace_id).await })
+    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).await?;
+      database_manager.initialize(user_id).await?;
+      Ok(())
+    })
   }
 
   fn did_sign_up(&self, user_profile: &UserProfile) -> Fut<FlowyResult<()>> {
-    let listener = self.listener.clone();
     let user_profile = user_profile.clone();
-    to_fut(async move { listener.did_sign_up(&user_profile).await })
+    let folder_manager = self.folder_manager.clone();
+    let database_manager = self.database_manager.clone();
+    to_fut(async move {
+      folder_manager
+        .initialize_with_new_user(
+          user_profile.id,
+          &user_profile.token,
+          &user_profile.workspace_id,
+        )
+        .await?;
+
+      database_manager
+        .initialize_with_new_user(user_profile.id, &user_profile.token)
+        .await?;
+
+      Ok(())
+    })
   }
 
-  fn did_expired(&self, token: &str, user_id: i64) -> Fut<FlowyResult<()>> {
-    let listener = self.listener.clone();
-    let token = token.to_owned();
-    let user_id = user_id.to_owned();
-    to_fut(async move { listener.did_expired(&token, user_id).await })
+  fn did_expired(&self, _token: &str, user_id: i64) -> Fut<FlowyResult<()>> {
+    let folder_manager = self.folder_manager.clone();
+    to_fut(async move {
+      folder_manager.clear(user_id).await;
+      Ok(())
+    })
   }
 }
 
-fn collab_storage_type_from_server_provider_type(
-  server_provider_type: &ServerProviderType,
-) -> CloudStorageType {
-  match server_provider_type {
-    ServerProviderType::Local => CloudStorageType::Local,
-    ServerProviderType::SelfHosted => CloudStorageType::Local,
-    ServerProviderType::Supabase => CloudStorageType::Supabase,
+impl From<ServerProviderType> for CloudStorageType {
+  fn from(server_provider: ServerProviderType) -> Self {
+    match server_provider {
+      ServerProviderType::Local => CloudStorageType::Local,
+      ServerProviderType::SelfHosted => CloudStorageType::Local,
+      ServerProviderType::Supabase => CloudStorageType::Supabase,
+    }
   }
 }

+ 15 - 6
frontend/rust-lib/flowy-database2/src/manager.rs

@@ -6,7 +6,7 @@ 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::{UserDatabase as InnerUserDatabase, UserDatabaseCollabBuilder};
+use collab_database::user::{DatabaseCollabBuilder, UserDatabase as InnerUserDatabase};
 use collab_database::views::{CreateDatabaseParams, CreateViewParams};
 use parking_lot::Mutex;
 use tokio::sync::RwLock;
@@ -183,7 +183,7 @@ impl DatabaseManager2 {
         match duplicated_view_id {
           None => {
             let params = CreateViewParams::new(database_id, target_view_id, name, layout.into());
-            database.create_linked_view(params);
+            database.create_linked_view(params)?;
           },
           Some(duplicated_view_id) => {
             database.duplicate_linked_view(&duplicated_view_id);
@@ -256,18 +256,27 @@ unsafe impl Send for UserDatabase {}
 
 struct UserDatabaseCollabBuilderImpl(Arc<AppFlowyCollabBuilder>);
 
-impl UserDatabaseCollabBuilder for UserDatabaseCollabBuilderImpl {
-  fn build(&self, uid: i64, object_id: &str, db: Arc<RocksCollabDB>) -> Arc<MutexCollab> {
-    self.0.build(uid, object_id, db)
+impl DatabaseCollabBuilder for UserDatabaseCollabBuilderImpl {
+  fn build(
+    &self,
+    uid: i64,
+    object_id: &str,
+    object_name: &str,
+    db: Arc<RocksCollabDB>,
+  ) -> Arc<MutexCollab> {
+    self.0.build(uid, object_id, object_name, db)
   }
 
   fn build_with_config(
     &self,
     uid: i64,
     object_id: &str,
+    object_name: &str,
     db: Arc<RocksCollabDB>,
     config: &CollabPersistenceConfig,
   ) -> Arc<MutexCollab> {
-    self.0.build_with_config(uid, object_id, db, config)
+    self
+      .0
+      .build_with_config(uid, object_id, object_name, db, config)
   }
 }

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs

@@ -313,7 +313,7 @@ impl<'a> CellBuilder<'a> {
   /// Build list of Cells from HashMap of cell string by field id.
   pub fn with_cells(cell_by_field_id: HashMap<String, String>, fields: &'a [Field]) -> Self {
     let field_maps = fields
-      .into_iter()
+      .iter()
       .map(|field| (field.id.clone(), field))
       .collect::<HashMap<String, &Field>>();
 

+ 17 - 7
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs

@@ -574,9 +574,14 @@ impl DatabaseEditor {
     row_id: RowId,
     options: Vec<SelectOptionPB>,
   ) -> FlowyResult<()> {
-    let field = self.database.lock().fields.get_field(field_id).ok_or(
-      FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id)),
-    )?;
+    let field = self
+      .database
+      .lock()
+      .fields
+      .get_field(field_id)
+      .ok_or_else(|| {
+        FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id))
+      })?;
     debug_assert!(FieldType::from(field.field_type).is_select_option());
 
     let mut type_option = select_type_option_from_field(&field)?;
@@ -670,9 +675,14 @@ impl DatabaseEditor {
     field_id: &str,
     changeset: ChecklistCellChangeset,
   ) -> FlowyResult<()> {
-    let field = self.database.lock().fields.get_field(field_id).ok_or(
-      FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id)),
-    )?;
+    let field = self
+      .database
+      .lock()
+      .fields
+      .get_field(field_id)
+      .ok_or_else(|| {
+        FlowyError::record_not_found().context(format!("Field with id:{} not found", &field_id))
+      })?;
     debug_assert!(FieldType::from(field.field_type).is_checklist());
 
     self
@@ -684,7 +694,7 @@ impl DatabaseEditor {
   #[tracing::instrument(level = "trace", skip_all, err)]
   pub async fn load_groups(&self, view_id: &str) -> FlowyResult<RepeatedGroupPB> {
     let view = self.database_views.get_view_editor(view_id).await?;
-    let groups = view.v_load_groups().await?;
+    let groups = view.v_load_groups().await.unwrap_or_default();
     Ok(RepeatedGroupPB { items: groups })
   }
 

+ 51 - 31
frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs

@@ -112,7 +112,7 @@ pub trait DatabaseViewData: Send + Sync + 'static {
 pub struct DatabaseViewEditor {
   pub view_id: String,
   delegate: Arc<dyn DatabaseViewData>,
-  group_controller: Arc<RwLock<Box<dyn GroupController>>>,
+  group_controller: Arc<RwLock<Option<Box<dyn GroupController>>>>,
   filter_controller: Arc<FilterController>,
   sort_controller: Arc<RwLock<SortController>>,
   pub notifier: DatabaseViewChangedNotifier,
@@ -192,10 +192,12 @@ impl DatabaseViewEditor {
       },
       Some(group_id) => {
         self
-          .group_controller
-          .write()
-          .await
-          .did_create_row(row, group_id);
+          .mut_group_controller(|group_controller, _| {
+            group_controller.did_create_row(row, group_id);
+            Ok(())
+          })
+          .await;
+
         let inserted_row = InsertedRowPB {
           row: RowPB::from(row),
           index: Some(index as i32),
@@ -347,22 +349,29 @@ impl DatabaseViewEditor {
   }
   /// Only call once after database view editor initialized
   #[tracing::instrument(level = "trace", skip(self))]
-  pub async fn v_load_groups(&self) -> FlowyResult<Vec<GroupPB>> {
+  pub async fn v_load_groups(&self) -> Option<Vec<GroupPB>> {
     let groups = self
       .group_controller
       .read()
       .await
+      .as_ref()?
       .groups()
       .into_iter()
       .map(|group_data| GroupPB::from(group_data.clone()))
       .collect::<Vec<_>>();
     tracing::trace!("Number of groups: {}", groups.len());
-    Ok(groups)
+    Some(groups)
   }
 
   #[tracing::instrument(level = "trace", skip(self))]
   pub async fn v_get_group(&self, group_id: &str) -> FlowyResult<GroupPB> {
-    match self.group_controller.read().await.get_group(group_id) {
+    match self
+      .group_controller
+      .read()
+      .await
+      .as_ref()
+      .and_then(|group| group.get_group(group_id))
+    {
       None => Err(FlowyError::record_not_found().context("Can't find the group")),
       Some((_, group)) => Ok(GroupPB::from(group)),
     }
@@ -371,19 +380,22 @@ impl DatabaseViewEditor {
   #[tracing::instrument(level = "trace", skip(self), err)]
   pub async fn v_move_group(&self, from_group: &str, to_group: &str) -> FlowyResult<()> {
     self
-      .group_controller
-      .write()
-      .await
-      .move_group(from_group, to_group)?;
+      .mut_group_controller(|group_controller, _| group_controller.move_group(from_group, to_group))
+      .await;
     Ok(())
   }
 
-  pub async fn group_id(&self) -> String {
-    self.group_controller.read().await.field_id().to_string()
+  pub async fn is_grouping_field(&self, field_id: &str) -> bool {
+    match self.group_controller.read().await.as_ref() {
+      Some(group_controller) => group_controller.field_id() == field_id,
+      None => false,
+    }
   }
 
+  /// Called when the user changes the grouping field
   pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> {
-    if self.group_controller.read().await.field_id() != field_id {
+    let is_grouping_field = self.is_grouping_field(field_id).await;
+    if !is_grouping_field {
       self.v_update_grouping_field(field_id).await?;
 
       if let Some(view) = self.delegate.get_view_setting(&self.view_id).await {
@@ -400,10 +412,11 @@ impl DatabaseViewEditor {
 
   pub async fn update_group_setting(&self, changeset: GroupSettingChangeset) -> FlowyResult<()> {
     self
-      .group_controller
-      .write()
-      .await
-      .apply_group_setting_changeset(changeset)
+      .mut_group_controller(|group_controller, _| {
+        group_controller.apply_group_setting_changeset(changeset)
+      })
+      .await;
+    Ok(())
   }
 
   pub async fn v_get_all_sorts(&self) -> Vec<Sort> {
@@ -631,10 +644,11 @@ impl DatabaseViewEditor {
         .await;
 
       self
-        .group_controller
-        .write()
-        .await
-        .did_update_field_type_option(&field);
+        .mut_group_controller(|group_controller, _| {
+          group_controller.did_update_field_type_option(&field);
+          Ok(())
+        })
+        .await;
 
       if let Some(filter) = self
         .delegate
@@ -672,7 +686,7 @@ impl DatabaseViewEditor {
         .map(|group| GroupPB::from(group.clone()))
         .collect();
 
-      *self.group_controller.write().await = new_group_controller;
+      *self.group_controller.write().await = Some(new_group_controller);
       let changeset = GroupChangesPB {
         view_id: self.view_id.clone(),
         initial_groups: new_groups,
@@ -804,13 +818,19 @@ impl DatabaseViewEditor {
   where
     F: FnOnce(&mut Box<dyn GroupController>, Arc<Field>) -> FlowyResult<T>,
   {
-    let group_field_id = self.group_controller.read().await.field_id().to_owned();
-    match self.delegate.get_field(&group_field_id).await {
-      None => None,
-      Some(field) => {
-        let mut write_guard = self.group_controller.write().await;
-        f(&mut write_guard, field).ok()
-      },
+    let group_field_id = self
+      .group_controller
+      .read()
+      .await
+      .as_ref()
+      .map(|group| group.field_id().to_owned())?;
+    let field = self.delegate.get_field(&group_field_id).await?;
+
+    let mut write_guard = self.group_controller.write().await;
+    if let Some(group_controller) = &mut *write_guard {
+      f(group_controller, field).ok()
+    } else {
+      None
     }
   }
 }

+ 18 - 15
frontend/rust-lib/flowy-database2/src/services/database_view/view_group.rs

@@ -3,7 +3,7 @@ use std::sync::Arc;
 use collab_database::fields::Field;
 use collab_database::rows::RowId;
 
-use flowy_error::{FlowyError, FlowyResult};
+use flowy_error::FlowyResult;
 use lib_infra::future::{to_fut, Fut};
 
 use crate::entities::FieldType;
@@ -35,13 +35,9 @@ pub async fn new_group_controller_with_field(
 pub async fn new_group_controller(
   view_id: String,
   delegate: Arc<dyn DatabaseViewData>,
-) -> FlowyResult<Box<dyn GroupController>> {
-  let setting_reader = GroupSettingReaderImpl(delegate.clone());
-  let setting_writer = GroupSettingWriterImpl(delegate.clone());
-
+) -> FlowyResult<Option<Box<dyn GroupController>>> {
   let fields = delegate.get_fields(&view_id, None).await;
-  let rows = delegate.get_rows(&view_id).await;
-  let layout = delegate.get_layout_for_view(&view_id);
+  let setting_reader = GroupSettingReaderImpl(delegate.clone());
 
   // Read the grouping field or find a new grouping field
   let mut grouping_field = setting_reader
@@ -54,22 +50,29 @@ pub async fn new_group_controller(
         .cloned()
     });
 
-  if grouping_field.is_none() {
-    grouping_field = find_new_grouping_field(&fields, &layout);
+  let layout = delegate.get_layout_for_view(&view_id);
+  // If the view is a board and the grouping field is empty, we need to find a new grouping field
+  if layout.is_board() {
+    if grouping_field.is_none() {
+      grouping_field = find_new_grouping_field(&fields, &layout);
+    }
   }
 
-  match grouping_field {
-    None => Err(FlowyError::internal().context("No grouping field found".to_owned())),
-    Some(_) => {
+  if let Some(grouping_field) = grouping_field {
+    let rows = delegate.get_rows(&view_id).await;
+    let setting_writer = GroupSettingWriterImpl(delegate.clone());
+    Ok(Some(
       make_group_controller(
         view_id,
-        grouping_field.unwrap(),
+        grouping_field,
         rows,
         setting_reader,
         setting_writer,
       )
-      .await
-    },
+      .await?,
+    ))
+  } else {
+    Ok(None)
   }
 }
 

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

@@ -93,7 +93,7 @@ impl DatabaseViews {
     let view_editor = self.get_view_editor(view_id).await?;
     // If the id of the grouping field is equal to the updated field's id, then we need to
     // update the group setting
-    if view_editor.group_id().await == field_id {
+    if view_editor.is_grouping_field(field_id).await {
       view_editor.v_update_grouping_field(field_id).await?;
     }
     view_editor

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs

@@ -108,7 +108,7 @@ fn update_cell_data_with_changeset(
     .into_iter()
     .for_each(|option_name| {
       let option = SelectOption::new(&option_name);
-      cell_data.options.push(option.clone());
+      cell_data.options.push(option);
     });
 
   // Update options

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/cell_test/test.rs

@@ -134,7 +134,7 @@ async fn update_updated_at_field_on_other_cell_update() {
     .editor
     .get_cells_for_field(&test.view_id, &updated_at_field.id)
     .await;
-  assert!(cells.len() > 0);
+  assert!(!cells.is_empty());
   for (i, cell) in cells.into_iter().enumerate() {
     let timestamp = DateCellData::from(cell.as_ref()).timestamp.unwrap();
     println!(

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs

@@ -11,7 +11,7 @@ async fn export_meta_csv_test() {
   let database = test.editor.clone();
   let s = database.export_csv(CSVFormat::META).await.unwrap();
   let mut reader = csv::Reader::from_reader(s.as_bytes());
-  for header in reader.headers() {
+  for header in reader.headers().unwrap() {
     dbg!(header);
   }
 

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

@@ -42,7 +42,7 @@ impl DocumentManager {
     tracing::debug!("create a document: {:?}", &doc_id);
     let uid = self.user.user_id()?;
     let db = self.user.collab_db()?;
-    let collab = self.collab_builder.build(uid, &doc_id, db);
+    let collab = self.collab_builder.build(uid, &doc_id, "document", db);
     let document = Arc::new(Document::create_with_data(collab, data.0)?);
     Ok(document)
   }
@@ -55,7 +55,7 @@ impl DocumentManager {
     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, db);
+    let collab = self.collab_builder.build(uid, &doc_id, "document", db);
     // read the existing document from the disk.
     let document = Arc::new(Document::new(collab)?);
     // save the document to the memory and read it from the memory if we open the same document again.
@@ -84,7 +84,7 @@ impl DocumentManager {
   pub fn get_document(&self, doc_id: String) -> FlowyResult<Arc<Document>> {
     let uid = self.user.user_id()?;
     let db = self.user.collab_db()?;
-    let collab = self.collab_builder.build(uid, &doc_id, db);
+    let collab = self.collab_builder.build(uid, &doc_id, "document", db);
     // read the existing document from the disk.
     let document = Arc::new(Document::new(collab)?);
     Ok(document)

+ 1 - 1
frontend/rust-lib/flowy-folder2/Cargo.toml

@@ -27,7 +27,7 @@ strum = "0.21"
 strum_macros = "0.21"
 protobuf = {version = "2.28.0"}
 uuid = { version = "1.3.3", features = ["v4"] }
-#flowy-document = { path = "../flowy-document" }
+tokio-stream = { version = "0.1.14", features = ["sync"] }
 
 [dev-dependencies]
 flowy-folder2 = { path = "../flowy-folder2"}

+ 117 - 71
frontend/rust-lib/flowy-folder2/src/manager.rs

@@ -1,13 +1,14 @@
 use std::collections::{HashMap, HashSet};
 
 use std::ops::Deref;
-use std::sync::Arc;
+use std::sync::{Arc, Weak};
 
 use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
+use collab::core::collab_state::CollabState;
 
 use collab_folder::core::{
-  Folder as InnerFolder, FolderContext, TrashChange, TrashChangeReceiver, TrashInfo, TrashRecord,
-  View, ViewChange, ViewChangeReceiver, ViewLayout, Workspace,
+  Folder, FolderContext, TrashChange, TrashChangeReceiver, TrashInfo, TrashRecord, View,
+  ViewChange, ViewChangeReceiver, ViewLayout, Workspace,
 };
 use parking_lot::Mutex;
 use tracing::{event, Level};
@@ -15,6 +16,8 @@ use tracing::{event, Level};
 use crate::deps::{FolderCloudService, FolderUser};
 use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use lib_infra::util::timestamp;
+use tokio_stream::wrappers::WatchStream;
+use tokio_stream::StreamExt;
 
 use crate::entities::{
   view_pb_with_child_views, CreateViewParams, CreateWorkspaceParams, RepeatedTrashPB,
@@ -29,7 +32,7 @@ use crate::user_default::DefaultFolderBuilder;
 use crate::view_ext::{create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers};
 
 pub struct Folder2Manager {
-  folder: Folder,
+  mutex_folder: Arc<MutexFolder>,
   collab_builder: Arc<AppFlowyCollabBuilder>,
   user: Arc<dyn FolderUser>,
   operation_handlers: FolderOperationHandlers,
@@ -46,10 +49,10 @@ impl Folder2Manager {
     operation_handlers: FolderOperationHandlers,
     cloud_service: Arc<dyn FolderCloudService>,
   ) -> FlowyResult<Self> {
-    let folder = Folder::default();
+    let mutex_folder = Arc::new(MutexFolder::default());
     let manager = Self {
       user,
-      folder,
+      mutex_folder,
       collab_builder,
       operation_handlers,
       cloud_service,
@@ -67,7 +70,7 @@ impl Folder2Manager {
 
   pub async fn get_current_workspace_views(&self) -> FlowyResult<Vec<ViewPB>> {
     let workspace_id = self
-      .folder
+      .mutex_folder
       .lock()
       .as_ref()
       .map(|folder| folder.get_current_workspace_id());
@@ -90,17 +93,25 @@ impl Folder2Manager {
   /// Called immediately after the application launched fi the user already sign in/sign up.
   #[tracing::instrument(level = "debug", skip(self), err)]
   pub async fn initialize(&self, uid: i64, workspace_id: &str) -> FlowyResult<()> {
+    let workspace_id = workspace_id.to_string();
     if let Ok(collab_db) = self.user.collab_db() {
-      let collab = self.collab_builder.build(uid, workspace_id, collab_db);
+      let collab = self
+        .collab_builder
+        .build(uid, &workspace_id, "workspace", collab_db);
       let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
       let (trash_tx, trash_rx) = tokio::sync::broadcast::channel(100);
       let folder_context = FolderContext {
-        view_change_tx: Some(view_tx),
-        trash_change_tx: Some(trash_tx),
+        view_change_tx: view_tx,
+        trash_change_tx: trash_tx,
       };
-      *self.folder.lock() = Some(InnerFolder::get_or_create(collab, folder_context));
-      listen_on_trash_change(trash_rx, self.folder.clone());
-      listen_on_view_change(view_rx, self.folder.clone());
+      let folder = Folder::get_or_create(collab, folder_context);
+      let folder_state_rx = folder.subscribe_state_change();
+      *self.mutex_folder.lock() = Some(folder);
+
+      let weak_mutex_folder = Arc::downgrade(&self.mutex_folder);
+      listen_on_folder_state_change(workspace_id, folder_state_rx, &weak_mutex_folder);
+      listen_on_trash_change(trash_rx, &weak_mutex_folder);
+      listen_on_view_change(view_rx, &weak_mutex_folder);
     }
 
     Ok(())
@@ -172,9 +183,9 @@ impl Folder2Manager {
 
   fn with_folder<F, Output>(&self, default_value: Output, f: F) -> Output
   where
-    F: FnOnce(&InnerFolder) -> Output,
+    F: FnOnce(&Folder) -> Output,
   {
-    let folder = self.folder.lock();
+    let folder = self.mutex_folder.lock();
     match &*folder {
       None => default_value,
       Some(folder) => f(folder),
@@ -222,7 +233,7 @@ impl Folder2Manager {
       folder.insert_view(view.clone());
     });
 
-    notify_parent_view_did_change(self.folder.clone(), vec![view.bid.clone()]);
+    notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid.clone()]);
     Ok(view)
   }
 
@@ -263,7 +274,7 @@ impl Folder2Manager {
   #[tracing::instrument(level = "debug", skip(self, view_id), err)]
   pub async fn get_view(&self, view_id: &str) -> FlowyResult<ViewPB> {
     let view_id = view_id.to_string();
-    let folder = self.folder.lock();
+    let folder = self.mutex_folder.lock();
     let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
     let trash_ids = folder
       .trash
@@ -279,7 +290,7 @@ impl Folder2Manager {
     match folder.views.get_view(&view_id) {
       None => Err(FlowyError::record_not_found()),
       Some(mut view) => {
-        view.belongings.retain(|b| !trash_ids.contains(&b.id));
+        view.children.retain(|b| !trash_ids.contains(&b.id));
         let child_views = folder
           .views
           .get_views_belong_to(&view.id)
@@ -325,7 +336,7 @@ impl Folder2Manager {
     match view {
       None => tracing::error!("Couldn't find the view. It should not be empty"),
       Some(view) => {
-        notify_parent_view_did_change(self.folder.clone(), vec![view.bid]);
+        notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid]);
       },
     }
     Ok(())
@@ -340,7 +351,7 @@ impl Folder2Manager {
   #[tracing::instrument(level = "trace", skip(self), err)]
   pub async fn update_view_with_params(&self, params: UpdateViewParams) -> FlowyResult<()> {
     let _ = self
-      .folder
+      .mutex_folder
       .lock()
       .as_ref()
       .ok_or_else(folder_not_init_error)?
@@ -353,7 +364,10 @@ impl Folder2Manager {
       });
 
     if let Ok(view_pb) = self.get_view(&params.view_id).await {
-      notify_parent_view_did_change(self.folder.clone(), vec![view_pb.parent_view_id.clone()]);
+      notify_parent_view_did_change(
+        self.mutex_folder.clone(),
+        vec![view_pb.parent_view_id.clone()],
+      );
       send_notification(&view_pb.id, FolderNotification::DidUpdateView)
         .payload(view_pb)
         .send();
@@ -369,10 +383,10 @@ impl Folder2Manager {
 
     let handler = self.get_handler(&view.layout)?;
     let view_data = handler.duplicate_view(&view.id).await?;
-    let mut ext = HashMap::new();
-    if let Some(database_id) = view.database_id {
-      ext.insert("database_id".to_string(), database_id);
-    }
+    let meta = HashMap::new();
+    // if let Some(database_id) = view.database_id {
+    //   meta.insert("database_id".to_string(), database_id);
+    // }
     let duplicate_params = CreateViewParams {
       parent_view_id: view.bid.clone(),
       name: format!("{} (copy)", &view.name),
@@ -380,7 +394,7 @@ impl Folder2Manager {
       layout: view.layout.into(),
       initial_data: view_data.to_vec(),
       view_id: gen_view_id(),
-      meta: ext,
+      meta,
     };
 
     let _ = self.create_view_with_params(duplicate_params).await?;
@@ -389,7 +403,7 @@ impl Folder2Manager {
 
   #[tracing::instrument(level = "trace", skip(self), err)]
   pub(crate) async fn set_current_view(&self, view_id: &str) -> Result<(), FlowyError> {
-    let folder = self.folder.lock();
+    let folder = self.mutex_folder.lock();
     let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
     folder.set_current_view(view_id);
 
@@ -487,7 +501,7 @@ impl Folder2Manager {
     self.with_folder((), |folder| {
       folder.insert_view(view.clone());
     });
-    notify_parent_view_did_change(self.folder.clone(), vec![view.bid.clone()]);
+    notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.bid.clone()]);
     Ok(view)
   }
 
@@ -507,52 +521,82 @@ impl Folder2Manager {
 }
 
 /// Listen on the [ViewChange] after create/delete/update events happened
-fn listen_on_view_change(mut rx: ViewChangeReceiver, folder: Folder) {
+fn listen_on_view_change(mut rx: ViewChangeReceiver, weak_mutex_folder: &Weak<MutexFolder>) {
+  let weak_mutex_folder = weak_mutex_folder.clone();
   tokio::spawn(async move {
     while let Ok(value) = rx.recv().await {
-      match value {
-        ViewChange::DidCreateView { view } => {
-          notify_parent_view_did_change(folder.clone(), vec![view.bid]);
-        },
-        ViewChange::DidDeleteView { views: _ } => {},
-        ViewChange::DidUpdate { view } => {
-          notify_parent_view_did_change(folder.clone(), vec![view.bid]);
-        },
-      };
+      if let Some(folder) = weak_mutex_folder.upgrade() {
+        tracing::trace!("Did receive view change: {:?}", value);
+        match value {
+          ViewChange::DidCreateView { view } => {
+            notify_parent_view_did_change(folder.clone(), vec![view.bid]);
+          },
+          ViewChange::DidDeleteView { views: _ } => {},
+          ViewChange::DidUpdate { view } => {
+            notify_parent_view_did_change(folder.clone(), vec![view.bid]);
+          },
+        };
+      }
+    }
+  });
+}
+
+fn listen_on_folder_state_change(
+  workspace_id: String,
+  mut folder_state_rx: WatchStream<CollabState>,
+  weak_mutex_folder: &Weak<MutexFolder>,
+) {
+  let weak_mutex_folder = weak_mutex_folder.clone();
+  tokio::spawn(async move {
+    while let Some(state) = folder_state_rx.next().await {
+      if state.is_root_changed() {
+        if let Some(mutex_folder) = weak_mutex_folder.upgrade() {
+          let folder = mutex_folder.lock().take();
+          if let Some(folder) = folder {
+            tracing::trace!("🔥Reload folder");
+            let reload_folder = folder.reload();
+            notify_did_update_workspace(&workspace_id, &reload_folder);
+            *mutex_folder.lock() = Some(reload_folder);
+          }
+        }
+      }
     }
   });
 }
 
 /// Listen on the [TrashChange]s and notify the frontend some views were changed.
-fn listen_on_trash_change(mut rx: TrashChangeReceiver, folder: Folder) {
+fn listen_on_trash_change(mut rx: TrashChangeReceiver, weak_mutex_folder: &Weak<MutexFolder>) {
+  let weak_mutex_folder = weak_mutex_folder.clone();
   tokio::spawn(async move {
     while let Ok(value) = rx.recv().await {
-      let mut unique_ids = HashSet::new();
-      tracing::trace!("Did receive trash change: {:?}", value);
-      let ids = match value {
-        TrashChange::DidCreateTrash { ids } => ids,
-        TrashChange::DidDeleteTrash { ids } => ids,
-      };
-
-      if let Some(folder) = folder.lock().as_ref() {
-        let views = folder.views.get_views(&ids);
-        for view in views {
-          unique_ids.insert(view.bid);
+      if let Some(folder) = weak_mutex_folder.upgrade() {
+        let mut unique_ids = HashSet::new();
+        tracing::trace!("Did receive trash change: {:?}", value);
+        let ids = match value {
+          TrashChange::DidCreateTrash { ids } => ids,
+          TrashChange::DidDeleteTrash { ids } => ids,
+        };
+
+        if let Some(folder) = folder.lock().as_ref() {
+          let views = folder.views.get_views(&ids);
+          for view in views {
+            unique_ids.insert(view.bid);
+          }
+
+          let repeated_trash: RepeatedTrashPB = folder.trash.get_all_trash().into();
+          send_notification("trash", FolderNotification::DidUpdateTrash)
+            .payload(repeated_trash)
+            .send();
         }
 
-        let repeated_trash: RepeatedTrashPB = folder.trash.get_all_trash().into();
-        send_notification("trash", FolderNotification::DidUpdateTrash)
-          .payload(repeated_trash)
-          .send();
+        let parent_view_ids = unique_ids.into_iter().collect();
+        notify_parent_view_did_change(folder.clone(), parent_view_ids);
       }
-
-      let parent_view_ids = unique_ids.into_iter().collect();
-      notify_parent_view_did_change(folder.clone(), parent_view_ids);
     }
   });
 }
 
-fn get_workspace_view_pbs(workspace_id: &str, folder: &InnerFolder) -> Vec<ViewPB> {
+fn get_workspace_view_pbs(workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
   let trash_ids = folder
     .trash
     .get_all_trash()
@@ -577,10 +621,18 @@ fn get_workspace_view_pbs(workspace_id: &str, folder: &InnerFolder) -> Vec<ViewP
     .collect()
 }
 
+fn notify_did_update_workspace(workspace_id: &str, folder: &Folder) {
+  let repeated_view: RepeatedViewPB = get_workspace_view_pbs(workspace_id, folder).into();
+  tracing::trace!("Did update workspace views: {:?}", repeated_view);
+  send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceViews)
+    .payload(repeated_view)
+    .send();
+}
+
 /// Notify the the list of parent view ids that its child views were changed.
 #[tracing::instrument(level = "debug", skip(folder, parent_view_ids))]
 fn notify_parent_view_did_change<T: AsRef<str>>(
-  folder: Folder,
+  folder: Arc<MutexFolder>,
   parent_view_ids: Vec<T>,
 ) -> Option<()> {
   let folder = folder.lock();
@@ -599,10 +651,7 @@ fn notify_parent_view_did_change<T: AsRef<str>>(
     // if the view's parent id equal to workspace id. Then it will fetch the current
     // workspace views. Because the the workspace is not a view stored in the views map.
     if parent_view_id == workspace_id {
-      let repeated_view: RepeatedViewPB = get_workspace_view_pbs(&workspace_id, folder).into();
-      send_notification(&workspace_id, FolderNotification::DidUpdateWorkspaceViews)
-        .payload(repeated_view)
-        .send();
+      notify_did_update_workspace(&workspace_id, folder)
     } else {
       // Parent view can contain a list of child views. Currently, only get the first level
       // child views.
@@ -627,15 +676,12 @@ fn folder_not_init_error() -> FlowyError {
 }
 
 #[derive(Clone, Default)]
-pub struct Folder(Arc<Mutex<Option<InnerFolder>>>);
-
-impl Deref for Folder {
-  type Target = Arc<Mutex<Option<InnerFolder>>>;
+pub struct MutexFolder(Arc<Mutex<Option<Folder>>>);
+impl Deref for MutexFolder {
+  type Target = Arc<Mutex<Option<Folder>>>;
   fn deref(&self) -> &Self::Target {
     &self.0
   }
 }
-
-unsafe impl Sync for Folder {}
-
-unsafe impl Send for Folder {}
+unsafe impl Sync for MutexFolder {}
+unsafe impl Send for MutexFolder {}

+ 4 - 8
frontend/rust-lib/flowy-folder2/src/user_default.rs

@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 
 use chrono::Utc;
-use collab_folder::core::{Belonging, Belongings, FolderData, View, ViewLayout, Workspace};
+use collab_folder::core::{FolderData, RepeatedView, View, ViewIdentifier, ViewLayout, Workspace};
 use nanoid::nanoid;
 
 use crate::entities::{view_pb_with_child_views, WorkspacePB};
@@ -24,10 +24,9 @@ impl DefaultFolderBuilder {
       bid: view_id.clone(),
       name: "Read me".to_string(),
       desc: "".to_string(),
-      belongings: Default::default(),
       created_at: time,
       layout: child_view_layout.clone(),
-      database_id: None,
+      children: Default::default(),
     };
 
     // create the document
@@ -50,21 +49,18 @@ impl DefaultFolderBuilder {
       bid: workspace_id.clone(),
       name: "⭐️ Getting started".to_string(),
       desc: "".to_string(),
-      belongings: Belongings::new(vec![Belonging {
+      children: RepeatedView::new(vec![ViewIdentifier {
         id: child_view.id.clone(),
-        name: child_view.name.clone(),
       }]),
       created_at: time,
       layout: ViewLayout::Document,
-      database_id: None,
     };
 
     let workspace = Workspace {
       id: workspace_id,
       name: "Workspace".to_string(),
-      belongings: Belongings::new(vec![Belonging {
+      child_views: RepeatedView::new(vec![ViewIdentifier {
         id: view.id.clone(),
-        name: view.name.clone(),
       }]),
       created_at: time,
     };

+ 1 - 2
frontend/rust-lib/flowy-folder2/src/view_ext.rs

@@ -83,10 +83,9 @@ pub(crate) fn create_view(params: CreateViewParams, layout: ViewLayout) -> View
     bid: params.parent_view_id,
     name: params.name,
     desc: params.desc,
-    belongings: Default::default(),
+    children: Default::default(),
     created_at: time,
     layout,
-    database_id: None,
   }
 }
 pub fn gen_view_id() -> String {

+ 1 - 1
frontend/rust-lib/flowy-server/src/local_server/impls/folder.rs

@@ -13,7 +13,7 @@ impl FolderCloudService for LocalServerFolderCloudServiceImpl {
       Ok(Workspace {
         id: gen_workspace_id(),
         name: name.to_string(),
-        belongings: Default::default(),
+        child_views: Default::default(),
         created_at: timestamp(),
       })
     })

+ 1 - 1
frontend/rust-lib/flowy-server/src/self_host/impls/folder.rs

@@ -13,7 +13,7 @@ impl FolderCloudService for SelfHostedServerFolderCloudServiceImpl {
       Ok(Workspace {
         id: gen_workspace_id(),
         name: name.to_string(),
-        belongings: Default::default(),
+        child_views: Default::default(),
         created_at: timestamp(),
       })
     })

+ 2 - 2
frontend/rust-lib/flowy-server/src/supabase/request.rs

@@ -187,7 +187,7 @@ pub(crate) async fn create_workspace_with_uid(
   Ok(Workspace {
     id: user_workspace.workspace_id,
     name: user_workspace.workspace_name,
-    belongings: Default::default(),
+    child_views: Default::default(),
     created_at: user_workspace.created_at.timestamp(),
   })
 }
@@ -218,7 +218,7 @@ pub(crate) async fn get_user_workspace_with_uid(
       .map(|user_workspace| Workspace {
         id: user_workspace.workspace_id,
         name: user_workspace.workspace_name,
-        belongings: Default::default(),
+        child_views: Default::default(),
         created_at: user_workspace.created_at.timestamp(),
       })
       .collect(),

+ 22 - 4
frontend/rust-lib/flowy-user/src/event_map.rs

@@ -7,7 +7,7 @@ use flowy_error::FlowyResult;
 
 use lib_dispatch::prelude::*;
 use lib_infra::box_any::BoxAny;
-use lib_infra::future::{Fut, FutureResult};
+use lib_infra::future::{to_fut, Fut, FutureResult};
 
 use crate::entities::{SignInResponse, SignUpResponse, UpdateUserProfileParams, UserProfile};
 use crate::event_handler::*;
@@ -31,7 +31,25 @@ pub fn init(user_session: Arc<UserSession>) -> AFPlugin {
     .event(UserEvent::ThirdPartyAuth, third_party_auth_handler)
 }
 
+pub(crate) struct DefaultUserStatusCallback;
+impl UserStatusCallback for DefaultUserStatusCallback {
+  fn auth_type_did_changed(&self, _auth_type: AuthType) {}
+
+  fn did_sign_in(&self, _user_id: i64, _workspace_id: &str) -> Fut<FlowyResult<()>> {
+    to_fut(async { Ok(()) })
+  }
+
+  fn did_sign_up(&self, _user_profile: &UserProfile) -> Fut<FlowyResult<()>> {
+    to_fut(async { Ok(()) })
+  }
+
+  fn did_expired(&self, _token: &str, _user_id: i64) -> Fut<FlowyResult<()>> {
+    to_fut(async { Ok(()) })
+  }
+}
+
 pub trait UserStatusCallback: Send + Sync + 'static {
+  fn auth_type_did_changed(&self, auth_type: AuthType);
   fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>>;
   fn did_sign_up(&self, user_profile: &UserProfile) -> Fut<FlowyResult<()>>;
   fn did_expired(&self, token: &str, user_id: i64) -> Fut<FlowyResult<()>>;
@@ -41,7 +59,7 @@ pub trait UserStatusCallback: Send + Sync + 'static {
 /// The provider can be supabase, firebase, aws, or any other cloud service.
 pub trait UserCloudServiceProvider: Send + Sync + 'static {
   fn set_auth_type(&self, auth_type: AuthType);
-  fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError>;
+  fn get_auth_service(&self) -> Result<Arc<dyn UserAuthService>, FlowyError>;
 }
 
 impl<T> UserCloudServiceProvider for Arc<T>
@@ -52,8 +70,8 @@ where
     (**self).set_auth_type(auth_type)
   }
 
-  fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
-    (**self).get_auth_service(auth_type)
+  fn get_auth_service(&self) -> Result<Arc<dyn UserAuthService>, FlowyError> {
+    (**self).get_auth_service()
   }
 }
 

+ 22 - 13
frontend/rust-lib/flowy-user/src/services/user_session.rs

@@ -19,7 +19,7 @@ use crate::entities::{
   AuthTypePB, SignInResponse, SignUpResponse, UpdateUserProfileParams, UserProfile,
 };
 use crate::entities::{UserProfilePB, UserSettingPB};
-use crate::event_map::{UserCloudServiceProvider, UserStatusCallback};
+use crate::event_map::{DefaultUserStatusCallback, UserCloudServiceProvider, UserStatusCallback};
 use crate::{
   errors::FlowyError,
   event_map::UserAuthService,
@@ -50,7 +50,7 @@ pub struct UserSession {
   database: UserDB,
   session_config: UserSessionConfig,
   cloud_services: Arc<dyn UserCloudServiceProvider>,
-  user_status_callback: RwLock<Option<Arc<dyn UserStatusCallback>>>,
+  user_status_callback: RwLock<Arc<dyn UserStatusCallback>>,
 }
 
 impl UserSession {
@@ -59,7 +59,8 @@ impl UserSession {
     cloud_services: Arc<dyn UserCloudServiceProvider>,
   ) -> Self {
     let db = UserDB::new(&session_config.root_dir);
-    let user_status_callback = RwLock::new(None);
+    let user_status_callback: RwLock<Arc<dyn UserStatusCallback>> =
+      RwLock::new(Arc::new(DefaultUserStatusCallback));
     Self {
       database: db,
       session_config,
@@ -74,7 +75,7 @@ impl UserSession {
         .did_sign_in(session.user_id, &session.workspace_id)
         .await;
     }
-    *self.user_status_callback.write().await = Some(Arc::new(user_status_callback));
+    *self.user_status_callback.write().await = Arc::new(user_status_callback);
   }
 
   pub fn db_connection(&self) -> Result<DBConnection, FlowyError> {
@@ -104,10 +105,16 @@ impl UserSession {
     auth_type: &AuthType,
     params: BoxAny,
   ) -> Result<UserProfile, FlowyError> {
+    self
+      .user_status_callback
+      .read()
+      .await
+      .auth_type_did_changed(auth_type.clone());
+
     self.cloud_services.set_auth_type(auth_type.clone());
     let resp = self
       .cloud_services
-      .get_auth_service(auth_type)?
+      .get_auth_service()?
       .sign_in(params)
       .await?;
 
@@ -118,8 +125,6 @@ impl UserSession {
       .user_status_callback
       .read()
       .await
-      .as_ref()
-      .unwrap()
       .did_sign_in(user_profile.id, &user_profile.workspace_id)
       .await;
     send_sign_in_notification()
@@ -135,10 +140,16 @@ impl UserSession {
     auth_type: &AuthType,
     params: BoxAny,
   ) -> Result<UserProfile, FlowyError> {
+    self
+      .user_status_callback
+      .read()
+      .await
+      .auth_type_did_changed(auth_type.clone());
+
     self.cloud_services.set_auth_type(auth_type.clone());
     let resp = self
       .cloud_services
-      .get_auth_service(auth_type)?
+      .get_auth_service()?
       .sign_up(params)
       .await?;
 
@@ -150,8 +161,6 @@ impl UserSession {
       .user_status_callback
       .read()
       .await
-      .as_ref()
-      .unwrap()
       .did_sign_up(&user_profile)
       .await;
     Ok(user_profile)
@@ -166,7 +175,7 @@ impl UserSession {
     self.database.close_user_db(session.user_id)?;
     self.set_session(None)?;
 
-    let server = self.cloud_services.get_auth_service(auth_type)?;
+    let server = self.cloud_services.get_auth_service()?;
     let token = session.token;
     let _ = tokio::spawn(async move {
       match server.sign_out(token).await {
@@ -256,12 +265,12 @@ impl UserSession {
 impl UserSession {
   async fn update_user(
     &self,
-    auth_type: &AuthType,
+    _auth_type: &AuthType,
     uid: i64,
     token: &Option<String>,
     params: UpdateUserProfileParams,
   ) -> Result<(), FlowyError> {
-    let server = self.cloud_services.get_auth_service(auth_type)?;
+    let server = self.cloud_services.get_auth_service()?;
     let token = token.to_owned();
     let _ = tokio::spawn(async move {
       match server.update_user(uid, &token, params).await {