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;
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;
}
}

View File

@ -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",

View File

@ -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" }

View File

@ -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",

View File

@ -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" }

View File

@ -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)
})
}

View File

@ -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)

View File

@ -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?;
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 {
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<()>> {
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,
}
}
}

View File

@ -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)
}
}

View File

@ -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>>();

View File

@ -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 })
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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!(

View File

@ -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);
}

View File

@ -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)

View File

@ -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"}

View File

@ -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) = 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);
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 {}

View File

@ -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,
};

View File

@ -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 {

View File

@ -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(),
})
})

View File

@ -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(),
})
})

View File

@ -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(),

View File

@ -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()
}
}

View File

@ -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 {