From 7f1a0adf135f7beff4b6a9cabc63ac27e345f9e7 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 17 Jan 2022 11:55:36 +0800 Subject: [PATCH] add folder editor --- .../rust-lib/flowy-core/src/controller.rs | 28 +- frontend/rust-lib/flowy-core/src/module.rs | 11 +- .../flowy-core/src/services/app/controller.rs | 18 +- .../src/services/persistence/mod.rs | 57 ++++- .../services/persistence/version_1/v1_impl.rs | 46 ++-- .../src/services/persistence/version_2/mod.rs | 1 + .../services/persistence/version_2/v2_impl.rs | 205 +++++++++++++++ .../src/services/trash/controller.rs | 44 ++-- .../src/services/view/controller.rs | 20 +- .../src/services/workspace/controller.rs | 16 +- frontend/rust-lib/flowy-core/src/util.rs | 14 +- .../rust-lib/flowy-document/src/controller.rs | 6 +- .../flowy-error/src/ext/collaborate.rs | 9 +- frontend/rust-lib/flowy-error/src/ext/ot.rs | 2 +- .../flowy-net/src/http_server/core.rs | 6 +- .../flowy-net/src/local_server/server.rs | 6 +- .../flowy-sdk/src/deps_resolve/core_deps.rs | 4 +- frontend/rust-lib/flowy-sync/src/cache/mod.rs | 17 +- .../src/client_document/document_pad.rs | 1 - .../src/folder/folder_manager.rs | 3 - .../folder/{folder_data.rs => folder_pad.rs} | 240 ++++++++++++------ .../flowy-collaboration/src/folder/mod.rs | 5 +- 22 files changed, 548 insertions(+), 211 deletions(-) create mode 100644 frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs delete mode 100644 shared-lib/flowy-collaboration/src/folder/folder_manager.rs rename shared-lib/flowy-collaboration/src/folder/{folder_data.rs => folder_pad.rs} (60%) diff --git a/frontend/rust-lib/flowy-core/src/controller.rs b/frontend/rust-lib/flowy-core/src/controller.rs index afa287c0dc..0aa3129588 100644 --- a/frontend/rust-lib/flowy-core/src/controller.rs +++ b/frontend/rust-lib/flowy-core/src/controller.rs @@ -5,6 +5,7 @@ use flowy_core_data_model::{entities::view::CreateViewParams, user_default}; use flowy_document::context::DocumentContext; use flowy_sync::RevisionWebSocket; use lazy_static::lazy_static; + use parking_lot::RwLock; use std::{collections::HashMap, sync::Arc}; @@ -12,14 +13,8 @@ use crate::{ dart_notification::{send_dart_notification, WorkspaceNotification}, entities::workspace::RepeatedWorkspace, errors::{FlowyError, FlowyResult}, - module::{WorkspaceCloudService, WorkspaceUser}, - services::{ - persistence::FlowyCorePersistence, - AppController, - TrashController, - ViewController, - WorkspaceController, - }, + module::{FolderCouldServiceV1, WorkspaceUser}, + services::{persistence::FolderPersistence, AppController, TrashController, ViewController, WorkspaceController}, }; lazy_static! { @@ -28,8 +23,8 @@ lazy_static! { pub struct FolderManager { pub user: Arc, - pub(crate) cloud_service: Arc, - pub(crate) persistence: Arc, + pub(crate) cloud_service: Arc, + pub(crate) persistence: Arc, pub workspace_controller: Arc, pub(crate) app_controller: Arc, pub(crate) view_controller: Arc, @@ -40,8 +35,8 @@ pub struct FolderManager { impl FolderManager { pub(crate) fn new( user: Arc, - cloud_service: Arc, - persistence: Arc, + cloud_service: Arc, + persistence: Arc, flowy_document: Arc, ws_sender: Arc, ) -> Self { @@ -106,13 +101,9 @@ impl FolderManager { Ok(()) } - pub async fn user_did_logout(&self) { - // TODO: (nathan) do something here - } + pub async fn user_did_logout(&self) { self.persistence.user_did_logout() } - pub async fn user_session_expired(&self) { - // TODO: (nathan) do something here - } + pub async fn user_session_expired(&self) { self.persistence.user_did_logout(); } pub async fn user_did_sign_up(&self, _token: &str) -> FlowyResult<()> { log::debug!("Create user default workspace"); @@ -156,6 +147,7 @@ impl FolderManager { .send(); tracing::debug!("Create default workspace after sign up"); + self.persistence.user_did_login().await?; let _ = self.init(&token).await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-core/src/module.rs b/frontend/rust-lib/flowy-core/src/module.rs index 2f11edc94d..6123fc6dd0 100644 --- a/frontend/rust-lib/flowy-core/src/module.rs +++ b/frontend/rust-lib/flowy-core/src/module.rs @@ -10,7 +10,7 @@ use crate::{ event::WorkspaceEvent, services::{ app::event_handler::*, - persistence::FlowyCorePersistence, + persistence::FolderPersistence, trash::event_handler::*, view::event_handler::*, workspace::event_handler::*, @@ -45,11 +45,10 @@ pub fn init_folder( user: Arc, database: Arc, flowy_document: Arc, - cloud_service: Arc, + cloud_service: Arc, ws_sender: Arc, ) -> Arc { - let persistence = Arc::new(FlowyCorePersistence::new(database.clone())); - + let persistence = Arc::new(FolderPersistence::new(user.clone(), database.clone())); Arc::new(FolderManager::new( user, cloud_service, @@ -103,7 +102,7 @@ pub fn create(folder: Arc) -> Module { module } -pub trait WorkspaceCloudService: Send + Sync { +pub trait FolderCouldServiceV1: Send + Sync { fn init(&self); // Workspace @@ -140,3 +139,5 @@ pub trait WorkspaceCloudService: Send + Sync { fn read_trash(&self, token: &str) -> FutureResult; } + +pub trait FolderCouldServiceV2: Send + Sync {} 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 5f0a5cfecf..218c085374 100644 --- a/frontend/rust-lib/flowy-core/src/services/app/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/app/controller.rs @@ -5,9 +5,9 @@ use crate::{ trash::TrashType, }, errors::*, - module::{WorkspaceCloudService, WorkspaceUser}, + module::{FolderCouldServiceV1, WorkspaceUser}, services::{ - persistence::{AppChangeset, FlowyCorePersistence, FlowyCorePersistenceTransaction}, + persistence::{AppChangeset, FolderPersistence, FolderPersistenceTransaction}, TrashController, TrashEvent, }, @@ -18,17 +18,17 @@ use std::{collections::HashSet, sync::Arc}; pub(crate) struct AppController { user: Arc, - persistence: Arc, + persistence: Arc, trash_controller: Arc, - cloud_service: Arc, + cloud_service: Arc, } impl AppController { pub(crate) fn new( user: Arc, - persistence: Arc, + persistence: Arc, trash_can: Arc, - cloud_service: Arc, + cloud_service: Arc, ) -> Self { Self { user, @@ -169,7 +169,7 @@ impl AppController { #[tracing::instrument(level = "trace", skip(persistence, trash_controller))] async fn handle_trash_event( - persistence: Arc, + persistence: Arc, trash_controller: Arc, event: TrashEvent, ) { @@ -207,7 +207,7 @@ async fn handle_trash_event( fn notify_apps_changed<'a>( workspace_id: &str, trash_controller: Arc, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult<()> { let repeated_app = read_local_workspace_apps(workspace_id, trash_controller, transaction)?; send_dart_notification(workspace_id, WorkspaceNotification::WorkspaceAppsChanged) @@ -219,7 +219,7 @@ fn notify_apps_changed<'a>( pub fn read_local_workspace_apps<'a>( workspace_id: &str, trash_controller: Arc, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result { let mut apps = transaction.read_workspace_apps(workspace_id)?; let trash_ids = trash_controller.read_trash_ids(transaction)?; 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 f20f015419..32bb212e25 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/mod.rs @@ -1,10 +1,14 @@ pub mod version_1; mod version_2; +use parking_lot::RwLock; use std::sync::Arc; pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*}; -use crate::module::WorkspaceDatabase; +use crate::{ + module::{WorkspaceDatabase, WorkspaceUser}, + services::persistence::version_2::v2_impl::FolderEditor, +}; use flowy_core_data_model::entities::{ app::App, prelude::RepeatedTrash, @@ -14,7 +18,7 @@ use flowy_core_data_model::entities::{ }; use flowy_error::{FlowyError, FlowyResult}; -pub trait FlowyCorePersistenceTransaction { +pub trait FolderPersistenceTransaction { fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()>; fn read_workspaces(&self, user_id: &str, workspace_id: Option) -> FlowyResult>; fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()>; @@ -33,22 +37,29 @@ pub trait FlowyCorePersistenceTransaction { fn delete_view(&self, view_id: &str) -> FlowyResult<()>; fn create_trash(&self, trashes: Vec) -> FlowyResult<()>; - fn read_all_trash(&self) -> FlowyResult; - fn delete_all_trash(&self) -> FlowyResult<()>; - fn read_trash(&self, trash_id: &str) -> FlowyResult; - fn delete_trash(&self, trash_ids: Vec) -> FlowyResult<()>; + fn read_trash(&self, trash_id: Option) -> FlowyResult; + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()>; } -pub struct FlowyCorePersistence { +pub struct FolderPersistence { + user: Arc, database: Arc, + folder_editor: RwLock>>, } -impl FlowyCorePersistence { - pub fn new(database: Arc) -> Self { Self { database } } +impl FolderPersistence { + pub fn new(user: Arc, database: Arc) -> Self { + let folder_editor = RwLock::new(None); + Self { + user, + database, + folder_editor, + } + } pub fn begin_transaction(&self, f: F) -> FlowyResult where - F: for<'a> FnOnce(Box) -> FlowyResult, + F: for<'a> FnOnce(Box) -> FlowyResult, { //[[immediate_transaction]] // https://sqlite.org/lang_transaction.html @@ -64,4 +75,30 @@ impl FlowyCorePersistence { let conn = self.database.db_connection()?; conn.immediate_transaction::<_, FlowyError, _>(|| f(Box::new(V1Transaction(&conn)))) } + + pub fn begin_transaction2(&self, f: F) -> FlowyResult + where + F: FnOnce(Arc) -> FlowyResult, + { + match self.folder_editor.read().clone() { + None => Err(FlowyError::internal()), + 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 async fn user_did_login(&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(()) + } } diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs index 93bad64d2c..4238aeb6ef 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs @@ -4,7 +4,7 @@ use crate::services::persistence::{ view_sql::{ViewChangeset, ViewTableSql}, workspace_sql::{WorkspaceChangeset, WorkspaceTableSql}, }, - FlowyCorePersistenceTransaction, + FolderPersistenceTransaction, TrashTableSql, }; use flowy_core_data_model::entities::{ @@ -16,7 +16,7 @@ use lib_sqlite::DBConnection; pub struct V1Transaction<'a>(pub &'a DBConnection); -impl<'a> FlowyCorePersistenceTransaction for V1Transaction<'a> { +impl<'a> FolderPersistenceTransaction for V1Transaction<'a> { fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> { let _ = WorkspaceTableSql::create_workspace(user_id, workspace, &*self.0)?; Ok(()) @@ -93,27 +93,35 @@ impl<'a> FlowyCorePersistenceTransaction for V1Transaction<'a> { Ok(()) } - fn read_all_trash(&self) -> FlowyResult { TrashTableSql::read_all(&*self.0) } - - fn delete_all_trash(&self) -> FlowyResult<()> { TrashTableSql::delete_all(&*self.0) } - - fn read_trash(&self, trash_id: &str) -> FlowyResult { - let table = TrashTableSql::read(trash_id, &*self.0)?; - Ok(Trash::from(table)) + fn read_trash(&self, trash_id: Option) -> FlowyResult { + match trash_id { + None => TrashTableSql::read_all(&*self.0), + Some(trash_id) => { + let table = TrashTableSql::read(&trash_id, &*self.0)?; + Ok(RepeatedTrash { + items: vec![Trash::from(table)], + }) + }, + } } - fn delete_trash(&self, trash_ids: Vec) -> FlowyResult<()> { - for trash_id in &trash_ids { - let _ = TrashTableSql::delete_trash(&trash_id, &*self.0)?; + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { + match trash_ids { + None => TrashTableSql::delete_all(&*self.0), + Some(trash_ids) => { + for trash_id in &trash_ids { + let _ = TrashTableSql::delete_trash(&trash_id, &*self.0)?; + } + Ok(()) + }, } - Ok(()) } } // https://www.reddit.com/r/rust/comments/droxdg/why_arent_traits_impld_for_boxdyn_trait/ -impl FlowyCorePersistenceTransaction for Box +impl FolderPersistenceTransaction for Box where - T: FlowyCorePersistenceTransaction + ?Sized, + T: FolderPersistenceTransaction + ?Sized, { fn create_workspace(&self, user_id: &str, workspace: Workspace) -> FlowyResult<()> { (**self).create_workspace(user_id, workspace) @@ -153,11 +161,7 @@ where fn create_trash(&self, trashes: Vec) -> FlowyResult<()> { (**self).create_trash(trashes) } - fn read_all_trash(&self) -> FlowyResult { (**self).read_all_trash() } + fn read_trash(&self, trash_id: Option) -> FlowyResult { (**self).read_trash(trash_id) } - fn delete_all_trash(&self) -> FlowyResult<()> { (**self).delete_all_trash() } - - fn read_trash(&self, trash_id: &str) -> FlowyResult { (**self).read_trash(trash_id) } - - fn delete_trash(&self, trash_ids: Vec) -> FlowyResult<()> { (**self).delete_trash(trash_ids) } + fn delete_trash(&self, trash_ids: Option>) -> FlowyResult<()> { (**self).delete_trash(trash_ids) } } diff --git a/frontend/rust-lib/flowy-core/src/services/persistence/version_2/mod.rs b/frontend/rust-lib/flowy-core/src/services/persistence/version_2/mod.rs index e69de29bb2..ae76b104d5 100644 --- a/frontend/rust-lib/flowy-core/src/services/persistence/version_2/mod.rs +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_2/mod.rs @@ -0,0 +1 @@ +pub mod v2_impl; 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 new file mode 100644 index 0000000000..74aa524e1e --- /dev/null +++ b/frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs @@ -0,0 +1,205 @@ +use crate::services::persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}; +use flowy_collaboration::{entities::revision::Revision, folder::FolderPad}; +use flowy_core_data_model::entities::{ + app::App, + prelude::{RepeatedTrash, Trash, View, Workspace}, +}; +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; + +const FOLDER_ID: &str = "flowy_folder"; + +pub struct FolderEditor { + user_id: String, + folder_pad: Arc>, + rev_manager: Arc, +} + +impl FolderEditor { + pub async fn new(user_id: &str, token: &str, pool: Arc) -> FlowyResult { + let cache = Arc::new(RevisionCache::new(user_id, FOLDER_ID, pool)); + let mut rev_manager = RevisionManager::new(user_id, FOLDER_ID, cache); + let cloud = Arc::new(FolderRevisionCloudServiceImpl { + token: token.to_string(), + }); + let folder_pad = Arc::new(RwLock::new(rev_manager.load::(cloud).await?)); + let rev_manager = Arc::new(rev_manager); + let user_id = user_id.to_owned(); + Ok(Self { + user_id, + folder_pad, + rev_manager, + }) + } + + fn add_local_delta(&self, delta: PlainDelta) -> FlowyResult<()> { + 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, + rev_id, + delta_data, + &self.user_id, + md5, + ); + let _ = futures::executor::block_on(async { self.rev_manager.add_local_revision(&revision).await })?; + Ok(()) + } +} + +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)?; + } + Ok(()) + } + + fn read_workspaces(&self, _user_id: &str, workspace_id: Option) -> FlowyResult> { + let workspaces = self.folder_pad.read().read_workspaces(workspace_id)?; + Ok(workspaces) + } + + fn update_workspace(&self, changeset: WorkspaceChangeset) -> FlowyResult<()> { + if let Some(delta) = self + .folder_pad + .write() + .update_workspace(&changeset.id, changeset.name, changeset.desc)? + { + let _ = self.add_local_delta(delta)?; + } + 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)?; + } + 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)?; + } + Ok(()) + } + + fn update_app(&self, changeset: AppChangeset) -> FlowyResult<()> { + if let Some(delta) = self + .folder_pad + .write() + .update_app(&changeset.id, changeset.name, changeset.desc)? + { + let _ = self.add_local_delta(delta)?; + } + Ok(()) + } + + fn read_app(&self, app_id: &str) -> FlowyResult { + let app = self.folder_pad.read().read_app(app_id)?; + Ok(app) + } + + fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult> { + let workspaces = self.folder_pad.read().read_workspaces(Some(workspace_id.to_owned()))?; + match workspaces.first() { + None => { + Err(FlowyError::record_not_found().context(format!("can't find workspace with id {}", workspace_id))) + }, + Some(workspace) => Ok(workspace.apps.clone().take_items()), + } + } + + 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)?; + } + 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)?; + } + Ok(()) + } + + fn read_view(&self, view_id: &str) -> FlowyResult { + let view = self.folder_pad.read().read_view(view_id)?; + Ok(view) + } + + fn read_views(&self, belong_to_id: &str) -> FlowyResult> { + let views = self.folder_pad.read().read_views(belong_to_id)?; + Ok(views) + } + + fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()> { + if let Some(delta) = self.folder_pad.write().update_view( + &changeset.id, + changeset.name, + changeset.desc, + changeset.modified_time, + )? { + let _ = self.add_local_delta(delta)?; + } + 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)?; + } + 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)?; + } + Ok(()) + } + + fn read_trash(&self, trash_id: Option) -> FlowyResult { + let trash = self.folder_pad.read().read_trash(trash_id)?; + Ok(RepeatedTrash { items: trash }) + } + + 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)?; + } + Ok(()) + } +} + +struct FolderPadBuilder(); +impl RevisionObjectBuilder for FolderPadBuilder { + type Output = FolderPad; + + fn build_with_revisions(_object_id: &str, revisions: Vec) -> FlowyResult { + let pad = FolderPad::from_revisions(revisions)?; + Ok(pad) + } +} + +struct FolderRevisionCloudServiceImpl { + token: String, + // server: Arc, +} + +impl RevisionCloudService for FolderRevisionCloudServiceImpl { + #[tracing::instrument(level = "debug", skip(self))] + fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { + FutureResult::new(async move { Ok(vec![]) }) + } +} 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 d0fe088b95..4dd435a160 100644 --- a/frontend/rust-lib/flowy-core/src/services/trash/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/trash/controller.rs @@ -2,24 +2,24 @@ use crate::{ dart_notification::{send_anonymous_dart_notification, WorkspaceNotification}, entities::trash::{RepeatedTrash, RepeatedTrashId, Trash, TrashId, TrashType}, errors::{FlowyError, FlowyResult}, - module::{WorkspaceCloudService, WorkspaceUser}, - services::persistence::{FlowyCorePersistence, FlowyCorePersistenceTransaction}, + module::{FolderCouldServiceV1, WorkspaceUser}, + services::persistence::{FolderPersistence, FolderPersistenceTransaction}, }; use std::{fmt::Formatter, sync::Arc}; use tokio::sync::{broadcast, mpsc}; pub struct TrashController { - persistence: Arc, + persistence: Arc, notify: broadcast::Sender, - cloud_service: Arc, + cloud_service: Arc, user: Arc, } impl TrashController { pub fn new( - persistence: Arc, - cloud_service: Arc, + persistence: Arc, + cloud_service: Arc, user: Arc, ) -> Self { let (tx, _) = broadcast::channel(10); @@ -37,10 +37,14 @@ impl TrashController { pub async fn putback(&self, trash_id: &str) -> FlowyResult<()> { let (tx, mut rx) = mpsc::channel::>(1); let trash = self.persistence.begin_transaction(|transaction| { - let trash = transaction.read_trash(trash_id); - let _ = transaction.delete_trash(vec![trash_id.to_owned()])?; - notify_trash_changed(transaction.read_all_trash()?); - trash + let mut repeated_trash = transaction.read_trash(Some(trash_id.to_owned()))?; + let _ = transaction.delete_trash(Some(vec![trash_id.to_owned()]))?; + notify_trash_changed(transaction.read_trash(None)?); + + if repeated_trash.is_empty() { + return Err(FlowyError::internal().context("Try to put back trash is not exists")); + } + Ok(repeated_trash.pop().unwrap()) })?; let identifier = TrashId { @@ -62,8 +66,8 @@ impl TrashController { #[tracing::instrument(level = "debug", skip(self) err)] pub async fn restore_all(&self) -> FlowyResult<()> { let repeated_trash = self.persistence.begin_transaction(|transaction| { - let trash = transaction.read_all_trash(); - let _ = transaction.delete_all_trash(); + let trash = transaction.read_trash(None); + let _ = transaction.delete_trash(None); trash })?; @@ -81,7 +85,7 @@ impl TrashController { pub async fn delete_all(&self) -> FlowyResult<()> { let repeated_trash = self .persistence - .begin_transaction(|transaction| transaction.read_all_trash())?; + .begin_transaction(|transaction| transaction.read_trash(None))?; let trash_identifiers: RepeatedTrashId = repeated_trash.items.clone().into(); let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?; @@ -95,7 +99,7 @@ impl TrashController { let _ = self.delete_with_identifiers(trash_identifiers.clone()).await?; let repeated_trash = self .persistence - .begin_transaction(|transaction| transaction.read_all_trash())?; + .begin_transaction(|transaction| transaction.read_trash(None))?; notify_trash_changed(repeated_trash); let _ = self.delete_trash_on_server(trash_identifiers)?; @@ -121,7 +125,7 @@ impl TrashController { .into_iter() .map(|item| item.id) .collect::>(); - transaction.delete_trash(ids) + transaction.delete_trash(Some(ids)) })?; Ok(()) @@ -154,7 +158,7 @@ impl TrashController { let _ = self.persistence.begin_transaction(|transaction| { let _ = transaction.create_trash(repeated_trash.clone())?; let _ = self.create_trash_on_server(repeated_trash); - notify_trash_changed(transaction.read_all_trash()?); + notify_trash_changed(transaction.read_trash(None)?); Ok(()) })?; let _ = self.notify.send(TrashEvent::NewTrash(identifiers.into(), tx)); @@ -168,17 +172,17 @@ impl TrashController { pub fn read_trash(&self) -> Result { let repeated_trash = self .persistence - .begin_transaction(|transaction| transaction.read_all_trash())?; + .begin_transaction(|transaction| transaction.read_trash(None))?; let _ = self.read_trash_on_server()?; Ok(repeated_trash) } pub fn read_trash_ids<'a>( &self, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result, FlowyError> { let ids = transaction - .read_all_trash()? + .read_trash(None)? .into_inner() .into_iter() .map(|item| item.id) @@ -229,7 +233,7 @@ impl TrashController { tracing::debug!("Remote trash count: {}", repeated_trash.items.len()); let result = persistence.begin_transaction(|transaction| { let _ = transaction.create_trash(repeated_trash.items.clone())?; - transaction.read_all_trash() + transaction.read_trash(None) }); match result { 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 8ab5f3820d..46776792a2 100644 --- a/frontend/rust-lib/flowy-core/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/view/controller.rs @@ -15,9 +15,9 @@ use crate::{ view::{CreateViewParams, RepeatedView, UpdateViewParams, View, ViewId}, }, errors::{FlowyError, FlowyResult}, - module::{WorkspaceCloudService, WorkspaceUser}, + module::{FolderCouldServiceV1, WorkspaceUser}, services::{ - persistence::{FlowyCorePersistence, FlowyCorePersistenceTransaction, ViewChangeset}, + persistence::{FolderPersistence, FolderPersistenceTransaction, ViewChangeset}, TrashController, TrashEvent, }, @@ -31,8 +31,8 @@ const LATEST_VIEW_ID: &str = "latest_view_id"; pub(crate) struct ViewController { user: Arc, - cloud_service: Arc, - persistence: Arc, + cloud_service: Arc, + persistence: Arc, trash_controller: Arc, document_ctx: Arc, } @@ -40,8 +40,8 @@ pub(crate) struct ViewController { impl ViewController { pub(crate) fn new( user: Arc, - persistence: Arc, - cloud_service: Arc, + persistence: Arc, + cloud_service: Arc, trash_can: Arc, document_ctx: Arc, ) -> Self { @@ -296,7 +296,7 @@ impl ViewController { #[tracing::instrument(level = "trace", skip(persistence, context, trash_can))] async fn handle_trash_event( - persistence: Arc, + persistence: Arc, context: Arc, trash_can: Arc, event: TrashEvent, @@ -347,7 +347,7 @@ async fn handle_trash_event( fn read_local_views_with_transaction<'a>( identifiers: RepeatedTrashId, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result, FlowyError> { let mut views = vec![]; for identifier in identifiers.items { @@ -365,7 +365,7 @@ fn notify_dart(view: View, notification: WorkspaceNotification) { fn notify_views_changed<'a>( belong_to_id: &str, trash_controller: Arc, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult<()> { let repeated_view = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)?; tracing::Span::current().record("view_count", &format!("{}", repeated_view.len()).as_str()); @@ -378,7 +378,7 @@ fn notify_views_changed<'a>( fn read_belonging_views_on_local<'a>( belong_to_id: &str, trash_controller: Arc, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult { let mut views = transaction.read_views(belong_to_id)?; let trash_ids = trash_controller.read_trash_ids(transaction)?; 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 bfaf723a88..17db8009a3 100644 --- a/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs +++ b/frontend/rust-lib/flowy-core/src/services/workspace/controller.rs @@ -1,9 +1,9 @@ use crate::{ dart_notification::*, errors::*, - module::{WorkspaceCloudService, WorkspaceUser}, + module::{FolderCouldServiceV1, WorkspaceUser}, services::{ - persistence::{FlowyCorePersistence, FlowyCorePersistenceTransaction, WorkspaceChangeset}, + persistence::{FolderPersistence, FolderPersistenceTransaction, WorkspaceChangeset}, read_local_workspace_apps, TrashController, }, @@ -14,17 +14,17 @@ use std::sync::Arc; pub struct WorkspaceController { pub user: Arc, - persistence: Arc, + persistence: Arc, pub(crate) trash_controller: Arc, - cloud_service: Arc, + cloud_service: Arc, } impl WorkspaceController { pub(crate) fn new( user: Arc, - persistence: Arc, + persistence: Arc, trash_can: Arc, - cloud_service: Arc, + cloud_service: Arc, ) -> Self { Self { user, @@ -119,7 +119,7 @@ impl WorkspaceController { &self, workspace_id: Option, user_id: &str, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result { let workspace_id = workspace_id.to_owned(); let workspaces = transaction.read_workspaces(user_id, workspace_id)?; @@ -130,7 +130,7 @@ impl WorkspaceController { &self, workspace_id: String, user_id: &str, - transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a), + transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> Result { let mut workspaces = transaction.read_workspaces(user_id, Some(workspace_id.clone()))?; if workspaces.is_empty() { diff --git a/frontend/rust-lib/flowy-core/src/util.rs b/frontend/rust-lib/flowy-core/src/util.rs index 9cf38af7c2..79ccfe6d48 100644 --- a/frontend/rust-lib/flowy-core/src/util.rs +++ b/frontend/rust-lib/flowy-core/src/util.rs @@ -1,5 +1,5 @@ #![allow(clippy::type_complexity)] -use crate::module::{WorkspaceCloudService, WorkspaceUser}; +use crate::module::{FolderCouldServiceV1, WorkspaceUser}; use lib_infra::retry::Action; use pin_project::pin_project; use std::{ @@ -10,12 +10,12 @@ use std::{ task::{Context, Poll}, }; -pub(crate) type Builder = Box) -> Fut + Send + Sync>; +pub(crate) type Builder = Box) -> Fut + Send + Sync>; #[allow(dead_code)] pub(crate) struct RetryAction { token: String, - cloud_service: Arc, + cloud_service: Arc, user: Arc, builder: Builder, phantom: PhantomData<(T, E)>, @@ -23,14 +23,10 @@ pub(crate) struct RetryAction { impl RetryAction { #[allow(dead_code)] - pub(crate) fn new( - cloud_service: Arc, - user: Arc, - builder: F, - ) -> Self + pub(crate) fn new(cloud_service: Arc, user: Arc, builder: F) -> Self where Fut: Future> + Send + Sync + 'static, - F: Fn(String, Arc) -> Fut + Send + Sync + 'static, + F: Fn(String, Arc) -> Fut + Send + Sync + 'static, { let token = user.token().unwrap_or_else(|_| "".to_owned()); Self { diff --git a/frontend/rust-lib/flowy-document/src/controller.rs b/frontend/rust-lib/flowy-document/src/controller.rs index d0c0905a41..980de70cbc 100644 --- a/frontend/rust-lib/flowy-document/src/controller.rs +++ b/frontend/rust-lib/flowy-document/src/controller.rs @@ -133,7 +133,7 @@ impl DocumentController { let user = self.user.clone(); let token = self.user.token()?; let rev_manager = self.make_rev_manager(doc_id, pool.clone())?; - let server = Arc::new(RevisionServerImpl { + let server = Arc::new(DocumentRevisionCloudServiceImpl { token, server: self.cloud_service.clone(), }); @@ -159,12 +159,12 @@ impl DocumentController { fn remove_ws_receiver(&self, id: &str) { self.ws_receivers.remove(id); } } -struct RevisionServerImpl { +struct DocumentRevisionCloudServiceImpl { token: String, server: Arc, } -impl RevisionCloudService for RevisionServerImpl { +impl RevisionCloudService for DocumentRevisionCloudServiceImpl { #[tracing::instrument(level = "debug", skip(self))] fn fetch_object(&self, user_id: &str, doc_id: &str) -> FutureResult, FlowyError> { let params = DocumentId { diff --git a/frontend/rust-lib/flowy-error/src/ext/collaborate.rs b/frontend/rust-lib/flowy-error/src/ext/collaborate.rs index 82246f7792..c4a6725794 100644 --- a/frontend/rust-lib/flowy-error/src/ext/collaborate.rs +++ b/frontend/rust-lib/flowy-error/src/ext/collaborate.rs @@ -1,5 +1,12 @@ use crate::FlowyError; +use flowy_collaboration::errors::ErrorCode; + impl std::convert::From for FlowyError { - fn from(error: flowy_collaboration::errors::CollaborateError) -> Self { FlowyError::internal().context(error) } + fn from(error: flowy_collaboration::errors::CollaborateError) -> Self { + match error.code { + ErrorCode::RecordNotFound => FlowyError::record_not_found().context(error.msg), + _ => FlowyError::internal().context(error.msg), + } + } } diff --git a/frontend/rust-lib/flowy-error/src/ext/ot.rs b/frontend/rust-lib/flowy-error/src/ext/ot.rs index bfce081953..fbeb683bbb 100644 --- a/frontend/rust-lib/flowy-error/src/ext/ot.rs +++ b/frontend/rust-lib/flowy-error/src/ext/ot.rs @@ -1,5 +1,5 @@ use crate::FlowyError; impl std::convert::From for FlowyError { - fn from(error: lib_ot::errors::OTError) -> Self { FlowyError::internal().context(error) } + fn from(error: lib_ot::errors::OTError) -> Self { FlowyError::internal().context(error.msg) } } diff --git a/frontend/rust-lib/flowy-net/src/http_server/core.rs b/frontend/rust-lib/flowy-net/src/http_server/core.rs index 0e90f479ab..4f69d60f24 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/core.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/core.rs @@ -12,7 +12,7 @@ use flowy_core_data_model::entities::{ }; use flowy_error::FlowyError; -use flowy_core::module::WorkspaceCloudService; +use flowy_core::module::{FolderCouldServiceV1, FolderCouldServiceV2}; use lazy_static::lazy_static; use lib_infra::future::FutureResult; use std::sync::Arc; @@ -26,7 +26,9 @@ impl CoreHttpCloudService { pub fn new(config: ClientServerConfiguration) -> CoreHttpCloudService { Self { config } } } -impl WorkspaceCloudService for CoreHttpCloudService { +impl FolderCouldServiceV2 for CoreHttpCloudService {} + +impl FolderCouldServiceV1 for CoreHttpCloudService { fn init(&self) {} fn create_workspace(&self, token: &str, params: CreateWorkspaceParams) -> FutureResult { diff --git a/frontend/rust-lib/flowy-net/src/local_server/server.rs b/frontend/rust-lib/flowy-net/src/local_server/server.rs index 48b33d7fc5..fba0329a91 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/server.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/server.rs @@ -12,7 +12,7 @@ use flowy_collaboration::{ server_document::ServerDocumentManager, synchronizer::{RevisionSyncResponse, RevisionUser}, }; -use flowy_core::module::WorkspaceCloudService; +use flowy_core::module::{FolderCouldServiceV1, FolderCouldServiceV2}; use flowy_error::{internal_error, FlowyError}; use futures_util::stream::StreamExt; use lib_ws::{WSModule, WebSocketRawMessage}; @@ -211,7 +211,9 @@ use flowy_user_data_model::entities::{ }; use lib_infra::{future::FutureResult, timestamp, uuid_string}; -impl WorkspaceCloudService for LocalServer { +impl FolderCouldServiceV2 for LocalServer {} + +impl FolderCouldServiceV1 for LocalServer { fn init(&self) {} fn create_workspace(&self, _token: &str, params: CreateWorkspaceParams) -> FutureResult { diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/core_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/core_deps.rs index c8cbce22d9..d728e466cb 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/core_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/core_deps.rs @@ -4,7 +4,7 @@ use flowy_collaboration::entities::ws_data::ClientRevisionWSData; use flowy_core::{ controller::FolderManager, errors::{internal_error, FlowyError}, - module::{init_folder, WorkspaceCloudService, WorkspaceDatabase, WorkspaceUser}, + module::{init_folder, FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser}, }; use flowy_database::ConnectionPool; use flowy_document::context::DocumentContext; @@ -30,7 +30,7 @@ impl CoreDepsResolver { let user: Arc = Arc::new(WorkspaceUserImpl(user_session.clone())); let database: Arc = Arc::new(WorkspaceDatabaseImpl(user_session)); let ws_sender = Arc::new(FolderWebSocketImpl(ws_conn.clone())); - let cloud_service: Arc = match local_server { + let cloud_service: Arc = match local_server { None => Arc::new(CoreHttpCloudService::new(server_config.clone())), Some(local_server) => local_server, }; diff --git a/frontend/rust-lib/flowy-sync/src/cache/mod.rs b/frontend/rust-lib/flowy-sync/src/cache/mod.rs index 36e1002bea..6e9a00ed6d 100644 --- a/frontend/rust-lib/flowy-sync/src/cache/mod.rs +++ b/frontend/rust-lib/flowy-sync/src/cache/mod.rs @@ -18,19 +18,19 @@ use std::{ use tokio::task::spawn_blocking; pub struct RevisionCache { - doc_id: String, + object_id: String, disk_cache: Arc>, memory_cache: Arc, latest_rev_id: AtomicI64, } impl RevisionCache { - pub fn new(user_id: &str, doc_id: &str, pool: Arc) -> RevisionCache { + pub fn new(user_id: &str, object_id: &str, pool: Arc) -> RevisionCache { let disk_cache = Arc::new(SQLitePersistence::new(user_id, pool)); - let memory_cache = Arc::new(RevisionMemoryCache::new(doc_id, Arc::new(disk_cache.clone()))); - let doc_id = doc_id.to_owned(); + let memory_cache = Arc::new(RevisionMemoryCache::new(object_id, Arc::new(disk_cache.clone()))); + let object_id = object_id.to_owned(); Self { - doc_id, + object_id, disk_cache, memory_cache, latest_rev_id: AtomicI64::new(0), @@ -63,7 +63,10 @@ impl RevisionCache { pub async fn get(&self, rev_id: i64) -> Option { match self.memory_cache.get(&rev_id).await { - None => match self.disk_cache.read_revision_records(&self.doc_id, Some(vec![rev_id])) { + None => match self + .disk_cache + .read_revision_records(&self.object_id, Some(vec![rev_id])) + { Ok(mut records) => { if !records.is_empty() { assert_eq!(records.len(), 1); @@ -93,7 +96,7 @@ impl RevisionCache { let range_len = range.len() as usize; if records.len() != range_len { let disk_cache = self.disk_cache.clone(); - let doc_id = self.doc_id.clone(); + let doc_id = self.object_id.clone(); records = spawn_blocking(move || disk_cache.read_revision_records_with_range(&doc_id, &range)) .await .map_err(internal_error)??; diff --git a/shared-lib/flowy-collaboration/src/client_document/document_pad.rs b/shared-lib/flowy-collaboration/src/client_document/document_pad.rs index 60f8e25f7a..2ec6d0e937 100644 --- a/shared-lib/flowy-collaboration/src/client_document/document_pad.rs +++ b/shared-lib/flowy-collaboration/src/client_document/document_pad.rs @@ -61,7 +61,6 @@ impl ClientDocument { pub fn delta(&self) -> &RichTextDelta { &self.delta } pub fn md5(&self) -> String { - // TODO: Optimize the cost of calculating the md5 let bytes = self.to_bytes(); format!("{:x}", md5::compute(bytes)) } diff --git a/shared-lib/flowy-collaboration/src/folder/folder_manager.rs b/shared-lib/flowy-collaboration/src/folder/folder_manager.rs deleted file mode 100644 index 17c33e27da..0000000000 --- a/shared-lib/flowy-collaboration/src/folder/folder_manager.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub trait FolderCloudPersistence: Send + Sync { - // fn read_folder(&self) -> BoxResultFuture<> -} diff --git a/shared-lib/flowy-collaboration/src/folder/folder_data.rs b/shared-lib/flowy-collaboration/src/folder/folder_pad.rs similarity index 60% rename from shared-lib/flowy-collaboration/src/folder/folder_data.rs rename to shared-lib/flowy-collaboration/src/folder/folder_pad.rs index 731a615723..047ad1fee6 100644 --- a/shared-lib/flowy-collaboration/src/folder/folder_data.rs +++ b/shared-lib/flowy-collaboration/src/folder/folder_pad.rs @@ -1,7 +1,8 @@ use crate::{ - entities::revision::Revision, + 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}; @@ -9,12 +10,30 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] -pub struct RootFolder { +pub struct FolderPad { workspaces: Vec>, trash: Vec>, + #[serde(skip)] + root: PlainDelta, } -impl RootFolder { +pub fn default_folder_delta() -> PlainDelta { + PlainDeltaBuilder::new() + .insert(r#"{"workspaces":[],"trash":[]}"#) + .build() +} + +impl std::default::Default for FolderPad { + fn default() -> Self { + FolderPad { + workspaces: vec![], + trash: vec![], + root: default_folder_delta(), + } + } +} + +impl FolderPad { pub fn from_revisions(revisions: Vec) -> CollaborateResult { let mut folder_delta = PlainDelta::new(); for revision in revisions { @@ -29,21 +48,26 @@ impl RootFolder { Self::from_delta(folder_delta) } - pub fn from_delta(delta: PlainDelta) -> CollaborateResult { + pub fn from_delta(mut delta: PlainDelta) -> CollaborateResult { + if delta.is_empty() { + delta = default_folder_delta(); + } let folder_json = delta.apply("").unwrap(); - let folder: RootFolder = serde_json::from_str(&folder_json) - .map_err(|e| CollaborateError::internal().context(format!("Deserial json to root folder failed: {}", e)))?; + let mut folder: FolderPad = serde_json::from_str(&folder_json).map_err(|e| { + CollaborateError::internal().context(format!("Deserialize json to root folder failed: {}", e)) + })?; + folder.root = delta; Ok(folder) } - pub fn add_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"); return Ok(None); } - self.modify_workspaces(move |workspaces, _| { + self.modify_workspaces(move |workspaces| { workspaces.push(workspace); Ok(Some(())) }) @@ -55,7 +79,7 @@ impl RootFolder { name: Option, desc: Option, ) -> CollaborateResult> { - self.modify_workspace(workspace_id, |workspace, _| { + self.modify_workspace(workspace_id, |workspace| { if let Some(name) = name { workspace.name = name; } @@ -67,16 +91,37 @@ impl RootFolder { }) } + pub fn read_workspaces(&self, workspace_id: Option) -> CollaborateResult> { + match workspace_id { + None => { + let workspaces = self + .workspaces + .iter() + .map(|workspace| workspace.as_ref().clone()) + .collect::>(); + Ok(workspaces) + }, + Some(workspace_id) => { + if let Some(workspace) = self.workspaces.iter().find(|workspace| workspace.id == workspace_id) { + Ok(vec![workspace.as_ref().clone()]) + } else { + Err(CollaborateError::record_not_found() + .context(format!("Can't find workspace with id {}", workspace_id))) + } + }, + } + } + pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult> { - self.modify_workspaces(|workspaces, _| { + self.modify_workspaces(|workspaces| { workspaces.retain(|w| w.id != workspace_id); Ok(Some(())) }) } - pub fn add_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.modify_workspace(&workspace_id, move |workspace| { if workspace.apps.contains(&app) { tracing::warn!("[RootFolder]: Duplicate app"); return Ok(None); @@ -86,13 +131,22 @@ impl RootFolder { }) } + pub fn read_app(&self, app_id: &str) -> CollaborateResult { + for workspace in &self.workspaces { + if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) { + return Ok(app.clone()); + } + } + Err(CollaborateError::record_not_found().context(format!("Can't find app with id {}", app_id))) + } + pub fn update_app( &mut self, app_id: &str, name: Option, desc: Option, ) -> CollaborateResult> { - self.modify_app(app_id, move |app, _| { + self.modify_app(app_id, move |app| { if let Some(name) = name { app.name = name; } @@ -104,22 +158,17 @@ impl RootFolder { }) } - pub fn delete_app(&mut self, workspace_id: &str, app_id: &str) -> CollaborateResult> { - self.modify_workspace(workspace_id, |workspace, trash| { - for app in workspace.apps.take_items() { - if app.id == app_id { - trash.push(Arc::new(Trash::from(app))) - } else { - workspace.apps.push(app); - } - } + pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult> { + let app = self.read_app(app_id)?; + self.modify_workspace(&app.workspace_id, |workspace| { + workspace.apps.retain(|app| app.id != app_id); Ok(Some(())) }) } - pub fn add_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.modify_app(&app_id, move |app| { if app.belongings.contains(&view) { tracing::warn!("[RootFolder]: Duplicate view"); return Ok(None); @@ -129,15 +178,38 @@ impl RootFolder { }) } + pub fn read_view(&self, view_id: &str) -> CollaborateResult { + for workspace in &self.workspaces { + for app in &(*workspace.apps) { + if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) { + return Ok(view.clone()); + } + } + } + Err(CollaborateError::record_not_found().context(format!("Can't find view with id {}", view_id))) + } + + pub fn read_views(&self, belong_to_id: &str) -> CollaborateResult> { + for workspace in &self.workspaces { + for app in &(*workspace.apps) { + if app.id == belong_to_id { + return Ok(app.clone().belongings.take_items()); + } + } + } + Err(CollaborateError::record_not_found() + .context(format!("Can't find any views with belong_to_id {}", belong_to_id))) + } + pub fn update_view( &mut self, - belong_to_id: &str, view_id: &str, name: Option, desc: Option, modified_time: i64, ) -> CollaborateResult> { - self.modify_view(belong_to_id, view_id, |view, _| { + let view = self.read_view(view_id)?; + self.modify_view(&view.belong_to_id, view_id, |view| { if let Some(name) = name { view.name = name; } @@ -151,57 +223,74 @@ impl RootFolder { }) } - pub fn delete_view(&mut self, belong_to_id: &str, view_id: &str) -> CollaborateResult> { - self.modify_app(belong_to_id, |app, trash| { - for view in app.belongings.take_items() { - if view.id == view_id { - trash.push(Arc::new(Trash::from(view))) - } else { - app.belongings.push(view); - } - } + 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| { + app.belongings.retain(|view| view.id != view_id); Ok(Some(())) }) } - pub fn putback_trash(&mut self, trash_id: &str) -> CollaborateResult> { - self.modify_trash(|trash| { - trash.retain(|t| t.id != trash_id); + pub fn create_trash(&mut self, trash: Vec) -> CollaborateResult> { + self.modify_trash(|t| { + let mut new_trash = trash.into_iter().map(Arc::new).collect::>>(); + t.append(&mut new_trash); + Ok(Some(())) }) } - pub fn delete_trash(&mut self, trash_id: &str) -> CollaborateResult> { - self.modify_trash(|trash| { - trash.retain(|t| t.id != trash_id); - Ok(Some(())) - }) + pub fn read_trash(&self, trash_id: Option) -> CollaborateResult> { + match trash_id { + None => Ok(self.trash.iter().map(|t| t.as_ref().clone()).collect::>()), + Some(trash_id) => match self.trash.iter().find(|t| t.id == trash_id) { + Some(trash) => Ok(vec![trash.as_ref().clone()]), + None => Ok(vec![]), + }, + } } + + pub fn delete_trash(&mut self, trash_ids: Option>) -> CollaborateResult> { + match trash_ids { + None => self.modify_trash(|trash| { + trash.clear(); + Ok(Some(())) + }), + Some(trash_ids) => self.modify_trash(|trash| { + trash.retain(|t| !trash_ids.contains(&t.id)); + Ok(Some(())) + }), + } + } + + pub fn md5(&self) -> String { md5(&self.root.to_bytes()) } } -impl RootFolder { +impl FolderPad { fn modify_workspaces(&mut self, f: F) -> CollaborateResult> where - F: FnOnce(&mut Vec>, &mut Vec>) -> CollaborateResult>, + F: FnOnce(&mut Vec>) -> CollaborateResult>, { let cloned_self = self.clone(); - match f(&mut self.workspaces, &mut self.trash)? { + match f(&mut self.workspaces)? { None => Ok(None), Some(_) => { let old = cloned_self.to_json()?; let new = self.to_json()?; - Ok(Some(cal_diff(old, new))) + let delta = cal_diff(old, new); + self.root = self.root.compose(&delta)?; + Ok(Some(delta)) }, } } fn modify_workspace(&mut self, workspace_id: &str, f: F) -> CollaborateResult> where - F: FnOnce(&mut Workspace, &mut Vec>) -> CollaborateResult>, + F: FnOnce(&mut Workspace) -> CollaborateResult>, { - self.modify_workspaces(|workspaces, trash| { + self.modify_workspaces(|workspaces| { if let Some(workspace) = workspaces.iter_mut().find(|workspace| workspace_id == workspace.id) { - f(Arc::make_mut(workspace), trash) + f(Arc::make_mut(workspace)) } else { tracing::warn!("[RootFolder]: Can't find any workspace with id: {}", workspace_id); Ok(None) @@ -219,14 +308,16 @@ impl RootFolder { Some(_) => { let old = cloned_self.to_json()?; let new = self.to_json()?; - Ok(Some(cal_diff(old, new))) + let delta = cal_diff(old, new); + self.root = self.root.compose(&delta)?; + Ok(Some(delta)) }, } } fn modify_app(&mut self, app_id: &str, f: F) -> CollaborateResult> where - F: FnOnce(&mut App, &mut Vec>) -> CollaborateResult>, + F: FnOnce(&mut App) -> CollaborateResult>, { let workspace_id = match self .workspaces @@ -240,22 +331,22 @@ impl RootFolder { Some(workspace) => workspace.id.clone(), }; - self.modify_workspace(&workspace_id, |workspace, trash| { - f(workspace.apps.iter_mut().find(|app| app_id == app.id).unwrap(), trash) + self.modify_workspace(&workspace_id, |workspace| { + 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> where - F: FnOnce(&mut View, &mut Vec>) -> CollaborateResult>, + F: FnOnce(&mut View) -> CollaborateResult>, { - self.modify_app(belong_to_id, |app, trash| { + self.modify_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); Ok(None) }, - Some(view) => f(view, trash), + Some(view) => f(view), } }) } @@ -288,7 +379,7 @@ fn cal_diff(old: String, new: String) -> Delta { #[cfg(test)] mod tests { #![allow(clippy::all)] - use crate::folder::folder_data::RootFolder; + use crate::folder::folder_pad::FolderPad; use chrono::Utc; use flowy_core_data_model::entities::{app::App, view::View, workspace::Workspace}; use lib_ot::core::{OperationTransformable, PlainDelta, PlainDeltaBuilder}; @@ -300,11 +391,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.add_workspace(workspace_1).unwrap().unwrap(); + let delta_1 = folder.create_workspace(workspace_1).unwrap().unwrap(); let mut workspace_2 = Workspace::default(); workspace_2.name = "My second workspace".to_owned(); - let delta_2 = folder.add_workspace(workspace_2).unwrap().unwrap(); + let delta_2 = folder.create_workspace(workspace_2).unwrap().unwrap(); let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta_1, delta_2]); assert_eq!(folder, folder_from_delta); @@ -344,8 +435,7 @@ mod tests { #[test] fn folder_delete_app() { let (mut folder, initial_delta, app) = test_app_folder(); - let delta = folder.delete_app(&app.workspace_id, &app.id).unwrap().unwrap(); - assert_eq!(folder.trash.len(), 1); + 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); @@ -362,7 +452,7 @@ mod tests { fn folder_update_view() { let (mut folder, initial_delta, view) = test_view_folder(); let delta = folder - .update_view(&view.belong_to_id, &view.id, Some("😁😁😁".to_owned()), None, 123) + .update_view(&view.id, Some("😁😁😁".to_owned()), None, 123) .unwrap() .unwrap(); @@ -373,18 +463,14 @@ mod tests { #[test] fn folder_delete_view() { let (mut folder, initial_delta, view) = test_view_folder(); - let delta = folder.delete_view(&view.belong_to_id, &view.id).unwrap().unwrap(); + let delta = folder.delete_view(&view.id).unwrap().unwrap(); - assert_eq!(folder.trash.len(), 1); let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]); assert_eq!(folder, folder_from_delta); } - fn test_folder() -> (RootFolder, PlainDelta, Workspace) { - let mut folder = RootFolder { - workspaces: vec![], - trash: vec![], - }; + fn test_folder() -> (FolderPad, PlainDelta, Workspace) { + let mut folder = FolderPad::default(); let folder_json = serde_json::to_string(&folder).unwrap(); let mut delta = PlainDeltaBuilder::new().insert(&folder_json).build(); @@ -393,42 +479,42 @@ mod tests { workspace.id = "1".to_owned(); delta = delta - .compose(&folder.add_workspace(workspace.clone()).unwrap().unwrap()) + .compose(&folder.create_workspace(workspace.clone()).unwrap().unwrap()) .unwrap(); (folder, delta, workspace) } - fn test_app_folder() -> (RootFolder, PlainDelta, App) { + fn test_app_folder() -> (FolderPad, PlainDelta, App) { 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(); initial_delta = initial_delta - .compose(&folder.add_app(app.clone()).unwrap().unwrap()) + .compose(&folder.create_app(app.clone()).unwrap().unwrap()) .unwrap(); (folder, initial_delta, app) } - fn test_view_folder() -> (RootFolder, PlainDelta, View) { + fn test_view_folder() -> (FolderPad, PlainDelta, View) { 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(); initial_delta = initial_delta - .compose(&folder.add_view(view.clone()).unwrap().unwrap()) + .compose(&folder.create_view(view.clone()).unwrap().unwrap()) .unwrap(); (folder, initial_delta, view) } - fn make_folder_from_delta(mut initial_delta: PlainDelta, deltas: Vec) -> RootFolder { + fn make_folder_from_delta(mut initial_delta: PlainDelta, deltas: Vec) -> FolderPad { for delta in deltas { initial_delta = initial_delta.compose(&delta).unwrap(); } - RootFolder::from_delta(initial_delta).unwrap() + FolderPad::from_delta(initial_delta).unwrap() } } diff --git a/shared-lib/flowy-collaboration/src/folder/mod.rs b/shared-lib/flowy-collaboration/src/folder/mod.rs index 1b0b578491..c669aa6bbb 100644 --- a/shared-lib/flowy-collaboration/src/folder/mod.rs +++ b/shared-lib/flowy-collaboration/src/folder/mod.rs @@ -1,2 +1,3 @@ -mod folder_data; -mod folder_manager; +mod folder_pad; + +pub use folder_pad::*;