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
This commit is contained in:
Nathan.fooo 2023-05-31 17:42:14 +08:00 committed by GitHub
parent 09d61c79c9
commit 012b6c0066
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 398 additions and 285 deletions

View File

@ -46,7 +46,8 @@ class MenuUser extends StatelessWidget {
String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl; String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl;
if (iconUrl.isEmpty) { if (iconUrl.isEmpty) {
iconUrl = defaultUserAvatar; 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); final Color color = ColorGenerator().generateColorFromString(name);
const initialsCount = 2; const initialsCount = 2;
// Taking the first letters of the name components and limiting to 2 elements // 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) { Widget _renderUserName(BuildContext context) {
String name = context.read<MenuUserBloc>().state.userProfile.name; String name = userName(context.read<MenuUserBloc>().state.userProfile);
if (name.isEmpty) {
name = context.read<MenuUserBloc>().state.userProfile.email;
}
return FlowyText.medium( return FlowyText.medium(
name, name,
overflow: TextOverflow.ellipsis, 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 /// Return the user name, if the user name is empty, return the default user name.
// Widget _renderDropButton(BuildContext context) { String userName(UserProfilePB userProfile) {
// return FlowyDropdownButton( String name = userProfile.name;
// onPressed: () { if (name.isEmpty) {
// debugPrint('show user profile'); name = LocaleKeys.defaultUsername.tr();
// }, }
// ); return name;
// } }
} }

View File

@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]] [[package]]
name = "appflowy-integrate" name = "appflowy-integrate"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -108,6 +108,7 @@ dependencies = [
"collab-folder", "collab-folder",
"collab-persistence", "collab-persistence",
"collab-plugins", "collab-plugins",
"parking_lot 0.12.1",
"serde", "serde",
"serde_json", "serde_json",
"tracing", "tracing",
@ -1023,7 +1024,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1032,6 +1033,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
"tokio",
"tracing", "tracing",
"y-sync", "y-sync",
"yrs", "yrs",
@ -1040,7 +1042,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-client-ws" name = "collab-client-ws"
version = "0.1.0" 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 = [ dependencies = [
"bytes", "bytes",
"collab-sync", "collab-sync",
@ -1058,10 +1060,11 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"base64 0.21.0",
"chrono", "chrono",
"collab", "collab",
"collab-derive", "collab-derive",
@ -1083,7 +1086,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1095,7 +1098,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1112,7 +1115,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -1124,13 +1127,14 @@ dependencies = [
"serde_repr", "serde_repr",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream",
"tracing", "tracing",
] ]
[[package]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" 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 = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
@ -1150,7 +1154,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1180,7 +1184,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-sync" name = "collab-sync"
version = "0.1.0" 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 = [ dependencies = [
"bytes", "bytes",
"collab", "collab",
@ -1963,6 +1967,7 @@ dependencies = [
"strum", "strum",
"strum_macros", "strum_macros",
"tokio", "tokio",
"tokio-stream",
"tracing", "tracing",
"unicode-segmentation", "unicode-segmentation",
"uuid", "uuid",

View File

@ -34,12 +34,12 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io] [patch.crates-io]
collab = { 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 = "173661" } collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" } collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" } collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" } collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "173661" } appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "26438c" }
#collab = { path = "../../AppFlowy-Collab/collab" } #collab = { path = "../../AppFlowy-Collab/collab" }
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" } #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

View File

@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]] [[package]]
name = "appflowy-integrate" name = "appflowy-integrate"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -94,6 +94,7 @@ dependencies = [
"collab-folder", "collab-folder",
"collab-persistence", "collab-persistence",
"collab-plugins", "collab-plugins",
"parking_lot 0.12.1",
"serde", "serde",
"serde_json", "serde_json",
"tracing", "tracing",
@ -886,7 +887,7 @@ dependencies = [
[[package]] [[package]]
name = "collab" name = "collab"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -895,6 +896,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
"tokio",
"tracing", "tracing",
"y-sync", "y-sync",
"yrs", "yrs",
@ -903,7 +905,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-client-ws" name = "collab-client-ws"
version = "0.1.0" 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 = [ dependencies = [
"bytes", "bytes",
"collab-sync", "collab-sync",
@ -921,10 +923,11 @@ dependencies = [
[[package]] [[package]]
name = "collab-database" name = "collab-database"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"base64 0.21.0",
"chrono", "chrono",
"collab", "collab",
"collab-derive", "collab-derive",
@ -946,7 +949,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-derive" name = "collab-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -958,7 +961,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-document" name = "collab-document"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -975,7 +978,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-folder" name = "collab-folder"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"collab", "collab",
@ -987,13 +990,14 @@ dependencies = [
"serde_repr", "serde_repr",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream",
"tracing", "tracing",
] ]
[[package]] [[package]]
name = "collab-persistence" name = "collab-persistence"
version = "0.1.0" 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 = [ dependencies = [
"bincode", "bincode",
"chrono", "chrono",
@ -1013,7 +1017,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-plugins" name = "collab-plugins"
version = "0.1.0" 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 = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1043,7 +1047,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-sync" name = "collab-sync"
version = "0.1.0" 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 = [ dependencies = [
"bytes", "bytes",
"collab", "collab",
@ -1748,6 +1752,7 @@ dependencies = [
"strum", "strum",
"strum_macros", "strum_macros",
"tokio", "tokio",
"tokio-stream",
"tracing", "tracing",
"unicode-segmentation", "unicode-segmentation",
"uuid", "uuid",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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