فهرست منبع

add folder editor

appflowy 3 سال پیش
والد
کامیت
7f1a0adf13
22فایلهای تغییر یافته به همراه548 افزوده شده و 211 حذف شده
  1. 10 18
      frontend/rust-lib/flowy-core/src/controller.rs
  2. 6 5
      frontend/rust-lib/flowy-core/src/module.rs
  3. 9 9
      frontend/rust-lib/flowy-core/src/services/app/controller.rs
  4. 47 10
      frontend/rust-lib/flowy-core/src/services/persistence/mod.rs
  5. 25 21
      frontend/rust-lib/flowy-core/src/services/persistence/version_1/v1_impl.rs
  6. 1 0
      frontend/rust-lib/flowy-core/src/services/persistence/version_2/mod.rs
  7. 205 0
      frontend/rust-lib/flowy-core/src/services/persistence/version_2/v2_impl.rs
  8. 24 20
      frontend/rust-lib/flowy-core/src/services/trash/controller.rs
  9. 10 10
      frontend/rust-lib/flowy-core/src/services/view/controller.rs
  10. 8 8
      frontend/rust-lib/flowy-core/src/services/workspace/controller.rs
  11. 5 9
      frontend/rust-lib/flowy-core/src/util.rs
  12. 3 3
      frontend/rust-lib/flowy-document/src/controller.rs
  13. 8 1
      frontend/rust-lib/flowy-error/src/ext/collaborate.rs
  14. 1 1
      frontend/rust-lib/flowy-error/src/ext/ot.rs
  15. 4 2
      frontend/rust-lib/flowy-net/src/http_server/core.rs
  16. 4 2
      frontend/rust-lib/flowy-net/src/local_server/server.rs
  17. 2 2
      frontend/rust-lib/flowy-sdk/src/deps_resolve/core_deps.rs
  18. 10 7
      frontend/rust-lib/flowy-sync/src/cache/mod.rs
  19. 0 1
      shared-lib/flowy-collaboration/src/client_document/document_pad.rs
  20. 0 3
      shared-lib/flowy-collaboration/src/folder/folder_manager.rs
  21. 163 77
      shared-lib/flowy-collaboration/src/folder/folder_pad.rs
  22. 3 2
      shared-lib/flowy-collaboration/src/folder/mod.rs

+ 10 - 18
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<dyn WorkspaceUser>,
-    pub(crate) cloud_service: Arc<dyn WorkspaceCloudService>,
-    pub(crate) persistence: Arc<FlowyCorePersistence>,
+    pub(crate) cloud_service: Arc<dyn FolderCouldServiceV1>,
+    pub(crate) persistence: Arc<FolderPersistence>,
     pub workspace_controller: Arc<WorkspaceController>,
     pub(crate) app_controller: Arc<AppController>,
     pub(crate) view_controller: Arc<ViewController>,
@@ -40,8 +35,8 @@ pub struct FolderManager {
 impl FolderManager {
     pub(crate) fn new(
         user: Arc<dyn WorkspaceUser>,
-        cloud_service: Arc<dyn WorkspaceCloudService>,
-        persistence: Arc<FlowyCorePersistence>,
+        cloud_service: Arc<dyn FolderCouldServiceV1>,
+        persistence: Arc<FolderPersistence>,
         flowy_document: Arc<DocumentContext>,
         ws_sender: Arc<dyn RevisionWebSocket>,
     ) -> 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(())
     }

+ 6 - 5
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<dyn WorkspaceUser>,
     database: Arc<dyn WorkspaceDatabase>,
     flowy_document: Arc<DocumentContext>,
-    cloud_service: Arc<dyn WorkspaceCloudService>,
+    cloud_service: Arc<dyn FolderCouldServiceV1>,
     ws_sender: Arc<dyn RevisionWebSocket>,
 ) -> Arc<FolderManager> {
-    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<FolderManager>) -> 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<RepeatedTrash, FlowyError>;
 }
+
+pub trait FolderCouldServiceV2: Send + Sync {}

+ 9 - 9
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<dyn WorkspaceUser>,
-    persistence: Arc<FlowyCorePersistence>,
+    persistence: Arc<FolderPersistence>,
     trash_controller: Arc<TrashController>,
-    cloud_service: Arc<dyn WorkspaceCloudService>,
+    cloud_service: Arc<dyn FolderCouldServiceV1>,
 }
 
 impl AppController {
     pub(crate) fn new(
         user: Arc<dyn WorkspaceUser>,
-        persistence: Arc<FlowyCorePersistence>,
+        persistence: Arc<FolderPersistence>,
         trash_can: Arc<TrashController>,
-        cloud_service: Arc<dyn WorkspaceCloudService>,
+        cloud_service: Arc<dyn FolderCouldServiceV1>,
     ) -> Self {
         Self {
             user,
@@ -169,7 +169,7 @@ impl AppController {
 
 #[tracing::instrument(level = "trace", skip(persistence, trash_controller))]
 async fn handle_trash_event(
-    persistence: Arc<FlowyCorePersistence>,
+    persistence: Arc<FolderPersistence>,
     trash_controller: Arc<TrashController>,
     event: TrashEvent,
 ) {
@@ -207,7 +207,7 @@ async fn handle_trash_event(
 fn notify_apps_changed<'a>(
     workspace_id: &str,
     trash_controller: Arc<TrashController>,
-    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<TrashController>,
-    transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
+    transaction: &'a (dyn FolderPersistenceTransaction + 'a),
 ) -> Result<RepeatedApp, FlowyError> {
     let mut apps = transaction.read_workspace_apps(workspace_id)?;
     let trash_ids = trash_controller.read_trash_ids(transaction)?;

+ 47 - 10
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<String>) -> FlowyResult<Vec<Workspace>>;
     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<Trash>) -> FlowyResult<()>;
-    fn read_all_trash(&self) -> FlowyResult<RepeatedTrash>;
-    fn delete_all_trash(&self) -> FlowyResult<()>;
-    fn read_trash(&self, trash_id: &str) -> FlowyResult<Trash>;
-    fn delete_trash(&self, trash_ids: Vec<String>) -> FlowyResult<()>;
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash>;
+    fn delete_trash(&self, trash_ids: Option<Vec<String>>) -> FlowyResult<()>;
 }
 
-pub struct FlowyCorePersistence {
+pub struct FolderPersistence {
+    user: Arc<dyn WorkspaceUser>,
     database: Arc<dyn WorkspaceDatabase>,
+    folder_editor: RwLock<Option<Arc<FolderEditor>>>,
 }
 
-impl FlowyCorePersistence {
-    pub fn new(database: Arc<dyn WorkspaceDatabase>) -> Self { Self { database } }
+impl FolderPersistence {
+    pub fn new(user: Arc<dyn WorkspaceUser>, database: Arc<dyn WorkspaceDatabase>) -> Self {
+        let folder_editor = RwLock::new(None);
+        Self {
+            user,
+            database,
+            folder_editor,
+        }
+    }
 
     pub fn begin_transaction<F, O>(&self, f: F) -> FlowyResult<O>
     where
-        F: for<'a> FnOnce(Box<dyn FlowyCorePersistenceTransaction + 'a>) -> FlowyResult<O>,
+        F: for<'a> FnOnce(Box<dyn FolderPersistenceTransaction + 'a>) -> FlowyResult<O>,
     {
         //[[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<F, O>(&self, f: F) -> FlowyResult<O>
+    where
+        F: FnOnce(Arc<dyn FolderPersistenceTransaction>) -> FlowyResult<O>,
+    {
+        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(())
+    }
 }

+ 25 - 21
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<RepeatedTrash> { TrashTableSql::read_all(&*self.0) }
-
-    fn delete_all_trash(&self) -> FlowyResult<()> { TrashTableSql::delete_all(&*self.0) }
-
-    fn read_trash(&self, trash_id: &str) -> FlowyResult<Trash> {
-        let table = TrashTableSql::read(trash_id, &*self.0)?;
-        Ok(Trash::from(table))
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash> {
+        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<String>) -> FlowyResult<()> {
-        for trash_id in &trash_ids {
-            let _ = TrashTableSql::delete_trash(&trash_id, &*self.0)?;
+    fn delete_trash(&self, trash_ids: Option<Vec<String>>) -> 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<T> FlowyCorePersistenceTransaction for Box<T>
+impl<T> FolderPersistenceTransaction for Box<T>
 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<Trash>) -> FlowyResult<()> { (**self).create_trash(trashes) }
 
-    fn read_all_trash(&self) -> FlowyResult<RepeatedTrash> { (**self).read_all_trash() }
-
-    fn delete_all_trash(&self) -> FlowyResult<()> { (**self).delete_all_trash() }
-
-    fn read_trash(&self, trash_id: &str) -> FlowyResult<Trash> { (**self).read_trash(trash_id) }
+    fn read_trash(&self, trash_id: Option<String>) -> FlowyResult<RepeatedTrash> { (**self).read_trash(trash_id) }
 
-    fn delete_trash(&self, trash_ids: Vec<String>) -> FlowyResult<()> { (**self).delete_trash(trash_ids) }
+    fn delete_trash(&self, trash_ids: Option<Vec<String>>) -> FlowyResult<()> { (**self).delete_trash(trash_ids) }
 }

+ 1 - 0
frontend/rust-lib/flowy-core/src/services/persistence/version_2/mod.rs

@@ -0,0 +1 @@
+pub mod v2_impl;

+ 205 - 0
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<RwLock<FolderPad>>,
+    rev_manager: Arc<RevisionManager>,
+}
+
+impl FolderEditor {
+    pub async fn new(user_id: &str, token: &str, pool: Arc<ConnectionPool>) -> FlowyResult<Self> {
+        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::<FolderPadBuilder>(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<String>) -> FlowyResult<Vec<Workspace>> {
+        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<App> {
+        let app = self.folder_pad.read().read_app(app_id)?;
+        Ok(app)
+    }
+
+    fn read_workspace_apps(&self, workspace_id: &str) -> FlowyResult<Vec<App>> {
+        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<App> {
+        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<View> {
+        let view = self.folder_pad.read().read_view(view_id)?;
+        Ok(view)
+    }
+
+    fn read_views(&self, belong_to_id: &str) -> FlowyResult<Vec<View>> {
+        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<Trash>) -> 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<String>) -> FlowyResult<RepeatedTrash> {
+        let trash = self.folder_pad.read().read_trash(trash_id)?;
+        Ok(RepeatedTrash { items: trash })
+    }
+
+    fn delete_trash(&self, trash_ids: Option<Vec<String>>) -> 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<Revision>) -> FlowyResult<Self::Output> {
+        let pad = FolderPad::from_revisions(revisions)?;
+        Ok(pad)
+    }
+}
+
+struct FolderRevisionCloudServiceImpl {
+    token: String,
+    // server: Arc<dyn FolderCouldServiceV2>,
+}
+
+impl RevisionCloudService for FolderRevisionCloudServiceImpl {
+    #[tracing::instrument(level = "debug", skip(self))]
+    fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult<Vec<Revision>, FlowyError> {
+        FutureResult::new(async move { Ok(vec![]) })
+    }
+}

+ 24 - 20
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<FlowyCorePersistence>,
+    persistence: Arc<FolderPersistence>,
     notify: broadcast::Sender<TrashEvent>,
-    cloud_service: Arc<dyn WorkspaceCloudService>,
+    cloud_service: Arc<dyn FolderCouldServiceV1>,
     user: Arc<dyn WorkspaceUser>,
 }
 
 impl TrashController {
     pub fn new(
-        persistence: Arc<FlowyCorePersistence>,
-        cloud_service: Arc<dyn WorkspaceCloudService>,
+        persistence: Arc<FolderPersistence>,
+        cloud_service: Arc<dyn FolderCouldServiceV1>,
         user: Arc<dyn WorkspaceUser>,
     ) -> 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::<FlowyResult<()>>(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::<Vec<_>>();
-            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<RepeatedTrash, FlowyError> {
         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<Vec<String>, 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 {

+ 10 - 10
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<dyn WorkspaceUser>,
-    cloud_service: Arc<dyn WorkspaceCloudService>,
-    persistence: Arc<FlowyCorePersistence>,
+    cloud_service: Arc<dyn FolderCouldServiceV1>,
+    persistence: Arc<FolderPersistence>,
     trash_controller: Arc<TrashController>,
     document_ctx: Arc<DocumentContext>,
 }
@@ -40,8 +40,8 @@ pub(crate) struct ViewController {
 impl ViewController {
     pub(crate) fn new(
         user: Arc<dyn WorkspaceUser>,
-        persistence: Arc<FlowyCorePersistence>,
-        cloud_service: Arc<dyn WorkspaceCloudService>,
+        persistence: Arc<FolderPersistence>,
+        cloud_service: Arc<dyn FolderCouldServiceV1>,
         trash_can: Arc<TrashController>,
         document_ctx: Arc<DocumentContext>,
     ) -> Self {
@@ -296,7 +296,7 @@ impl ViewController {
 
 #[tracing::instrument(level = "trace", skip(persistence, context, trash_can))]
 async fn handle_trash_event(
-    persistence: Arc<FlowyCorePersistence>,
+    persistence: Arc<FolderPersistence>,
     context: Arc<DocumentContext>,
     trash_can: Arc<TrashController>,
     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<Vec<View>, 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<TrashController>,
-    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<TrashController>,
-    transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
+    transaction: &'a (dyn FolderPersistenceTransaction + 'a),
 ) -> FlowyResult<RepeatedView> {
     let mut views = transaction.read_views(belong_to_id)?;
     let trash_ids = trash_controller.read_trash_ids(transaction)?;

+ 8 - 8
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<dyn WorkspaceUser>,
-    persistence: Arc<FlowyCorePersistence>,
+    persistence: Arc<FolderPersistence>,
     pub(crate) trash_controller: Arc<TrashController>,
-    cloud_service: Arc<dyn WorkspaceCloudService>,
+    cloud_service: Arc<dyn FolderCouldServiceV1>,
 }
 
 impl WorkspaceController {
     pub(crate) fn new(
         user: Arc<dyn WorkspaceUser>,
-        persistence: Arc<FlowyCorePersistence>,
+        persistence: Arc<FolderPersistence>,
         trash_can: Arc<TrashController>,
-        cloud_service: Arc<dyn WorkspaceCloudService>,
+        cloud_service: Arc<dyn FolderCouldServiceV1>,
     ) -> Self {
         Self {
             user,
@@ -119,7 +119,7 @@ impl WorkspaceController {
         &self,
         workspace_id: Option<String>,
         user_id: &str,
-        transaction: &'a (dyn FlowyCorePersistenceTransaction + 'a),
+        transaction: &'a (dyn FolderPersistenceTransaction + 'a),
     ) -> Result<RepeatedWorkspace, FlowyError> {
         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<Workspace, FlowyError> {
         let mut workspaces = transaction.read_workspaces(user_id, Some(workspace_id.clone()))?;
         if workspaces.is_empty() {

+ 5 - 9
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<Fut> = Box<dyn Fn(String, Arc<dyn WorkspaceCloudService>) -> Fut + Send + Sync>;
+pub(crate) type Builder<Fut> = Box<dyn Fn(String, Arc<dyn FolderCouldServiceV1>) -> Fut + Send + Sync>;
 
 #[allow(dead_code)]
 pub(crate) struct RetryAction<Fut, T, E> {
     token: String,
-    cloud_service: Arc<dyn WorkspaceCloudService>,
+    cloud_service: Arc<dyn FolderCouldServiceV1>,
     user: Arc<dyn WorkspaceUser>,
     builder: Builder<Fut>,
     phantom: PhantomData<(T, E)>,
@@ -23,14 +23,10 @@ pub(crate) struct RetryAction<Fut, T, E> {
 
 impl<Fut, T, E> RetryAction<Fut, T, E> {
     #[allow(dead_code)]
-    pub(crate) fn new<F>(
-        cloud_service: Arc<dyn WorkspaceCloudService>,
-        user: Arc<dyn WorkspaceUser>,
-        builder: F,
-    ) -> Self
+    pub(crate) fn new<F>(cloud_service: Arc<dyn FolderCouldServiceV1>, user: Arc<dyn WorkspaceUser>, builder: F) -> Self
     where
         Fut: Future<Output = Result<T, E>> + Send + Sync + 'static,
-        F: Fn(String, Arc<dyn WorkspaceCloudService>) -> Fut + Send + Sync + 'static,
+        F: Fn(String, Arc<dyn FolderCouldServiceV1>) -> Fut + Send + Sync + 'static,
     {
         let token = user.token().unwrap_or_else(|_| "".to_owned());
         Self {

+ 3 - 3
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<dyn DocumentCloudService>,
 }
 
-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<Vec<Revision>, FlowyError> {
         let params = DocumentId {

+ 8 - 1
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<flowy_collaboration::errors::CollaborateError> 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),
+        }
+    }
 }

+ 1 - 1
frontend/rust-lib/flowy-error/src/ext/ot.rs

@@ -1,5 +1,5 @@
 use crate::FlowyError;
 
 impl std::convert::From<lib_ot::errors::OTError> 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) }
 }

+ 4 - 2
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<Workspace, FlowyError> {

+ 4 - 2
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<Workspace, FlowyError> {

+ 2 - 2
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<dyn WorkspaceUser> = Arc::new(WorkspaceUserImpl(user_session.clone()));
         let database: Arc<dyn WorkspaceDatabase> = Arc::new(WorkspaceDatabaseImpl(user_session));
         let ws_sender = Arc::new(FolderWebSocketImpl(ws_conn.clone()));
-        let cloud_service: Arc<dyn WorkspaceCloudService> = match local_server {
+        let cloud_service: Arc<dyn FolderCouldServiceV1> = match local_server {
             None => Arc::new(CoreHttpCloudService::new(server_config.clone())),
             Some(local_server) => local_server,
         };

+ 10 - 7
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<dyn RevisionDiskCache<Error = FlowyError>>,
     memory_cache: Arc<RevisionMemoryCache>,
     latest_rev_id: AtomicI64,
 }
 
 impl RevisionCache {
-    pub fn new(user_id: &str, doc_id: &str, pool: Arc<ConnectionPool>) -> RevisionCache {
+    pub fn new(user_id: &str, object_id: &str, pool: Arc<ConnectionPool>) -> 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<RevisionRecord> {
         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)??;

+ 0 - 1
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))
     }

+ 0 - 3
shared-lib/flowy-collaboration/src/folder/folder_manager.rs

@@ -1,3 +0,0 @@
-pub trait FolderCloudPersistence: Send + Sync {
-    // fn read_folder(&self) -> BoxResultFuture<>
-}

+ 163 - 77
shared-lib/flowy-collaboration/src/folder/folder_data.rs → 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<Arc<Workspace>>,
     trash: Vec<Arc<Trash>>,
+    #[serde(skip)]
+    root: PlainDelta,
+}
+
+pub fn default_folder_delta() -> PlainDelta {
+    PlainDeltaBuilder::new()
+        .insert(r#"{"workspaces":[],"trash":[]}"#)
+        .build()
 }
 
-impl RootFolder {
+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<Revision>) -> CollaborateResult<Self> {
         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<Self> {
+    pub fn from_delta(mut delta: PlainDelta) -> CollaborateResult<Self> {
+        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<Option<PlainDelta>> {
+    pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<PlainDelta>> {
         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<String>,
         desc: Option<String>,
     ) -> CollaborateResult<Option<PlainDelta>> {
-        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<String>) -> CollaborateResult<Vec<Workspace>> {
+        match workspace_id {
+            None => {
+                let workspaces = self
+                    .workspaces
+                    .iter()
+                    .map(|workspace| workspace.as_ref().clone())
+                    .collect::<Vec<Workspace>>();
+                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<Option<PlainDelta>> {
-        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<Option<PlainDelta>> {
+    pub fn create_app(&mut self, app: App) -> CollaborateResult<Option<PlainDelta>> {
         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<App> {
+        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<String>,
         desc: Option<String>,
     ) -> CollaborateResult<Option<PlainDelta>> {
-        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<Option<PlainDelta>> {
-        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<Option<PlainDelta>> {
+        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<Option<PlainDelta>> {
+    pub fn create_view(&mut self, view: View) -> CollaborateResult<Option<PlainDelta>> {
         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<View> {
+        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<Vec<View>> {
+        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<String>,
         desc: Option<String>,
         modified_time: i64,
     ) -> CollaborateResult<Option<PlainDelta>> {
-        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<Option<PlainDelta>> {
-        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<Option<PlainDelta>> {
+        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<Option<PlainDelta>> {
-        self.modify_trash(|trash| {
-            trash.retain(|t| t.id != trash_id);
+    pub fn create_trash(&mut self, trash: Vec<Trash>) -> CollaborateResult<Option<PlainDelta>> {
+        self.modify_trash(|t| {
+            let mut new_trash = trash.into_iter().map(Arc::new).collect::<Vec<Arc<Trash>>>();
+            t.append(&mut new_trash);
+
             Ok(Some(()))
         })
     }
 
-    pub fn delete_trash(&mut self, trash_id: &str) -> CollaborateResult<Option<PlainDelta>> {
-        self.modify_trash(|trash| {
-            trash.retain(|t| t.id != trash_id);
-            Ok(Some(()))
-        })
+    pub fn read_trash(&self, trash_id: Option<String>) -> CollaborateResult<Vec<Trash>> {
+        match trash_id {
+            None => Ok(self.trash.iter().map(|t| t.as_ref().clone()).collect::<Vec<Trash>>()),
+            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<Vec<String>>) -> CollaborateResult<Option<PlainDelta>> {
+        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<F>(&mut self, f: F) -> CollaborateResult<Option<PlainDelta>>
     where
-        F: FnOnce(&mut Vec<Arc<Workspace>>, &mut Vec<Arc<Trash>>) -> CollaborateResult<Option<()>>,
+        F: FnOnce(&mut Vec<Arc<Workspace>>) -> CollaborateResult<Option<()>>,
     {
         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<F>(&mut self, workspace_id: &str, f: F) -> CollaborateResult<Option<PlainDelta>>
     where
-        F: FnOnce(&mut Workspace, &mut Vec<Arc<Trash>>) -> CollaborateResult<Option<()>>,
+        F: FnOnce(&mut Workspace) -> CollaborateResult<Option<()>>,
     {
-        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<F>(&mut self, app_id: &str, f: F) -> CollaborateResult<Option<PlainDelta>>
     where
-        F: FnOnce(&mut App, &mut Vec<Arc<Trash>>) -> CollaborateResult<Option<()>>,
+        F: FnOnce(&mut App) -> CollaborateResult<Option<()>>,
     {
         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<F>(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult<Option<PlainDelta>>
     where
-        F: FnOnce(&mut View, &mut Vec<Arc<Trash>>) -> CollaborateResult<Option<()>>,
+        F: FnOnce(&mut View) -> CollaborateResult<Option<()>>,
     {
-        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<PlainTextAttributes> {
 #[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<PlainDelta>) -> RootFolder {
+    fn make_folder_from_delta(mut initial_delta: PlainDelta, deltas: Vec<PlainDelta>) -> FolderPad {
         for delta in deltas {
             initial_delta = initial_delta.compose(&delta).unwrap();
         }
-        RootFolder::from_delta(initial_delta).unwrap()
+        FolderPad::from_delta(initial_delta).unwrap()
     }
 }

+ 3 - 2
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::*;