diff --git a/frontend/rust-lib/flowy-core/src/controller.rs b/frontend/rust-lib/flowy-core/src/controller.rs index 0aa3129588..e2b6b46436 100644 --- a/frontend/rust-lib/flowy-core/src/controller.rs +++ b/frontend/rust-lib/flowy-core/src/controller.rs @@ -6,13 +6,15 @@ use flowy_document::context::DocumentContext; use flowy_sync::RevisionWebSocket; use lazy_static::lazy_static; +use futures_core::future::BoxFuture; + use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; use crate::{ dart_notification::{send_dart_notification, WorkspaceNotification}, entities::workspace::RepeatedWorkspace, - errors::{FlowyError, FlowyResult}, + errors::FlowyResult, module::{FolderCouldServiceV1, WorkspaceUser}, services::{persistence::FolderPersistence, AppController, TrashController, ViewController, WorkspaceController}, }; @@ -95,27 +97,47 @@ impl FolderManager { pub async fn did_receive_ws_data(&self, _data: Bytes) {} - pub async fn user_did_sign_in(&self, token: &str) -> FlowyResult<()> { - log::debug!("workspace initialize after sign in"); - let _ = self.init(token).await?; + pub async fn initialize(&self, token: &str) -> FlowyResult<()> { + self.initialize_with_fn(token, || Box::pin(async { Ok(()) })).await?; Ok(()) } - pub async fn user_did_logout(&self) { self.persistence.user_did_logout() } + pub async fn clear(&self) { self.persistence.user_did_logout() } - pub async fn user_session_expired(&self) { self.persistence.user_did_logout(); } + pub async fn initialize_with_new_user(&self, token: &str) -> FlowyResult<()> { + self.initialize_with_fn(token, || Box::pin(self.initial_default_workspace())) + .await + } - pub async fn user_did_sign_up(&self, _token: &str) -> FlowyResult<()> { + async fn initialize_with_fn<'a, F>(&'a self, token: &str, f: F) -> FlowyResult<()> + where + F: FnOnce() -> BoxFuture<'a, FlowyResult<()>>, + { + if let Some(is_init) = INIT_WORKSPACE.read().get(token) { + if *is_init { + return Ok(()); + } + } + INIT_WORKSPACE.write().insert(token.to_owned(), true); + + self.persistence.initialize().await?; + f().await?; + let _ = self.app_controller.initialize()?; + let _ = self.view_controller.initialize()?; + Ok(()) + } + + async fn initial_default_workspace(&self) -> FlowyResult<()> { log::debug!("Create user default workspace"); let time = Utc::now(); - let mut workspace = user_default::create_default_workspace(time); - let apps = workspace.take_apps().into_inner(); + let workspace = user_default::create_default_workspace(time); + let apps = workspace.apps.clone().into_inner(); let cloned_workspace = workspace.clone(); let _ = self.workspace_controller.create_workspace_on_local(workspace).await?; - for mut app in apps { + for app in apps { let app_id = app.id.clone(); - let views = app.take_belongings().into_inner(); + let views = app.belongings.clone().into_inner(); let _ = self.app_controller.create_app_on_local(app).await?; for (index, view) in views.into_iter().enumerate() { let view_data = if index == 0 { @@ -145,25 +167,6 @@ impl FolderManager { send_dart_notification(&token, WorkspaceNotification::UserCreateWorkspace) .payload(repeated_workspace) .send(); - - tracing::debug!("Create default workspace after sign up"); - self.persistence.user_did_login().await?; - let _ = self.init(&token).await?; - Ok(()) - } - - async fn init(&self, token: &str) -> Result<(), FlowyError> { - if let Some(is_init) = INIT_WORKSPACE.read().get(token) { - if *is_init { - return Ok(()); - } - } - INIT_WORKSPACE.write().insert(token.to_owned(), true); - let _ = self.workspace_controller.init()?; - let _ = self.app_controller.init()?; - let _ = self.view_controller.init()?; - let _ = self.trash_controller.init()?; - Ok(()) } } diff --git a/frontend/rust-lib/flowy-core/src/services/app/controller.rs b/frontend/rust-lib/flowy-core/src/services/app/controller.rs index 218c085374..eb302cb08d 100644 --- a/frontend/rust-lib/flowy-core/src/services/app/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/app/controller.rs @@ -38,7 +38,7 @@ impl AppController { } } - pub fn init(&self) -> Result<(), FlowyError> { + pub fn initialize(&self) -> Result<(), FlowyError> { self.listen_trash_controller_event(); Ok(()) } diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs index 32bb212e25..dd25a63b26 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs @@ -57,7 +57,8 @@ impl FolderPersistence { } } - pub fn begin_transaction(&self, f: F) -> FlowyResult + #[allow(dead_code)] + pub fn begin_transaction2(&self, f: F) -> FlowyResult where F: for<'a> FnOnce(Box) -> FlowyResult, { @@ -76,29 +77,34 @@ impl FolderPersistence { conn.immediate_transaction::<_, FlowyError, _>(|| f(Box::new(V1Transaction(&conn)))) } - pub fn begin_transaction2(&self, f: F) -> FlowyResult + pub fn begin_transaction(&self, f: F) -> FlowyResult where F: FnOnce(Arc) -> FlowyResult, { match self.folder_editor.read().clone() { - None => Err(FlowyError::internal()), + None => { + tracing::error!("FolderEditor should be initialized after user login in."); + let editor = futures::executor::block_on(async { self.init_folder_editor().await })?; + f(editor) + }, Some(editor) => f(editor), } } - pub fn user_did_logout(&self) { - // let user_id = user.user_id()?; - // let pool = database.db_pool()?; - // let folder_editor = Arc::new(FolderEditor::new(&user_id, pool)?); - *self.folder_editor.write() = None; + pub fn user_did_logout(&self) { *self.folder_editor.write() = None; } + + pub async fn initialize(&self) -> FlowyResult<()> { + let _ = self.init_folder_editor().await?; + Ok(()) } - pub async fn user_did_login(&self) -> FlowyResult<()> { + async fn init_folder_editor(&self) -> FlowyResult> { let user_id = self.user.user_id()?; let token = self.user.token()?; let pool = self.database.db_pool()?; let folder_editor = FolderEditor::new(&user_id, &token, pool).await?; - *self.folder_editor.write() = Some(Arc::new(folder_editor)); - Ok(()) + let editor = Arc::new(folder_editor); + *self.folder_editor.write() = Some(editor.clone()); + Ok(editor) } } diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs index 74aa524e1e..03fc8e6706 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs @@ -1,5 +1,8 @@ use crate::services::persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}; -use flowy_collaboration::{entities::revision::Revision, folder::FolderPad}; +use flowy_collaboration::{ + entities::revision::Revision, + folder::{FolderChange, FolderPad}, +}; use flowy_core_data_model::entities::{ app::App, prelude::{RepeatedTrash, Trash, View, Workspace}, @@ -7,7 +10,6 @@ use flowy_core_data_model::entities::{ use flowy_error::{FlowyError, FlowyResult}; use flowy_sync::{RevisionCache, RevisionCloudService, RevisionManager, RevisionObjectBuilder}; use lib_infra::future::FutureResult; -use lib_ot::core::PlainDelta; use lib_sqlite::ConnectionPool; use parking_lot::RwLock; use std::sync::Arc; @@ -37,10 +39,10 @@ impl FolderEditor { }) } - fn add_local_delta(&self, delta: PlainDelta) -> FlowyResult<()> { + fn apply_change(&self, change: FolderChange) -> FlowyResult<()> { + let FolderChange { delta, md5 } = change; let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair(); let delta_data = delta.to_bytes(); - let md5 = self.folder_pad.read().md5(); let revision = Revision::new( &self.rev_manager.object_id, base_rev_id, @@ -56,8 +58,8 @@ impl FolderEditor { impl FolderPersistenceTransaction for FolderEditor { fn create_workspace(&self, _user_id: &str, workspace: Workspace) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().create_workspace(workspace)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().create_workspace(workspace)? { + let _ = self.apply_change(change)?; } Ok(()) } @@ -68,37 +70,37 @@ impl FolderPersistenceTransaction for FolderEditor { } fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { - if let Some(delta) = self + if let Some(change) = self .folder_pad .write() .update_workspace(&changeset.id, changeset.name, changeset.desc)? { - let _ = self.add_local_delta(delta)?; + let _ = self.apply_change(change)?; } Ok(()) } fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().delete_workspace(workspace_id)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().delete_workspace(workspace_id)? { + let _ = self.apply_change(change)?; } Ok(()) } fn create_app(&self, app: App) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().create_app(app)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().create_app(app)? { + let _ = self.apply_change(change)?; } Ok(()) } fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { - if let Some(delta) = self + if let Some(change) = self .folder_pad .write() .update_app(&changeset.id, changeset.name, changeset.desc)? { - let _ = self.add_local_delta(delta)?; + let _ = self.apply_change(change)?; } Ok(()) } @@ -120,15 +122,15 @@ impl FolderPersistenceTransaction for FolderEditor { fn delete_app(&self, app_id: &str) -> FlowyResult { let app = self.folder_pad.read().read_app(app_id)?; - if let Some(delta) = self.folder_pad.write().delete_app(app_id)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().delete_app(app_id)? { + let _ = self.apply_change(change)?; } Ok(app) } fn create_view(&self, view: View) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().create_view(view)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().create_view(view)? { + let _ = self.apply_change(change)?; } Ok(()) } @@ -144,27 +146,27 @@ impl FolderPersistenceTransaction for FolderEditor { } fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().update_view( + if let Some(change) = self.folder_pad.write().update_view( &changeset.id, changeset.name, changeset.desc, changeset.modified_time, )? { - let _ = self.add_local_delta(delta)?; + let _ = self.apply_change(change)?; } Ok(()) } fn delete_view(&self, view_id: &str) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().delete_view(view_id)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().delete_view(view_id)? { + let _ = self.apply_change(change)?; } Ok(()) } fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().create_trash(trashes)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().create_trash(trashes)? { + let _ = self.apply_change(change)?; } Ok(()) } @@ -175,8 +177,8 @@ impl FolderPersistenceTransaction for FolderEditor { } fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { - if let Some(delta) = self.folder_pad.write().delete_trash(trash_ids)? { - let _ = self.add_local_delta(delta)?; + if let Some(change) = self.folder_pad.write().delete_trash(trash_ids)? { + let _ = self.apply_change(change)?; } Ok(()) } @@ -193,6 +195,7 @@ impl RevisionObjectBuilder for FolderPadBuilder { } struct FolderRevisionCloudServiceImpl { + #[allow(dead_code)] token: String, // server: Arc, } @@ -203,3 +206,50 @@ impl RevisionCloudService for FolderRevisionCloudServiceImpl { FutureResult::new(async move { Ok(vec![]) }) } } + +impl FolderPersistenceTransaction for Arc +where + T: FolderPersistenceTransaction + ?Sized, +{ + fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> { + (**self).create_workspace(user_id, workspace) + } + + fn read_workspaces(&self, user_id: &str, workspace_id: Option) -> FlowyResult> { + (**self).read_workspaces(user_id, workspace_id) + } + + fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { + (**self).update_workspace(changeset) + } + + fn delete_workspace(&self, workspace_id: &str) -> FlowyResult<()> { (**self).delete_workspace(workspace_id) } + + fn create_app(&self, app: App) -> FlowyResult<()> { (**self).create_app(app) } + + fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { (**self).update_app(changeset) } + + fn read_app(&self, app_id: &str) -> FlowyResult { (**self).read_app(app_id) } + + fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { + (**self).read_workspace_apps(workspace_id) + } + + fn delete_app(&self, app_id: &str) -> FlowyResult { (**self).delete_app(app_id) } + + fn create_view(&self, view: View) -> FlowyResult<()> { (**self).create_view(view) } + + fn read_view(&self, view_id: &str) -> FlowyResult { (**self).read_view(view_id) } + + fn read_views(&self, belong_to_id: &str) -> FlowyResult> { (**self).read_views(belong_to_id) } + + fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { (**self).update_view(changeset) } + + fn delete_view(&self, view_id: &str) -> FlowyResult<()> { (**self).delete_view(view_id) } + + fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { (**self).create_trash(trashes) } + + fn read_trash(&self, trash_id: Option) -> FlowyResult { (**self).read_trash(trash_id) } + + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { (**self).delete_trash(trash_ids) } +} diff --git a/frontend/rust-lib/flowy-core/src/services/trash/controller.rs b/frontend/rust-lib/flowy-core/src/services/trash/controller.rs index 4dd435a160..8d276b5223 100644 --- a/frontend/rust-lib/flowy-core/src/services/trash/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/trash/controller.rs @@ -31,8 +31,6 @@ impl TrashController { } } - pub(crate) fn init(&self) -> Result<(), FlowyError> { Ok(()) } - #[tracing::instrument(level = "debug", skip(self), fields(putback) err)] pub async fn putback(&self, trash_id: &str) -> FlowyResult<()> { let (tx, mut rx) = mpsc::channel::>(1); diff --git a/frontend/rust-lib/flowy-core/src/services/view/controller.rs b/frontend/rust-lib/flowy-core/src/services/view/controller.rs index 46776792a2..c2f62d597f 100644 --- a/frontend/rust-lib/flowy-core/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/view/controller.rs @@ -54,7 +54,7 @@ impl ViewController { } } - pub(crate) fn init(&self) -> Result<(), FlowyError> { + pub(crate) fn initialize(&self) -> Result<(), FlowyError> { let _ = self.document_ctx.init()?; self.listen_trash_can_event(); Ok(()) diff --git a/frontend/rust-lib/flowy-core/src/services/view/event_handler.rs b/frontend/rust-lib/flowy-core/src/services/view/event_handler.rs index f3acad59bc..8ad82d11db 100644 --- a/frontend/rust-lib/flowy-core/src/services/view/event_handler.rs +++ b/frontend/rust-lib/flowy-core/src/services/view/event_handler.rs @@ -35,6 +35,8 @@ pub(crate) async fn read_view_handler( ) -> DataResult { let params: ViewId = data.into_inner().try_into()?; let mut view = controller.read_view(params.clone()).await?; + // For the moment, app and view can contains lots of views. Reading the view + // belongings using the view_id. view.belongings = controller.read_views_belong_to(¶ms.view_id).await?; data_result(view) diff --git a/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs b/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs index 17db8009a3..f5ef2642a3 100644 --- a/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs @@ -34,8 +34,6 @@ impl WorkspaceController { } } - pub(crate) fn init(&self) -> Result<(), FlowyError> { Ok(()) } - pub(crate) async fn create_workspace_from_params( &self, params: CreateWorkspaceParams, diff --git a/frontend/rust-lib/flowy-sdk/src/lib.rs b/frontend/rust-lib/flowy-sdk/src/lib.rs index 34920ad1ff..475010c939 100644 --- a/frontend/rust-lib/flowy-sdk/src/lib.rs +++ b/frontend/rust-lib/flowy-sdk/src/lib.rs @@ -176,19 +176,23 @@ async fn _listen_user_status( let result = || async { match status { UserStatus::Login { token, user_id } => { - let _ = folder_manager.user_did_sign_in(&token).await?; + tracing::trace!("User did login"); + let _ = folder_manager.initialize(&token).await?; let _ = ws_conn.start(token, user_id).await?; }, UserStatus::Logout { .. } => { - folder_manager.user_did_logout().await; + tracing::trace!("User did logout"); + folder_manager.clear().await; let _ = ws_conn.stop().await; }, UserStatus::Expired { .. } => { - folder_manager.user_session_expired().await; + tracing::trace!("User session has been expired"); + folder_manager.clear().await; let _ = ws_conn.stop().await; }, UserStatus::SignUp { profile, ret } => { - let _ = folder_manager.user_did_sign_up(&profile.token).await?; + tracing::trace!("User did sign up"); + let _ = folder_manager.initialize_with_new_user(&profile.token).await?; let _ = ws_conn.start(profile.token.clone(), profile.id.clone()).await?; let _ = ret.send(()); }, diff --git a/shared-lib/flowy-collaboration/Cargo.toml b/shared-lib/flowy-collaboration/Cargo.toml index 7ff7589521..930d5e34e8 100644 --- a/shared-lib/flowy-collaboration/Cargo.toml +++ b/shared-lib/flowy-collaboration/Cargo.toml @@ -26,4 +26,6 @@ chrono = "0.4.19" parking_lot = "0.11" dashmap = "4.0" futures = "0.3.15" -async-stream = "0.3.2" \ No newline at end of file +async-stream = "0.3.2" + +[dev-dependencies] diff --git a/shared-lib/flowy-collaboration/src/folder/folder_pad.rs b/shared-lib/flowy-collaboration/src/folder/folder_pad.rs index 047ad1fee6..8c95b10cdf 100644 --- a/shared-lib/flowy-collaboration/src/folder/folder_pad.rs +++ b/shared-lib/flowy-collaboration/src/folder/folder_pad.rs @@ -2,7 +2,6 @@ use crate::{ entities::revision::{md5, Revision}, errors::{CollaborateError, CollaborateResult}, }; - use dissimilar::*; use flowy_core_data_model::entities::{app::App, trash::Trash, view::View, workspace::Workspace}; use lib_ot::core::{Delta, FlowyStr, OperationTransformable, PlainDelta, PlainDeltaBuilder, PlainTextAttributes}; @@ -33,6 +32,12 @@ impl std::default::Default for FolderPad { } } +pub struct FolderChange { + pub delta: PlainDelta, + /// md5: the md5 of the FolderPad's delta after applying the change. + pub md5: String, +} + impl FolderPad { pub fn from_revisions(revisions: Vec) -> CollaborateResult { let mut folder_delta = PlainDelta::new(); @@ -60,7 +65,7 @@ impl FolderPad { Ok(folder) } - pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult> { + pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult> { let workspace = Arc::new(workspace); if self.workspaces.contains(&workspace) { tracing::warn!("[RootFolder]: Duplicate workspace"); @@ -78,8 +83,8 @@ impl FolderPad { workspace_id: &str, name: Option, desc: Option, - ) -> CollaborateResult> { - self.modify_workspace(workspace_id, |workspace| { + ) -> CollaborateResult> { + self.with_workspace(workspace_id, |workspace| { if let Some(name) = name { workspace.name = name; } @@ -112,16 +117,16 @@ impl FolderPad { } } - pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult> { + pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult> { self.modify_workspaces(|workspaces| { workspaces.retain(|w| w.id != workspace_id); Ok(Some(())) }) } - pub fn create_app(&mut self, app: App) -> CollaborateResult> { + pub fn create_app(&mut self, app: App) -> CollaborateResult> { let workspace_id = app.workspace_id.clone(); - self.modify_workspace(&workspace_id, move |workspace| { + self.with_workspace(&workspace_id, move |workspace| { if workspace.apps.contains(&app) { tracing::warn!("[RootFolder]: Duplicate app"); return Ok(None); @@ -145,8 +150,8 @@ impl FolderPad { app_id: &str, name: Option, desc: Option, - ) -> CollaborateResult> { - self.modify_app(app_id, move |app| { + ) -> CollaborateResult> { + self.with_app(app_id, move |app| { if let Some(name) = name { app.name = name; } @@ -158,17 +163,17 @@ impl FolderPad { }) } - pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult> { + pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult> { let app = self.read_app(app_id)?; - self.modify_workspace(&app.workspace_id, |workspace| { + self.with_workspace(&app.workspace_id, |workspace| { workspace.apps.retain(|app| app.id != app_id); Ok(Some(())) }) } - pub fn create_view(&mut self, view: View) -> CollaborateResult> { + pub fn create_view(&mut self, view: View) -> CollaborateResult> { let app_id = view.belong_to_id.clone(); - self.modify_app(&app_id, move |app| { + self.with_app(&app_id, move |app| { if app.belongings.contains(&view) { tracing::warn!("[RootFolder]: Duplicate view"); return Ok(None); @@ -193,12 +198,11 @@ impl FolderPad { for workspace in &self.workspaces { for app in &(*workspace.apps) { if app.id == belong_to_id { - return Ok(app.clone().belongings.take_items()); + return Ok(app.belongings.clone().take_items()); } } } - Err(CollaborateError::record_not_found() - .context(format!("Can't find any views with belong_to_id {}", belong_to_id))) + Ok(vec![]) } pub fn update_view( @@ -207,9 +211,9 @@ impl FolderPad { name: Option, desc: Option, modified_time: i64, - ) -> CollaborateResult> { + ) -> CollaborateResult> { let view = self.read_view(view_id)?; - self.modify_view(&view.belong_to_id, view_id, |view| { + self.with_view(&view.belong_to_id, view_id, |view| { if let Some(name) = name { view.name = name; } @@ -223,16 +227,16 @@ impl FolderPad { }) } - pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult> { + pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult> { let view = self.read_view(view_id)?; - self.modify_app(&view.belong_to_id, |app| { + self.with_app(&view.belong_to_id, |app| { app.belongings.retain(|view| view.id != view_id); Ok(Some(())) }) } - pub fn create_trash(&mut self, trash: Vec) -> CollaborateResult> { - self.modify_trash(|t| { + pub fn create_trash(&mut self, trash: Vec) -> CollaborateResult> { + self.with_trash(|t| { let mut new_trash = trash.into_iter().map(Arc::new).collect::>>(); t.append(&mut new_trash); @@ -250,13 +254,13 @@ impl FolderPad { } } - pub fn delete_trash(&mut self, trash_ids: Option>) -> CollaborateResult> { + pub fn delete_trash(&mut self, trash_ids: Option>) -> CollaborateResult> { match trash_ids { - None => self.modify_trash(|trash| { + None => self.with_trash(|trash| { trash.clear(); Ok(Some(())) }), - Some(trash_ids) => self.modify_trash(|trash| { + Some(trash_ids) => self.with_trash(|trash| { trash.retain(|t| !trash_ids.contains(&t.id)); Ok(Some(())) }), @@ -267,7 +271,7 @@ impl FolderPad { } impl FolderPad { - fn modify_workspaces(&mut self, f: F) -> CollaborateResult> + fn modify_workspaces(&mut self, f: F) -> CollaborateResult> where F: FnOnce(&mut Vec>) -> CollaborateResult>, { @@ -279,12 +283,12 @@ impl FolderPad { let new = self.to_json()?; let delta = cal_diff(old, new); self.root = self.root.compose(&delta)?; - Ok(Some(delta)) + Ok(Some(FolderChange { delta, md5: self.md5() })) }, } } - fn modify_workspace(&mut self, workspace_id: &str, f: F) -> CollaborateResult> + fn with_workspace(&mut self, workspace_id: &str, f: F) -> CollaborateResult> where F: FnOnce(&mut Workspace) -> CollaborateResult>, { @@ -298,7 +302,7 @@ impl FolderPad { }) } - fn modify_trash(&mut self, f: F) -> CollaborateResult> + fn with_trash(&mut self, f: F) -> CollaborateResult> where F: FnOnce(&mut Vec>) -> CollaborateResult>, { @@ -310,12 +314,12 @@ impl FolderPad { let new = self.to_json()?; let delta = cal_diff(old, new); self.root = self.root.compose(&delta)?; - Ok(Some(delta)) + Ok(Some(FolderChange { delta, md5: self.md5() })) }, } } - fn modify_app(&mut self, app_id: &str, f: F) -> CollaborateResult> + fn with_app(&mut self, app_id: &str, f: F) -> CollaborateResult> where F: FnOnce(&mut App) -> CollaborateResult>, { @@ -331,16 +335,17 @@ impl FolderPad { Some(workspace) => workspace.id.clone(), }; - self.modify_workspace(&workspace_id, |workspace| { + self.with_workspace(&workspace_id, |workspace| { + // It's ok to unwrap because we get the workspace from the app_id. f(workspace.apps.iter_mut().find(|app| app_id == app.id).unwrap()) }) } - fn modify_view(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult> + fn with_view(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult> where F: FnOnce(&mut View) -> CollaborateResult>, { - self.modify_app(belong_to_id, |app| { + self.with_app(belong_to_id, |app| { match app.belongings.iter_mut().find(|view| view_id == view.id) { None => { tracing::warn!("[RootFolder]: Can't find any view with id: {}", view_id); @@ -381,7 +386,7 @@ mod tests { #![allow(clippy::all)] use crate::folder::folder_pad::FolderPad; use chrono::Utc; - use flowy_core_data_model::entities::{app::App, view::View, workspace::Workspace}; + use flowy_core_data_model::entities::{app::App, trash::Trash, view::View, workspace::Workspace}; use lib_ot::core::{OperationTransformable, PlainDelta, PlainDeltaBuilder}; #[test] @@ -391,11 +396,11 @@ mod tests { let _time = Utc::now(); let mut workspace_1 = Workspace::default(); workspace_1.name = "My first workspace".to_owned(); - let delta_1 = folder.create_workspace(workspace_1).unwrap().unwrap(); + let delta_1 = folder.create_workspace(workspace_1).unwrap().unwrap().delta; let mut workspace_2 = Workspace::default(); workspace_2.name = "My second workspace".to_owned(); - let delta_2 = folder.create_workspace(workspace_2).unwrap().unwrap(); + let delta_2 = folder.create_workspace(workspace_2).unwrap().unwrap().delta; let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta_1, delta_2]); assert_eq!(folder, folder_from_delta); @@ -404,13 +409,24 @@ mod tests { #[test] fn folder_update_workspace() { let (mut folder, initial_delta, workspace) = test_folder(); + assert_folder_equal( + &folder, + &make_folder_from_delta(initial_delta.clone(), vec![]), + r#"{"workspaces":[{"id":"1","name":"😁 my first workspace","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#, + ); + let delta = folder - .update_workspace(&workspace.id, Some("βœ…οΈ".to_string()), None) + .update_workspace(&workspace.id, Some("☺️ rename workspace️".to_string()), None) .unwrap() - .unwrap(); + .unwrap() + .delta; let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]); - assert_eq!(folder, folder_from_delta); + assert_folder_equal( + &folder, + &folder_from_delta, + r#"{"workspaces":[{"id":"1","name":"☺️ rename workspace️","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#, + ); } #[test] @@ -418,55 +434,269 @@ mod tests { let (folder, initial_delta, _app) = test_app_folder(); let folder_from_delta = make_folder_from_delta(initial_delta, vec![]); assert_eq!(folder, folder_from_delta); + assert_folder_equal( + &folder, + &folder_from_delta, + r#"{ + "workspaces": [ + { + "id": "1", + "name": "😁 my first workspace", + "desc": "", + "apps": [ + { + "id": "", + "workspace_id": "1", + "name": "😁 my first app", + "desc": "", + "belongings": [], + "version": 0, + "modified_time": 0, + "create_time": 0 + } + ], + "modified_time": 0, + "create_time": 0 + } + ], + "trash": [] + }"#, + ); } #[test] fn folder_update_app() { let (mut folder, initial_delta, app) = test_app_folder(); let delta = folder - .update_app(&app.id, Some("😁😁😁".to_owned()), None) + .update_app(&app.id, Some("πŸ€ͺ rename app".to_owned()), None) .unwrap() - .unwrap(); + .unwrap() + .delta; - let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]); - assert_eq!(folder, folder_from_delta); + let new_folder = make_folder_from_delta(initial_delta, vec![delta]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ + "workspaces": [ + { + "id": "1", + "name": "😁 my first workspace", + "desc": "", + "apps": [ + { + "id": "", + "workspace_id": "1", + "name": "πŸ€ͺ rename app", + "desc": "", + "belongings": [], + "version": 0, + "modified_time": 0, + "create_time": 0 + } + ], + "modified_time": 0, + "create_time": 0 + } + ], + "trash": [] + }"#, + ); } #[test] fn folder_delete_app() { let (mut folder, initial_delta, app) = test_app_folder(); - let delta = folder.delete_app(&app.id).unwrap().unwrap(); - - let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]); - assert_eq!(folder, folder_from_delta); + let delta = folder.delete_app(&app.id).unwrap().unwrap().delta; + let new_folder = make_folder_from_delta(initial_delta, vec![delta]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ + "workspaces": [ + { + "id": "1", + "name": "😁 my first workspace", + "desc": "", + "apps": [], + "modified_time": 0, + "create_time": 0 + } + ], + "trash": [] + }"#, + ); } #[test] fn folder_add_view() { let (folder, initial_delta, _view) = test_view_folder(); - let folder_from_delta = make_folder_from_delta(initial_delta, vec![]); - assert_eq!(folder, folder_from_delta); + assert_folder_equal( + &folder, + &make_folder_from_delta(initial_delta, vec![]), + r#" + { + "workspaces": [ + { + "id": "1", + "name": "😁 my first workspace", + "desc": "", + "apps": [ + { + "id": "", + "workspace_id": "1", + "name": "😁 my first app", + "desc": "", + "belongings": [ + { + "id": "", + "belong_to_id": "", + "name": "πŸŽƒ my first view", + "desc": "", + "view_type": "Blank", + "version": 0, + "belongings": [], + "modified_time": 0, + "create_time": 0 + } + ], + "version": 0, + "modified_time": 0, + "create_time": 0 + } + ], + "modified_time": 0, + "create_time": 0 + } + ], + "trash": [] + }"#, + ); } #[test] fn folder_update_view() { let (mut folder, initial_delta, view) = test_view_folder(); let delta = folder - .update_view(&view.id, Some("😁😁😁".to_owned()), None, 123) + .update_view(&view.id, Some("😦 rename view".to_owned()), None, 123) .unwrap() - .unwrap(); + .unwrap() + .delta; - let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]); - assert_eq!(folder, folder_from_delta); + let new_folder = make_folder_from_delta(initial_delta, vec![delta]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ + "workspaces": [ + { + "id": "1", + "name": "😁 my first workspace", + "desc": "", + "apps": [ + { + "id": "", + "workspace_id": "1", + "name": "😁 my first app", + "desc": "", + "belongings": [ + { + "id": "", + "belong_to_id": "", + "name": "😦 rename view", + "desc": "", + "view_type": "Blank", + "version": 0, + "belongings": [], + "modified_time": 123, + "create_time": 0 + } + ], + "version": 0, + "modified_time": 0, + "create_time": 0 + } + ], + "modified_time": 0, + "create_time": 0 + } + ], + "trash": [] + }"#, + ); } #[test] fn folder_delete_view() { let (mut folder, initial_delta, view) = test_view_folder(); - let delta = folder.delete_view(&view.id).unwrap().unwrap(); + let delta = folder.delete_view(&view.id).unwrap().unwrap().delta; - let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]); - assert_eq!(folder, folder_from_delta); + let new_folder = make_folder_from_delta(initial_delta, vec![delta]); + assert_folder_equal( + &folder, + &new_folder, + r#"{ + "workspaces": [ + { + "id": "1", + "name": "😁 my first workspace", + "desc": "", + "apps": [ + { + "id": "", + "workspace_id": "1", + "name": "😁 my first app", + "desc": "", + "belongings": [], + "version": 0, + "modified_time": 0, + "create_time": 0 + } + ], + "modified_time": 0, + "create_time": 0 + } + ], + "trash": [] + }"#, + ); + } + + #[test] + fn folder_add_trash() { + let (folder, initial_delta, _trash) = test_trash(); + assert_folder_equal( + &folder, + &make_folder_from_delta(initial_delta, vec![]), + r#"{ + "workspaces": [], + "trash": [ + { + "id": "1", + "name": "🚽 my first trash", + "modified_time": 0, + "create_time": 0, + "ty": "Unknown" + } + ] + } + "#, + ); + } + + #[test] + fn folder_delete_trash() { + let (mut folder, initial_delta, trash) = test_trash(); + let delta = folder.delete_trash(Some(vec![trash.id])).unwrap().unwrap().delta; + assert_folder_equal( + &folder, + &make_folder_from_delta(initial_delta, vec![delta]), + r#"{ + "workspaces": [], + "trash": [] + } + "#, + ); } fn test_folder() -> (FolderPad, PlainDelta, Workspace) { @@ -474,12 +704,12 @@ mod tests { let folder_json = serde_json::to_string(&folder).unwrap(); let mut delta = PlainDeltaBuilder::new().insert(&folder_json).build(); - let _time = Utc::now(); let mut workspace = Workspace::default(); + workspace.name = "😁 my first workspace".to_owned(); workspace.id = "1".to_owned(); delta = delta - .compose(&folder.create_workspace(workspace.clone()).unwrap().unwrap()) + .compose(&folder.create_workspace(workspace.clone()).unwrap().unwrap().delta) .unwrap(); (folder, delta, workspace) @@ -489,10 +719,10 @@ mod tests { let (mut folder, mut initial_delta, workspace) = test_folder(); let mut app = App::default(); app.workspace_id = workspace.id; - app.name = "My first app".to_owned(); + app.name = "😁 my first app".to_owned(); initial_delta = initial_delta - .compose(&folder.create_app(app.clone()).unwrap().unwrap()) + .compose(&folder.create_app(app.clone()).unwrap().unwrap().delta) .unwrap(); (folder, initial_delta, app) @@ -502,19 +732,46 @@ mod tests { let (mut folder, mut initial_delta, app) = test_app_folder(); let mut view = View::default(); view.belong_to_id = app.id.clone(); - view.name = "My first view".to_owned(); + view.name = "πŸŽƒ my first view".to_owned(); initial_delta = initial_delta - .compose(&folder.create_view(view.clone()).unwrap().unwrap()) + .compose(&folder.create_view(view.clone()).unwrap().unwrap().delta) .unwrap(); (folder, initial_delta, view) } + fn test_trash() -> (FolderPad, PlainDelta, Trash) { + let mut folder = FolderPad::default(); + let folder_json = serde_json::to_string(&folder).unwrap(); + let mut delta = PlainDeltaBuilder::new().insert(&folder_json).build(); + + let mut trash = Trash::default(); + trash.name = "🚽 my first trash".to_owned(); + trash.id = "1".to_owned(); + + delta = delta + .compose(&folder.create_trash(vec![trash.clone()]).unwrap().unwrap().delta) + .unwrap(); + + (folder, delta, trash) + } + fn make_folder_from_delta(mut initial_delta: PlainDelta, deltas: Vec) -> FolderPad { for delta in deltas { initial_delta = initial_delta.compose(&delta).unwrap(); } FolderPad::from_delta(initial_delta).unwrap() } + + fn assert_folder_equal(old: &FolderPad, new: &FolderPad, expected: &str) { + assert_eq!(old, new); + + let json1 = old.to_json().unwrap(); + let json2 = new.to_json().unwrap(); + + let expect_folder: FolderPad = serde_json::from_str(expected).unwrap(); + assert_eq!(json1, expect_folder.to_json().unwrap()); + assert_eq!(json1, json2); + } } diff --git a/shared-lib/flowy-core-data-model/src/entities/app.rs b/shared-lib/flowy-core-data-model/src/entities/app.rs index 841fb61448..777e091d49 100644 --- a/shared-lib/flowy-core-data-model/src/entities/app.rs +++ b/shared-lib/flowy-core-data-model/src/entities/app.rs @@ -38,10 +38,6 @@ pub struct App { pub create_time: i64, } -impl App { - pub fn take_belongings(&mut self) -> RepeatedView { std::mem::take(&mut self.belongings) } -} - #[derive(Eq, PartialEq, Debug, Default, ProtoBuf, Clone, Serialize, Deserialize)] #[serde(transparent)] pub struct RepeatedApp { diff --git a/shared-lib/flowy-core-data-model/src/entities/workspace.rs b/shared-lib/flowy-core-data-model/src/entities/workspace.rs index ab5ed57645..d7c4d0df23 100644 --- a/shared-lib/flowy-core-data-model/src/entities/workspace.rs +++ b/shared-lib/flowy-core-data-model/src/entities/workspace.rs @@ -29,9 +29,6 @@ pub struct Workspace { pub create_time: i64, } -impl Workspace { - pub fn take_apps(&mut self) -> RepeatedApp { std::mem::take(&mut self.apps) } -} #[derive(PartialEq, Debug, Default, ProtoBuf)] pub struct RepeatedWorkspace { #[pb(index = 1)]