2023-08-04 11:27:14 +00:00
use crate ::entities ::icon ::UpdateViewIconParams ;
2023-04-04 00:41:16 +00:00
use crate ::entities ::{
2024-04-23 13:46:57 +00:00
view_pb_with_child_views , view_pb_without_child_views , view_pb_without_child_views_from_arc ,
CreateViewParams , CreateWorkspaceParams , DeletedViewPB , FolderSnapshotPB , MoveNestedViewParams ,
RepeatedTrashPB , RepeatedViewIdPB , RepeatedViewPB , UpdateViewParams , ViewPB , ViewSectionPB ,
WorkspacePB , WorkspaceSettingPB ,
2023-12-17 19:14:05 +00:00
} ;
use crate ::manager_observer ::{
2024-03-04 17:24:49 +00:00
notify_child_views_changed , notify_did_update_workspace , notify_parent_view_did_change ,
ChildViewChangeReason ,
2023-04-04 00:41:16 +00:00
} ;
use crate ::notification ::{
2023-11-01 03:45:35 +00:00
send_notification , send_workspace_setting_notification , FolderNotification ,
2023-04-04 00:41:16 +00:00
} ;
2023-05-31 06:08:54 +00:00
use crate ::share ::ImportParams ;
2023-12-29 05:02:27 +00:00
use crate ::util ::{
folder_not_init_error , insert_parent_child_views , workspace_data_not_sync_error ,
} ;
2023-08-28 05:28:24 +00:00
use crate ::view_operation ::{ create_view , FolderOperationHandler , FolderOperationHandlers } ;
2024-04-15 06:50:28 +00:00
use collab ::core ::collab ::{ DataSource , MutexCollab } ;
2024-03-23 01:18:47 +00:00
use collab_entity ::CollabType ;
use collab_folder ::error ::FolderError ;
use collab_folder ::{
2024-04-26 01:44:07 +00:00
Folder , FolderNotify , Section , SectionItem , TrashInfo , UserId , View , ViewLayout , ViewUpdate ,
Workspace ,
2024-03-23 01:18:47 +00:00
} ;
use collab_integrate ::collab_builder ::{ AppFlowyCollabBuilder , CollabBuilderConfig } ;
2024-04-26 01:44:07 +00:00
use collab_integrate ::CollabKVDB ;
2024-03-23 01:18:47 +00:00
use flowy_error ::{ ErrorCode , FlowyError , FlowyResult } ;
use flowy_folder_pub ::cloud ::{ gen_view_id , FolderCloudService } ;
use flowy_folder_pub ::folder_builder ::ParentChildViews ;
2024-04-12 08:21:41 +00:00
use flowy_search_pub ::entities ::FolderIndexManager ;
2024-04-23 13:46:57 +00:00
use parking_lot ::RwLock ;
2024-03-23 01:18:47 +00:00
use std ::fmt ::{ Display , Formatter } ;
use std ::ops ::Deref ;
use std ::sync ::{ Arc , Weak } ;
2024-04-24 02:26:58 +00:00
use tracing ::{ error , info , instrument } ;
2023-04-04 00:41:16 +00:00
2024-04-26 01:44:07 +00:00
pub trait FolderUser : Send + Sync {
fn user_id ( & self ) -> Result < i64 , FlowyError > ;
fn workspace_id ( & self ) -> Result < String , FlowyError > ;
fn collab_db ( & self , uid : i64 ) -> Result < Weak < CollabKVDB > , FlowyError > ;
2023-07-29 01:46:24 +00:00
}
2023-07-05 12:57:09 +00:00
pub struct FolderManager {
2024-04-12 08:21:41 +00:00
/// MutexFolder is the folder that is used to store the data.
2023-12-17 19:14:05 +00:00
pub ( crate ) mutex_folder : Arc < MutexFolder > ,
2024-02-24 23:49:44 +00:00
pub ( crate ) collab_builder : Arc < AppFlowyCollabBuilder > ,
2023-12-17 19:14:05 +00:00
pub ( crate ) user : Arc < dyn FolderUser > ,
pub ( crate ) operation_handlers : FolderOperationHandlers ,
2023-11-28 23:49:47 +00:00
pub cloud_service : Arc < dyn FolderCloudService > ,
2024-04-12 08:21:41 +00:00
pub ( crate ) folder_indexer : Arc < dyn FolderIndexManager > ,
2023-04-04 00:41:16 +00:00
}
2023-07-05 12:57:09 +00:00
impl FolderManager {
2023-04-04 00:41:16 +00:00
pub async fn new (
user : Arc < dyn FolderUser > ,
2023-05-15 14:16:05 +00:00
collab_builder : Arc < AppFlowyCollabBuilder > ,
2023-05-31 06:08:54 +00:00
operation_handlers : FolderOperationHandlers ,
2023-05-23 15:55:21 +00:00
cloud_service : Arc < dyn FolderCloudService > ,
2024-04-12 08:21:41 +00:00
folder_indexer : Arc < dyn FolderIndexManager > ,
2023-04-04 00:41:16 +00:00
) -> FlowyResult < Self > {
2023-05-31 09:42:14 +00:00
let mutex_folder = Arc ::new ( MutexFolder ::default ( ) ) ;
2023-04-04 00:41:16 +00:00
let manager = Self {
user ,
2023-05-31 09:42:14 +00:00
mutex_folder ,
2023-05-15 14:16:05 +00:00
collab_builder ,
2023-05-31 06:08:54 +00:00
operation_handlers ,
2023-05-23 15:55:21 +00:00
cloud_service ,
2024-04-12 08:21:41 +00:00
folder_indexer ,
2023-04-04 00:41:16 +00:00
} ;
Ok ( manager )
}
2023-11-01 03:45:35 +00:00
#[ instrument(level = " debug " , skip(self), err) ]
2023-06-06 16:05:27 +00:00
pub async fn get_current_workspace ( & self ) -> FlowyResult < WorkspacePB > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2023-07-14 05:37:13 +00:00
self . with_folder (
2023-08-21 16:19:15 +00:00
| | {
let uid = self . user . user_id ( ) ? ;
Err ( workspace_data_not_sync_error ( uid , & workspace_id ) )
} ,
2023-07-14 05:37:13 +00:00
| folder | {
let workspace_pb_from_workspace = | workspace : Workspace , folder : & Folder | {
2024-04-26 01:44:07 +00:00
let views = get_workspace_public_view_pbs ( & workspace_id , folder ) ;
2023-07-14 05:37:13 +00:00
let workspace : WorkspacePB = ( workspace , views ) . into ( ) ;
Ok ::< WorkspacePB , FlowyError > ( workspace )
} ;
2023-06-07 14:27:13 +00:00
2024-04-26 01:44:07 +00:00
match folder . get_workspace_info ( & workspace_id ) {
2023-11-01 03:45:35 +00:00
None = > Err ( FlowyError ::record_not_found ( ) . with_context ( " Can not find the workspace " ) ) ,
2023-07-14 05:37:13 +00:00
Some ( workspace ) = > workspace_pb_from_workspace ( workspace , folder ) ,
}
} ,
)
2023-04-04 00:41:16 +00:00
}
2023-06-05 05:10:14 +00:00
/// Return a list of views of the current workspace.
/// Only the first level of child views are included.
2024-03-22 09:15:18 +00:00
pub async fn get_current_workspace_public_views ( & self ) -> FlowyResult < Vec < ViewPB > > {
2024-04-26 01:44:07 +00:00
let views = self . get_workspace_public_views ( ) . await ? ;
Ok ( views )
2023-04-04 06:08:50 +00:00
}
2023-04-04 00:41:16 +00:00
2024-04-24 00:47:33 +00:00
pub async fn get_workspace_public_views ( & self ) -> FlowyResult < Vec < ViewPB > > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
Ok ( self . with_folder ( Vec ::new , | folder | {
get_workspace_public_view_pbs ( & workspace_id , folder )
} ) )
2024-03-21 04:02:03 +00:00
}
2024-04-24 00:47:33 +00:00
pub async fn get_workspace_private_views ( & self ) -> FlowyResult < Vec < ViewPB > > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
Ok ( self . with_folder ( Vec ::new , | folder | {
get_workspace_private_view_pbs ( & workspace_id , folder )
} ) )
2023-04-04 00:41:16 +00:00
}
2024-04-26 01:44:07 +00:00
#[ instrument(level = " trace " , skip_all, err) ]
2024-02-24 23:49:44 +00:00
pub ( crate ) async fn make_folder < T : Into < Option < FolderNotify > > > (
2023-07-14 05:37:13 +00:00
& self ,
uid : i64 ,
workspace_id : & str ,
2024-01-04 16:05:38 +00:00
collab_db : Weak < CollabKVDB > ,
2024-04-15 06:50:28 +00:00
doc_state : DataSource ,
2024-02-24 23:49:44 +00:00
folder_notifier : T ,
) -> Result < Folder , FlowyError > {
let folder_notifier = folder_notifier . into ( ) ;
2024-04-24 06:38:47 +00:00
// only need the check the workspace id when the doc state is not from the disk.
let should_check_workspace_id = ! matches! ( doc_state , DataSource ::Disk ) ;
let should_auto_initialize = ! should_check_workspace_id ;
let config = CollabBuilderConfig ::default ( )
. sync_enable ( true )
. auto_initialize ( should_auto_initialize ) ;
2024-04-26 01:44:07 +00:00
let object_id = workspace_id ;
2024-03-23 01:18:47 +00:00
let collab = self . collab_builder . build_with_config (
workspace_id ,
2024-04-26 01:44:07 +00:00
uid ,
object_id ,
2024-03-23 01:18:47 +00:00
CollabType ::Folder ,
collab_db ,
doc_state ,
2024-04-24 06:38:47 +00:00
config ,
2024-03-23 01:18:47 +00:00
) ? ;
2024-04-24 06:38:47 +00:00
let ( should_clear , err ) = match Folder ::open ( UserId ::from ( uid ) , collab . clone ( ) , folder_notifier )
{
2024-02-24 23:49:44 +00:00
Ok ( folder ) = > {
2024-04-24 06:38:47 +00:00
if should_check_workspace_id {
// check the workspace id in the folder is matched with the workspace id. Just in case the folder
// is overwritten by another workspace.
let folder_workspace_id = folder . get_workspace_id ( ) ;
if folder_workspace_id ! = workspace_id {
error! (
2024-04-26 01:44:07 +00:00
" expect workspace_id: {}, actual workspace_id: {} " ,
2024-04-24 06:38:47 +00:00
workspace_id , folder_workspace_id
) ;
return Err ( FlowyError ::workspace_data_not_match ( ) ) ;
}
// Initialize the folder manually
collab . lock ( ) . initialize ( ) ;
}
2024-02-24 23:49:44 +00:00
return Ok ( folder ) ;
} ,
Err ( err ) = > ( matches! ( err , FolderError ::NoRequiredData ( _ ) ) , err ) ,
} ;
// If opening the folder fails due to missing required data (indicated by a `FolderError::NoRequiredData`),
// the function logs an informational message and attempts to clear the folder data by deleting its
// document from the collaborative database. It then returns the encountered error.
if should_clear {
info! ( " Clear the folder data and try to open the folder again " ) ;
if let Some ( db ) = self . user . collab_db ( uid ) . ok ( ) . and_then ( | a | a . upgrade ( ) ) {
let _ = db . delete_doc ( uid , workspace_id ) . await ;
}
}
Err ( err . into ( ) )
}
pub ( crate ) async fn create_empty_collab (
& self ,
uid : i64 ,
workspace_id : & str ,
collab_db : Weak < CollabKVDB > ,
) -> Result < Arc < MutexCollab > , FlowyError > {
2024-04-26 01:44:07 +00:00
let object_id = workspace_id ;
2024-03-23 01:18:47 +00:00
let collab = self . collab_builder . build_with_config (
workspace_id ,
2024-04-26 01:44:07 +00:00
uid ,
object_id ,
2024-03-23 01:18:47 +00:00
CollabType ::Folder ,
collab_db ,
2024-04-15 06:50:28 +00:00
DataSource ::Disk ,
2024-03-23 01:18:47 +00:00
CollabBuilderConfig ::default ( ) . sync_enable ( true ) ,
) ? ;
2023-07-14 05:37:13 +00:00
Ok ( collab )
}
2023-07-29 01:46:24 +00:00
/// Initialize the folder with the given workspace id.
/// Fetch the folder updates from the cloud service and initialize the folder.
2023-11-05 06:00:24 +00:00
#[ tracing::instrument(skip(self, user_id), err) ]
2024-04-26 01:44:07 +00:00
pub async fn initialize_with_workspace_id ( & self , user_id : i64 ) -> FlowyResult < ( ) > {
let workspace_id = self . user . workspace_id ( ) ? ;
let object_id = & workspace_id ;
2023-11-28 23:49:47 +00:00
let folder_doc_state = self
2023-07-14 05:37:13 +00:00
. cloud_service
2024-04-26 01:44:07 +00:00
. get_folder_doc_state ( & workspace_id , user_id , CollabType ::Folder , object_id )
2023-07-14 05:37:13 +00:00
. await ? ;
2023-12-18 20:36:24 +00:00
if let Err ( err ) = self
2023-07-14 05:37:13 +00:00
. initialize (
user_id ,
2024-04-26 01:44:07 +00:00
& workspace_id ,
2023-11-28 23:49:47 +00:00
FolderInitDataSource ::Cloud ( folder_doc_state ) ,
2023-07-14 05:37:13 +00:00
)
2023-12-18 20:36:24 +00:00
. await
{
// If failed to open folder with remote data, open from local disk. After open from the local
// disk. the data will be synced to the remote server.
2024-02-03 21:49:45 +00:00
error! ( " initialize folder with error {:?}, fallback local " , err ) ;
2023-12-18 20:36:24 +00:00
self
. initialize (
user_id ,
2024-04-26 01:44:07 +00:00
& workspace_id ,
2023-12-18 20:36:24 +00:00
FolderInitDataSource ::LocalDisk {
create_if_not_exist : false ,
} ,
)
. await ? ;
}
2023-07-14 05:37:13 +00:00
Ok ( ( ) )
}
2023-07-29 01:46:24 +00:00
/// Initialize the folder for the new user.
/// Using the [DefaultFolderBuilder] to create the default workspace for the new user.
2023-11-05 06:00:24 +00:00
#[ instrument(level = " info " , skip_all, err) ]
2023-07-29 01:46:24 +00:00
pub async fn initialize_with_new_user (
2023-05-21 10:53:59 +00:00
& self ,
user_id : i64 ,
2023-07-14 05:37:13 +00:00
_token : & str ,
2023-07-05 12:57:09 +00:00
is_new : bool ,
2023-11-05 06:00:24 +00:00
data_source : FolderInitDataSource ,
2023-05-21 10:53:59 +00:00
workspace_id : & str ,
) -> FlowyResult < ( ) > {
2023-07-05 12:57:09 +00:00
// Create the default workspace if the user is new
2023-10-07 01:58:44 +00:00
info! ( " initialize_when_sign_up: is_new: {} " , is_new ) ;
2023-07-05 12:57:09 +00:00
if is_new {
2023-08-28 05:28:24 +00:00
self . initialize ( user_id , workspace_id , data_source ) . await ? ;
2023-07-14 05:37:13 +00:00
} else {
2023-08-17 15:46:39 +00:00
// The folder updates should not be empty, as the folder data is stored
// when the user signs up for the first time.
2023-10-07 01:58:44 +00:00
let result = self
2023-07-14 05:37:13 +00:00
. cloud_service
2024-02-03 21:49:45 +00:00
. get_folder_doc_state ( workspace_id , user_id , CollabType ::Folder , workspace_id )
2023-10-07 01:58:44 +00:00
. await
. map_err ( FlowyError ::from ) ;
2023-08-17 15:46:39 +00:00
2023-10-07 01:58:44 +00:00
match result {
2024-04-26 01:44:07 +00:00
Ok ( folder_doc_state ) = > {
2023-10-07 01:58:44 +00:00
info! (
2024-02-22 00:07:59 +00:00
" Get folder updates via {}, doc state len: {} " ,
2023-10-07 01:58:44 +00:00
self . cloud_service . service_name ( ) ,
2024-04-26 01:44:07 +00:00
folder_doc_state . len ( )
2023-10-07 01:58:44 +00:00
) ;
self
. initialize (
user_id ,
workspace_id ,
2024-04-26 01:44:07 +00:00
FolderInitDataSource ::Cloud ( folder_doc_state ) ,
2023-10-07 01:58:44 +00:00
)
. await ? ;
} ,
Err ( err ) = > {
if err . is_record_not_found ( ) {
self . initialize ( user_id , workspace_id , data_source ) . await ? ;
} else {
return Err ( err ) ;
}
} ,
}
2023-07-05 12:57:09 +00:00
}
2023-04-04 00:41:16 +00:00
Ok ( ( ) )
}
/// Called when the current user logout
///
2023-04-28 06:08:53 +00:00
pub async fn clear ( & self , _user_id : i64 ) { }
2023-04-04 00:41:16 +00:00
2023-06-08 06:20:31 +00:00
#[ tracing::instrument(level = " info " , skip_all, err) ]
2024-02-03 21:49:45 +00:00
pub async fn create_workspace ( & self , params : CreateWorkspaceParams ) -> FlowyResult < Workspace > {
let uid = self . user . user_id ( ) ? ;
let new_workspace = self
. cloud_service
. create_workspace ( uid , & params . name )
. await ? ;
Ok ( new_workspace )
2023-04-04 00:41:16 +00:00
}
2023-11-05 06:00:24 +00:00
pub async fn get_workspace_setting_pb ( & self ) -> FlowyResult < WorkspaceSettingPB > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2023-11-01 03:45:35 +00:00
let latest_view = self . get_current_view ( ) . await ;
2023-11-05 06:00:24 +00:00
Ok ( WorkspaceSettingPB {
2023-11-01 03:45:35 +00:00
workspace_id ,
latest_view ,
} )
}
2024-01-12 06:34:59 +00:00
pub async fn insert_parent_child_views (
& self ,
views : Vec < ParentChildViews > ,
) -> Result < ( ) , FlowyError > {
self . with_folder (
| | Err ( FlowyError ::internal ( ) . with_context ( " The folder is not initialized " ) ) ,
| folder | {
for view in views {
insert_parent_child_views ( folder , view ) ;
}
Ok ( ( ) )
} ,
) ? ;
Ok ( ( ) )
}
2023-11-05 06:00:24 +00:00
pub async fn get_workspace_pb ( & self ) -> FlowyResult < WorkspacePB > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
let guard = self . mutex_folder . read ( ) ;
let folder = guard
2023-07-29 01:46:24 +00:00
. as_ref ( )
2024-04-26 01:44:07 +00:00
. ok_or ( FlowyError ::internal ( ) . with_context ( " folder is not initialized " ) ) ? ;
let workspace = folder
. get_workspace_info ( & workspace_id )
. ok_or_else ( | | FlowyError ::record_not_found ( ) . with_context ( " Can not find the workspace " ) ) ? ;
let views = folder
. views
. get_views_belong_to ( & workspace . id )
. into_iter ( )
. map ( | view | view_pb_without_child_views ( view . as_ref ( ) . clone ( ) ) )
. collect ::< Vec < ViewPB > > ( ) ;
drop ( guard ) ;
Ok ( WorkspacePB {
id : workspace . id ,
name : workspace . name ,
views ,
create_time : workspace . created_at ,
} )
2023-07-29 01:46:24 +00:00
}
2023-08-21 16:19:15 +00:00
/// This function acquires a lock on the `mutex_folder` and checks its state.
/// If the folder is `None`, it invokes the `none_callback`, otherwise, it passes the folder to the `f2` callback.
///
/// # Parameters
///
/// * `none_callback`: A callback function that is invoked when `mutex_folder` contains `None`.
/// * `f2`: A callback function that is invoked when `mutex_folder` contains a `Some` value. The contained folder is passed as an argument to this callback.
fn with_folder < F1 , F2 , Output > ( & self , none_callback : F1 , f2 : F2 ) -> Output
2023-04-04 00:41:16 +00:00
where
2023-08-21 16:19:15 +00:00
F1 : FnOnce ( ) -> Output ,
F2 : FnOnce ( & Folder ) -> Output ,
2023-04-04 00:41:16 +00:00
{
2024-04-23 13:46:57 +00:00
let folder = self . mutex_folder . read ( ) ;
2023-04-04 00:41:16 +00:00
match & * folder {
2023-08-21 16:19:15 +00:00
None = > none_callback ( ) ,
Some ( folder ) = > f2 ( folder ) ,
2023-04-04 00:41:16 +00:00
}
}
pub async fn create_view_with_params ( & self , params : CreateViewParams ) -> FlowyResult < View > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2023-04-04 00:41:16 +00:00
let view_layout : ViewLayout = params . layout . clone ( ) . into ( ) ;
2023-05-31 06:08:54 +00:00
let handler = self . get_handler ( & view_layout ) ? ;
2023-04-04 00:41:16 +00:00
let user_id = self . user . user_id ( ) ? ;
2023-06-01 12:23:27 +00:00
let meta = params . meta . clone ( ) ;
2023-06-06 09:19:53 +00:00
if meta . is_empty ( ) & & params . initial_data . is_empty ( ) {
tracing ::trace! ( " Create view with build-in data " ) ;
handler
. create_built_in_view ( user_id , & params . view_id , & params . name , view_layout . clone ( ) )
. await ? ;
} else {
tracing ::trace! ( " Create view with view data " ) ;
handler
. create_view_with_view_data (
user_id ,
& params . view_id ,
& params . name ,
params . initial_data . clone ( ) ,
view_layout . clone ( ) ,
meta ,
)
. await ? ;
2023-04-04 00:41:16 +00:00
}
2023-06-06 09:19:53 +00:00
2023-08-02 13:20:51 +00:00
let index = params . index ;
2024-03-21 04:02:03 +00:00
let section = params . section . clone ( ) . unwrap_or ( ViewSectionPB ::Public ) ;
let is_private = section = = ViewSectionPB ::Private ;
2023-11-08 13:48:17 +00:00
let view = create_view ( self . user . user_id ( ) ? , params , view_layout ) ;
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . insert_view ( view . clone ( ) , index ) ;
2024-03-21 04:02:03 +00:00
if is_private {
folder . add_private_view_ids ( vec! [ view . id . clone ( ) ] ) ;
}
2023-08-21 16:19:15 +00:00
} ,
) ;
2023-04-04 00:41:16 +00:00
2024-04-26 01:44:07 +00:00
let folder = & self . mutex_folder . read ( ) ;
if let Some ( folder ) = folder . as_ref ( ) {
notify_did_update_workspace ( & workspace_id , folder ) ;
2024-04-12 08:21:41 +00:00
}
2023-04-04 00:41:16 +00:00
Ok ( view )
}
2023-06-14 14:16:33 +00:00
/// The orphan view is meant to be a view that is not attached to any parent view. By default, this
/// view will not be shown in the view list unless it is attached to a parent view that is shown in
/// the view list.
pub async fn create_orphan_view_with_params (
& self ,
params : CreateViewParams ,
) -> FlowyResult < View > {
let view_layout : ViewLayout = params . layout . clone ( ) . into ( ) ;
2024-01-04 00:02:12 +00:00
// TODO(nathan): remove orphan view. Just use for create document in row
2023-06-14 14:16:33 +00:00
let handler = self . get_handler ( & view_layout ) ? ;
let user_id = self . user . user_id ( ) ? ;
handler
. create_built_in_view ( user_id , & params . view_id , & params . name , view_layout . clone ( ) )
. await ? ;
2024-01-03 03:41:29 +00:00
2023-11-08 13:48:17 +00:00
let view = create_view ( self . user . user_id ( ) ? , params , view_layout ) ;
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . insert_view ( view . clone ( ) , None ) ;
} ,
) ;
2023-06-14 14:16:33 +00:00
Ok ( view )
}
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " debug " , skip(self), err) ]
pub ( crate ) async fn close_view ( & self , view_id : & str ) -> Result < ( ) , FlowyError > {
2023-08-21 16:19:15 +00:00
if let Some ( view ) = self . with_folder ( | | None , | folder | folder . views . get_view ( view_id ) ) {
2023-07-29 01:46:24 +00:00
let handler = self . get_handler ( & view . layout ) ? ;
handler . close_view ( view_id ) . await ? ;
}
2023-04-04 00:41:16 +00:00
Ok ( ( ) )
}
2024-04-09 00:58:53 +00:00
/// Retrieves the view corresponding to the specified view ID.
///
/// It is important to note that if the target view contains child views,
/// this method only provides access to the first level of child views.
///
/// Therefore, to access a nested child view within one of the initial child views, you must invoke this method
/// again using the ID of the child view you wish to access.
2024-02-09 02:18:52 +00:00
#[ tracing::instrument(level = " debug " , skip(self)) ]
2023-11-01 03:45:35 +00:00
pub async fn get_view_pb ( & self , view_id : & str ) -> FlowyResult < ViewPB > {
2023-04-04 00:41:16 +00:00
let view_id = view_id . to_string ( ) ;
2024-04-23 13:46:57 +00:00
let folder = self . mutex_folder . read ( ) ;
2023-04-04 00:41:16 +00:00
let folder = folder . as_ref ( ) . ok_or_else ( folder_not_init_error ) ? ;
2024-04-07 15:06:33 +00:00
// trash views and other private views should not be accessed
let view_ids_should_be_filtered = self . get_view_ids_should_be_filtered ( folder ) ;
if view_ids_should_be_filtered . contains ( & view_id ) {
2024-02-09 02:18:52 +00:00
return Err ( FlowyError ::new (
ErrorCode ::RecordNotFound ,
2024-04-09 00:58:53 +00:00
format! ( " View: {} is in trash or other private sections " , view_id ) ,
2024-02-09 02:18:52 +00:00
) ) ;
2023-04-04 00:41:16 +00:00
}
match folder . views . get_view ( & view_id ) {
2024-02-09 02:18:52 +00:00
None = > {
error! ( " Can't find the view with id: {} " , view_id ) ;
Err ( FlowyError ::record_not_found ( ) )
} ,
2023-06-23 15:19:34 +00:00
Some ( view ) = > {
2023-05-10 11:43:32 +00:00
let child_views = folder
2023-04-04 00:41:16 +00:00
. views
2023-05-10 11:43:32 +00:00
. get_views_belong_to ( & view . id )
2023-04-04 00:41:16 +00:00
. into_iter ( )
2024-04-07 15:06:33 +00:00
. filter ( | view | ! view_ids_should_be_filtered . contains ( & view . id ) )
2023-06-23 15:19:34 +00:00
. collect ::< Vec < _ > > ( ) ;
2023-05-10 11:43:32 +00:00
let view_pb = view_pb_with_child_views ( view , child_views ) ;
2023-04-04 00:41:16 +00:00
Ok ( view_pb )
} ,
}
}
2024-04-23 13:46:57 +00:00
/// Retrieves all views.
///
/// It is important to note that this will return a flat map of all views,
/// excluding all child views themselves, as they are all at the same level in this
/// map.
///
#[ tracing::instrument(level = " debug " , skip(self)) ]
pub async fn get_all_views_pb ( & self ) -> FlowyResult < Vec < ViewPB > > {
let folder = self . mutex_folder . read ( ) ;
let folder = folder . as_ref ( ) . ok_or_else ( folder_not_init_error ) ? ;
// trash views and other private views should not be accessed
let view_ids_should_be_filtered = self . get_view_ids_should_be_filtered ( folder ) ;
let all_views = folder . views . get_all_views ( ) ;
let views = all_views
. into_iter ( )
. filter ( | view | ! view_ids_should_be_filtered . contains ( & view . id ) )
. map ( view_pb_without_child_views_from_arc )
. collect ::< Vec < _ > > ( ) ;
Ok ( views )
}
2024-04-09 00:58:53 +00:00
/// Retrieves the ancestors of the view corresponding to the specified view ID, including the view itself.
///
/// For example, if the view hierarchy is as follows:
/// - View A
/// - View B
/// - View C
///
/// If you invoke this method with the ID of View C, it will return a list of views: [View A, View B, View C].
#[ tracing::instrument(level = " debug " , skip(self)) ]
pub async fn get_view_ancestors_pb ( & self , view_id : & str ) -> FlowyResult < Vec < ViewPB > > {
let mut ancestors = vec! [ ] ;
let mut parent_view_id = view_id . to_string ( ) ;
while let Some ( view ) =
self . with_folder ( | | None , | folder | folder . views . get_view ( & parent_view_id ) )
{
ancestors . push ( view_pb_without_child_views ( view . as_ref ( ) . clone ( ) ) ) ;
parent_view_id = view . parent_view_id . clone ( ) ;
}
ancestors . reverse ( ) ;
Ok ( ancestors )
}
2023-06-03 05:40:12 +00:00
/// Move the view to trash. If the view is the current view, then set the current view to empty.
/// When the view is moved to trash, all the child views will be moved to trash as well.
2023-08-02 13:20:51 +00:00
/// All the favorite views being trashed will be unfavorited first to remove it from favorites list as well. The process of unfavoriting concerned view is handled by `unfavorite_view_and_decendants()`
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " debug " , skip(self), err) ]
pub async fn move_view_to_trash ( & self , view_id : & str ) -> FlowyResult < ( ) > {
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
if let Some ( view ) = folder . views . get_view ( view_id ) {
self . unfavorite_view_and_decendants ( view . clone ( ) , folder ) ;
2024-03-22 09:15:18 +00:00
folder . add_trash_view_ids ( vec! [ view_id . to_string ( ) ] ) ;
2023-08-21 16:19:15 +00:00
// notify the parent view that the view is moved to trash
send_notification ( view_id , FolderNotification ::DidMoveViewToTrash )
. payload ( DeletedViewPB {
view_id : view_id . to_string ( ) ,
index : None ,
} )
. send ( ) ;
notify_child_views_changed (
2024-03-12 02:59:52 +00:00
view_pb_without_child_views ( view . as_ref ( ) . clone ( ) ) ,
2023-12-29 05:02:27 +00:00
ChildViewChangeReason ::Delete ,
2023-08-21 16:19:15 +00:00
) ;
}
} ,
) ;
2023-04-04 00:41:16 +00:00
Ok ( ( ) )
}
2023-08-02 13:20:51 +00:00
fn unfavorite_view_and_decendants ( & self , view : Arc < View > , folder : & Folder ) {
let mut all_descendant_views : Vec < Arc < View > > = vec! [ view . clone ( ) ] ;
all_descendant_views . extend ( folder . views . get_views_belong_to ( & view . id ) ) ;
let favorite_descendant_views : Vec < ViewPB > = all_descendant_views
. iter ( )
. filter ( | view | view . is_favorite )
2024-03-12 02:59:52 +00:00
. map ( | view | view_pb_without_child_views ( view . as_ref ( ) . clone ( ) ) )
2023-08-02 13:20:51 +00:00
. collect ( ) ;
if ! favorite_descendant_views . is_empty ( ) {
2024-03-22 09:15:18 +00:00
folder . delete_favorite_view_ids (
2023-08-02 13:20:51 +00:00
favorite_descendant_views
. iter ( )
. map ( | v | v . id . clone ( ) )
. collect ( ) ,
) ;
send_notification ( " favorite " , FolderNotification ::DidUnfavoriteView )
. payload ( RepeatedViewPB {
items : favorite_descendant_views ,
} )
. send ( ) ;
}
}
2023-07-26 08:49:50 +00:00
/// Moves a nested view to a new location in the hierarchy.
///
/// This function takes the `view_id` of the view to be moved,
/// `new_parent_id` of the view under which the `view_id` should be moved,
/// and an optional `prev_view_id` to position the `view_id` right after
/// this specific view.
///
/// If `prev_view_id` is provided, the moved view will be placed right after
/// the view corresponding to `prev_view_id` under the `new_parent_id`.
/// If `prev_view_id` is `None`, the moved view will become the first child of the new parent.
///
/// # Arguments
///
/// * `view_id` - A string slice that holds the id of the view to be moved.
/// * `new_parent_id` - A string slice that holds the id of the new parent view.
/// * `prev_view_id` - An `Option<String>` that holds the id of the view after which the `view_id` should be positioned.
///
#[ tracing::instrument(level = " trace " , skip(self), err) ]
2024-03-21 04:02:03 +00:00
pub async fn move_nested_view ( & self , params : MoveNestedViewParams ) -> FlowyResult < ( ) > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2024-03-21 04:02:03 +00:00
let view_id = params . view_id ;
let new_parent_id = params . new_parent_id ;
let prev_view_id = params . prev_view_id ;
let from_section = params . from_section ;
let to_section = params . to_section ;
2023-11-01 03:45:35 +00:00
let view = self . get_view_pb ( & view_id ) . await ? ;
2023-07-26 08:49:50 +00:00
let old_parent_id = view . parent_view_id ;
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . move_nested_view ( & view_id , & new_parent_id , prev_view_id ) ;
2024-03-21 04:02:03 +00:00
if from_section ! = to_section {
if to_section = = Some ( ViewSectionPB ::Private ) {
folder . add_private_view_ids ( vec! [ view_id . clone ( ) ] ) ;
} else {
folder . delete_private_view_ids ( vec! [ view_id . clone ( ) ] ) ;
}
}
2023-08-21 16:19:15 +00:00
} ,
) ;
2023-07-26 08:49:50 +00:00
notify_parent_view_did_change (
2024-04-26 01:44:07 +00:00
& workspace_id ,
2023-07-26 08:49:50 +00:00
self . mutex_folder . clone ( ) ,
vec! [ new_parent_id , old_parent_id ] ,
) ;
Ok ( ( ) )
}
2023-06-06 16:05:27 +00:00
/// Move the view with given id from one position to another position.
/// The view will be moved to the new position in the same parent view.
/// The passed in index is the index of the view that displayed in the UI.
/// We need to convert the index to the real index of the view in the parent view.
#[ tracing::instrument(level = " trace " , skip(self), err) ]
2023-04-04 00:41:16 +00:00
pub async fn move_view ( & self , view_id : & str , from : usize , to : usize ) -> FlowyResult < ( ) > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2023-06-06 16:05:27 +00:00
if let Some ( ( is_workspace , parent_view_id , child_views ) ) = self . get_view_relation ( view_id ) . await
{
// The display parent view is the view that is displayed in the UI
let display_views = if is_workspace {
self
. get_current_workspace ( )
. await ?
. views
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < _ > > ( )
} else {
self
2023-11-01 03:45:35 +00:00
. get_view_pb ( & parent_view_id )
2023-06-06 16:05:27 +00:00
. await ?
. child_views
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < _ > > ( )
} ;
2023-04-04 00:41:16 +00:00
2023-06-06 16:05:27 +00:00
if display_views . len ( ) > to {
let to_view_id = display_views [ to ] . clone ( ) ;
// Find the actual index of the view in the parent view
let actual_from_index = child_views . iter ( ) . position ( | id | id = = view_id ) ;
let actual_to_index = child_views . iter ( ) . position ( | id | id = = & to_view_id ) ;
if let ( Some ( actual_from_index ) , Some ( actual_to_index ) ) =
( actual_from_index , actual_to_index )
{
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . move_view ( view_id , actual_from_index as u32 , actual_to_index as u32 ) ;
} ,
) ;
2024-04-26 01:44:07 +00:00
notify_parent_view_did_change (
& workspace_id ,
self . mutex_folder . clone ( ) ,
vec! [ parent_view_id ] ,
) ;
2023-06-06 16:05:27 +00:00
}
}
2023-04-04 00:41:16 +00:00
}
Ok ( ( ) )
}
2023-06-03 05:40:12 +00:00
/// Return a list of views that belong to the given parent view id.
#[ tracing::instrument(level = " debug " , skip(self, parent_view_id), err) ]
2023-06-23 15:19:34 +00:00
pub async fn get_views_belong_to ( & self , parent_view_id : & str ) -> FlowyResult < Vec < Arc < View > > > {
2023-11-01 03:45:35 +00:00
let views = self . with_folder ( Vec ::new , | folder | {
2023-08-28 05:28:24 +00:00
folder . views . get_views_belong_to ( parent_view_id )
} ) ;
2023-04-04 00:41:16 +00:00
Ok ( views )
}
2023-06-03 05:40:12 +00:00
/// Update the view with the given params.
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " trace " , skip(self), err) ]
2023-05-10 11:43:32 +00:00
pub async fn update_view_with_params ( & self , params : UpdateViewParams ) -> FlowyResult < ( ) > {
2023-08-04 11:27:14 +00:00
self
. update_view ( & params . view_id , | update | {
2023-04-04 00:41:16 +00:00
update
. set_name_if_not_none ( params . name )
. set_desc_if_not_none ( params . desc )
2023-06-01 12:23:27 +00:00
. set_layout_if_not_none ( params . layout )
2023-08-02 13:20:51 +00:00
. set_favorite_if_not_none ( params . is_favorite )
2023-04-04 00:41:16 +00:00
. done ( )
2023-08-04 11:27:14 +00:00
} )
. await
}
2023-04-04 00:41:16 +00:00
2023-08-04 11:27:14 +00:00
/// Update the icon of the view with the given params.
#[ tracing::instrument(level = " trace " , skip(self), err) ]
pub async fn update_view_icon_with_params (
& self ,
params : UpdateViewIconParams ,
) -> FlowyResult < ( ) > {
self
. update_view ( & params . view_id , | update | {
update . set_icon ( params . icon ) . done ( )
} )
. await
2023-04-04 00:41:16 +00:00
}
2023-06-03 05:40:12 +00:00
/// Duplicate the view with the given view id.
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " debug " , skip(self), err) ]
pub ( crate ) async fn duplicate_view ( & self , view_id : & str ) -> Result < ( ) , FlowyError > {
let view = self
2023-08-21 16:19:15 +00:00
. with_folder ( | | None , | folder | folder . views . get_view ( view_id ) )
. ok_or_else ( | | FlowyError ::record_not_found ( ) . with_context ( " Can't duplicate the view " ) ) ? ;
2023-04-04 00:41:16 +00:00
2023-05-31 06:08:54 +00:00
let handler = self . get_handler ( & view . layout ) ? ;
let view_data = handler . duplicate_view ( & view . id ) . await ? ;
2023-08-02 13:20:51 +00:00
// get the current view index in the parent view, because we need to insert the duplicated view below the current view.
let index = if let Some ( ( _ , __ , views ) ) = self . get_view_relation ( & view . parent_view_id ) . await {
views . iter ( ) . position ( | id | id = = view_id ) . map ( | i | i as u32 )
} else {
None
} ;
2024-03-22 09:15:18 +00:00
let is_private = self . with_folder (
| | false ,
| folder | folder . is_view_in_section ( Section ::Private , & view . id ) ,
) ;
let section = if is_private {
ViewSectionPB ::Private
} else {
ViewSectionPB ::Public
} ;
2023-04-04 00:41:16 +00:00
let duplicate_params = CreateViewParams {
2023-06-01 12:23:27 +00:00
parent_view_id : view . parent_view_id . clone ( ) ,
2023-04-04 00:41:16 +00:00
name : format ! ( " {} (copy) " , & view . name ) ,
2023-06-23 15:19:34 +00:00
desc : view . desc . clone ( ) ,
layout : view . layout . clone ( ) . into ( ) ,
2023-04-04 00:41:16 +00:00
initial_data : view_data . to_vec ( ) ,
2023-08-28 05:28:24 +00:00
view_id : gen_view_id ( ) . to_string ( ) ,
2023-06-03 05:40:12 +00:00
meta : Default ::default ( ) ,
set_as_current : true ,
2023-08-02 13:20:51 +00:00
index ,
2024-03-22 09:15:18 +00:00
section : Some ( section ) ,
2023-04-04 00:41:16 +00:00
} ;
2023-08-02 13:20:51 +00:00
self . create_view_with_params ( duplicate_params ) . await ? ;
2023-04-04 00:41:16 +00:00
Ok ( ( ) )
}
#[ tracing::instrument(level = " trace " , skip(self), err) ]
pub ( crate ) async fn set_current_view ( & self , view_id : & str ) -> Result < ( ) , FlowyError > {
2024-04-26 01:44:07 +00:00
self . with_folder (
2023-11-01 03:45:35 +00:00
| | Err ( FlowyError ::record_not_found ( ) ) ,
| folder | {
folder . set_current_view ( view_id ) ;
2023-11-13 02:07:46 +00:00
folder . add_recent_view_ids ( vec! [ view_id . to_string ( ) ] ) ;
2024-04-26 01:44:07 +00:00
Ok ( ( ) )
2023-11-01 03:45:35 +00:00
} ,
) ? ;
2023-04-04 00:41:16 +00:00
2024-03-30 08:28:24 +00:00
let view = self . get_current_view ( ) . await ;
if let Some ( view ) = & view {
let view_layout : ViewLayout = view . layout . clone ( ) . into ( ) ;
if let Some ( handle ) = self . operation_handlers . get ( & view_layout ) {
let _ = handle . open_view ( view_id ) . await ;
}
}
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2024-03-30 08:28:24 +00:00
send_workspace_setting_notification ( workspace_id , view ) ;
2023-04-04 00:41:16 +00:00
Ok ( ( ) )
}
#[ tracing::instrument(level = " trace " , skip(self)) ]
pub ( crate ) async fn get_current_view ( & self ) -> Option < ViewPB > {
2023-08-21 16:19:15 +00:00
let view_id = self . with_folder ( | | None , | folder | folder . get_current_view ( ) ) ? ;
2023-11-01 03:45:35 +00:00
self . get_view_pb ( & view_id ) . await . ok ( )
2023-04-04 00:41:16 +00:00
}
2023-08-02 13:20:51 +00:00
/// Toggles the favorite status of a view identified by `view_id`If the view is not a favorite, it will be added to the favorites list; otherwise, it will be removed from the list.
#[ tracing::instrument(level = " debug " , skip(self), err) ]
pub async fn toggle_favorites ( & self , view_id : & str ) -> FlowyResult < ( ) > {
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
if let Some ( old_view ) = folder . views . get_view ( view_id ) {
if old_view . is_favorite {
2024-03-22 09:15:18 +00:00
folder . delete_favorite_view_ids ( vec! [ view_id . to_string ( ) ] ) ;
2023-08-21 16:19:15 +00:00
} else {
2024-03-22 09:15:18 +00:00
folder . add_favorite_view_ids ( vec! [ view_id . to_string ( ) ] ) ;
2023-08-21 16:19:15 +00:00
}
2023-08-02 13:20:51 +00:00
}
2023-08-21 16:19:15 +00:00
} ,
) ;
2023-08-02 13:20:51 +00:00
self . send_toggle_favorite_notification ( view_id ) . await ;
Ok ( ( ) )
}
2023-12-01 01:58:36 +00:00
/// Add the view to the recent view list / history.
#[ tracing::instrument(level = " debug " , skip(self), err) ]
pub async fn add_recent_views ( & self , view_ids : Vec < String > ) -> FlowyResult < ( ) > {
self . with_folder (
| | ( ) ,
| folder | {
folder . add_recent_view_ids ( view_ids ) ;
} ,
) ;
self . send_update_recent_views_notification ( ) . await ;
Ok ( ( ) )
}
/// Add the view to the recent view list / history.
#[ tracing::instrument(level = " debug " , skip(self), err) ]
pub async fn remove_recent_views ( & self , view_ids : Vec < String > ) -> FlowyResult < ( ) > {
self . with_folder (
| | ( ) ,
| folder | {
folder . delete_recent_view_ids ( view_ids ) ;
} ,
) ;
self . send_update_recent_views_notification ( ) . await ;
Ok ( ( ) )
}
2023-08-02 13:20:51 +00:00
// Used by toggle_favorites to send notification to frontend, after the favorite status of view has been changed.It sends two distinct notifications: one to correctly update the concerned view's is_favorite status, and another to update the list of favorites that is to be displayed.
async fn send_toggle_favorite_notification ( & self , view_id : & str ) {
2023-11-01 03:45:35 +00:00
if let Ok ( view ) = self . get_view_pb ( view_id ) . await {
2023-08-02 13:20:51 +00:00
let notification_type = if view . is_favorite {
FolderNotification ::DidFavoriteView
} else {
FolderNotification ::DidUnfavoriteView
} ;
send_notification ( " favorite " , notification_type )
. payload ( RepeatedViewPB {
items : vec ! [ view . clone ( ) ] ,
} )
. send ( ) ;
send_notification ( & view . id , FolderNotification ::DidUpdateView )
. payload ( view )
. send ( )
}
}
2023-12-01 01:58:36 +00:00
async fn send_update_recent_views_notification ( & self ) {
2024-04-02 01:57:14 +00:00
let recent_views = self . get_my_recent_sections ( ) . await ;
2023-12-01 01:58:36 +00:00
send_notification ( " recent_views " , FolderNotification ::DidUpdateRecentViews )
. payload ( RepeatedViewIdPB {
items : recent_views . into_iter ( ) . map ( | item | item . id ) . collect ( ) ,
} )
. send ( ) ;
}
2023-08-02 13:20:51 +00:00
#[ tracing::instrument(level = " trace " , skip(self)) ]
2023-11-01 06:47:25 +00:00
pub ( crate ) async fn get_all_favorites ( & self ) -> Vec < SectionItem > {
2023-11-13 02:07:46 +00:00
self . get_sections ( Section ::Favorite )
}
2023-08-02 13:20:51 +00:00
2024-04-02 01:57:14 +00:00
#[ tracing::instrument(level = " debug " , skip(self)) ]
pub ( crate ) async fn get_my_recent_sections ( & self ) -> Vec < SectionItem > {
2023-11-13 02:07:46 +00:00
self . get_sections ( Section ::Recent )
2023-08-02 13:20:51 +00:00
}
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " trace " , skip(self)) ]
2024-03-22 09:15:18 +00:00
pub ( crate ) async fn get_my_trash_info ( & self ) -> Vec < TrashInfo > {
self . with_folder ( Vec ::new , | folder | folder . get_my_trash_info ( ) )
2023-04-04 00:41:16 +00:00
}
#[ tracing::instrument(level = " trace " , skip(self)) ]
pub ( crate ) async fn restore_all_trash ( & self ) {
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
2024-03-22 09:15:18 +00:00
folder . remove_all_my_trash_sections ( ) ;
2023-08-21 16:19:15 +00:00
} ,
) ;
2023-04-04 00:41:16 +00:00
send_notification ( " trash " , FolderNotification ::DidUpdateTrash )
. payload ( RepeatedTrashPB { items : vec ! [ ] } )
. send ( ) ;
}
#[ tracing::instrument(level = " trace " , skip(self)) ]
pub ( crate ) async fn restore_trash ( & self , trash_id : & str ) {
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
2024-03-22 09:15:18 +00:00
folder . delete_trash_view_ids ( vec! [ trash_id . to_string ( ) ] ) ;
2023-08-21 16:19:15 +00:00
} ,
) ;
2023-04-04 00:41:16 +00:00
}
2023-06-05 01:42:11 +00:00
/// Delete all the trash permanently.
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " trace " , skip(self)) ]
2024-03-22 09:15:18 +00:00
pub ( crate ) async fn delete_my_trash ( & self ) {
let deleted_trash = self . with_folder ( Vec ::new , | folder | folder . get_my_trash_info ( ) ) ;
2023-06-05 01:42:11 +00:00
for trash in deleted_trash {
let _ = self . delete_trash ( & trash . id ) . await ;
}
2023-04-04 00:41:16 +00:00
send_notification ( " trash " , FolderNotification ::DidUpdateTrash )
. payload ( RepeatedTrashPB { items : vec ! [ ] } )
. send ( ) ;
}
2023-06-05 01:42:11 +00:00
/// Delete the trash permanently.
/// Delete the view will delete all the resources that the view holds. For example, if the view
/// is a database view. Then the database will be deleted as well.
#[ tracing::instrument(level = " debug " , skip(self, view_id), err) ]
pub async fn delete_trash ( & self , view_id : & str ) -> FlowyResult < ( ) > {
2023-08-21 16:19:15 +00:00
let view = self . with_folder ( | | None , | folder | folder . views . get_view ( view_id ) ) ;
self . with_folder (
| | ( ) ,
| folder | {
2024-03-22 09:15:18 +00:00
folder . delete_trash_view_ids ( vec! [ view_id . to_string ( ) ] ) ;
2023-08-21 16:19:15 +00:00
folder . views . delete_views ( vec! [ view_id ] ) ;
} ,
) ;
2023-06-05 01:42:11 +00:00
if let Some ( view ) = view {
if let Ok ( handler ) = self . get_handler ( & view . layout ) {
handler . delete_view ( view_id ) . await ? ;
}
}
Ok ( ( ) )
}
2023-05-31 06:08:54 +00:00
pub ( crate ) async fn import ( & self , import_data : ImportParams ) -> FlowyResult < View > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2023-05-31 06:08:54 +00:00
if import_data . data . is_none ( ) & & import_data . file_path . is_none ( ) {
return Err ( FlowyError ::new (
2023-07-05 12:57:09 +00:00
ErrorCode ::InvalidParams ,
2023-05-31 06:08:54 +00:00
" data or file_path is required " ,
) ) ;
}
let handler = self . get_handler ( & import_data . view_layout ) ? ;
2023-08-28 05:28:24 +00:00
let view_id = gen_view_id ( ) . to_string ( ) ;
2023-08-17 15:46:39 +00:00
let uid = self . user . user_id ( ) ? ;
2023-05-31 06:08:54 +00:00
if let Some ( data ) = import_data . data {
handler
2023-08-17 15:46:39 +00:00
. import_from_bytes (
uid ,
& view_id ,
& import_data . name ,
import_data . import_type ,
data ,
)
2023-05-31 06:08:54 +00:00
. await ? ;
}
if let Some ( file_path ) = import_data . file_path {
handler
. import_from_file_path ( & view_id , & import_data . name , file_path )
. await ? ;
}
let params = CreateViewParams {
parent_view_id : import_data . parent_view_id ,
name : import_data . name ,
desc : " " . to_string ( ) ,
layout : import_data . view_layout . clone ( ) . into ( ) ,
initial_data : vec ! [ ] ,
view_id ,
meta : Default ::default ( ) ,
2023-06-03 05:40:12 +00:00
set_as_current : false ,
2023-08-02 13:20:51 +00:00
index : None ,
2024-03-22 09:15:18 +00:00
section : None ,
2023-05-31 06:08:54 +00:00
} ;
2023-11-08 13:48:17 +00:00
let view = create_view ( self . user . user_id ( ) ? , params , import_data . view_layout ) ;
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . insert_view ( view . clone ( ) , None ) ;
} ,
) ;
2024-04-26 01:44:07 +00:00
notify_parent_view_did_change (
& workspace_id ,
self . mutex_folder . clone ( ) ,
vec! [ view . parent_view_id . clone ( ) ] ,
) ;
2023-05-31 06:08:54 +00:00
Ok ( view )
}
2023-08-04 11:27:14 +00:00
/// Update the view with the provided view_id using the specified function.
async fn update_view < F > ( & self , view_id : & str , f : F ) -> FlowyResult < ( ) >
where
F : FnOnce ( ViewUpdate ) -> Option < View > ,
{
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) ? ;
2023-08-21 16:19:15 +00:00
let value = self . with_folder (
| | None ,
| folder | {
let old_view = folder . views . get_view ( view_id ) ;
let new_view = folder . views . update_view ( view_id , f ) ;
2023-08-04 11:27:14 +00:00
2023-08-21 16:19:15 +00:00
Some ( ( old_view , new_view ) )
} ,
) ;
2023-08-04 11:27:14 +00:00
if let Some ( ( Some ( old_view ) , Some ( new_view ) ) ) = value {
if let Ok ( handler ) = self . get_handler ( & old_view . layout ) {
handler . did_update_view ( & old_view , & new_view ) . await ? ;
}
}
2023-11-01 03:45:35 +00:00
if let Ok ( view_pb ) = self . get_view_pb ( view_id ) . await {
2023-08-04 11:27:14 +00:00
send_notification ( & view_pb . id , FolderNotification ::DidUpdateView )
. payload ( view_pb )
. send ( ) ;
2024-03-04 17:24:49 +00:00
2024-04-26 01:44:07 +00:00
let folder = & self . mutex_folder . read ( ) ;
if let Some ( folder ) = folder . as_ref ( ) {
notify_did_update_workspace ( & workspace_id , folder ) ;
2024-03-04 17:24:49 +00:00
}
2023-08-04 11:27:14 +00:00
}
2024-03-04 17:24:49 +00:00
2023-08-04 11:27:14 +00:00
Ok ( ( ) )
}
2023-05-31 06:08:54 +00:00
/// Returns a handler that implements the [FolderOperationHandler] trait
fn get_handler (
2023-04-04 00:41:16 +00:00
& self ,
view_layout : & ViewLayout ,
2023-05-31 06:08:54 +00:00
) -> FlowyResult < Arc < dyn FolderOperationHandler + Send + Sync > > {
match self . operation_handlers . get ( view_layout ) {
2023-08-21 16:19:15 +00:00
None = > Err ( FlowyError ::internal ( ) . with_context ( format! (
2023-04-04 00:41:16 +00:00
" Get data processor failed. Unknown layout type: {:?} " ,
view_layout
) ) ) ,
Some ( processor ) = > Ok ( processor . clone ( ) ) ,
}
}
2023-06-06 16:05:27 +00:00
/// Returns the relation of the view. The relation is a tuple of (is_workspace, parent_view_id,
/// child_view_ids). If the view is a workspace, then the parent_view_id is the workspace id.
/// Otherwise, the parent_view_id is the parent view id of the view. The child_view_ids is the
/// child view ids of the view.
async fn get_view_relation ( & self , view_id : & str ) -> Option < ( bool , String , Vec < String > ) > {
2024-04-26 01:44:07 +00:00
let workspace_id = self . user . workspace_id ( ) . ok ( ) ? ;
2023-08-21 16:19:15 +00:00
self . with_folder (
| | None ,
| folder | {
let view = folder . views . get_view ( view_id ) ? ;
match folder . views . get_view ( & view . parent_view_id ) {
2024-04-26 01:44:07 +00:00
None = > folder . get_workspace_info ( & workspace_id ) . map ( | workspace | {
2023-08-21 16:19:15 +00:00
(
true ,
workspace . id ,
workspace
. child_views
. items
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < String > > ( ) ,
)
} ) ,
Some ( parent_view ) = > Some ( (
false ,
parent_view . id . clone ( ) ,
parent_view
. children
2023-06-06 16:05:27 +00:00
. items
2023-08-21 16:19:15 +00:00
. clone ( )
2023-06-06 16:05:27 +00:00
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < String > > ( ) ,
2023-08-21 16:19:15 +00:00
) ) ,
}
} ,
)
2023-06-06 16:05:27 +00:00
}
2023-07-05 12:57:09 +00:00
pub async fn get_folder_snapshots (
& self ,
workspace_id : & str ,
2023-08-17 15:46:39 +00:00
limit : usize ,
2023-07-05 12:57:09 +00:00
) -> FlowyResult < Vec < FolderSnapshotPB > > {
2023-08-17 15:46:39 +00:00
let snapshots = self
2023-07-05 12:57:09 +00:00
. cloud_service
2023-08-17 15:46:39 +00:00
. get_folder_snapshots ( workspace_id , limit )
2023-07-05 12:57:09 +00:00
. await ?
2023-08-17 15:46:39 +00:00
. into_iter ( )
2023-07-05 12:57:09 +00:00
. map ( | snapshot | FolderSnapshotPB {
snapshot_id : snapshot . snapshot_id ,
snapshot_desc : " " . to_string ( ) ,
created_at : snapshot . created_at ,
data : snapshot . data ,
} )
2023-08-17 15:46:39 +00:00
. collect ::< Vec < _ > > ( ) ;
2023-07-05 12:57:09 +00:00
Ok ( snapshots )
}
2024-04-01 06:27:29 +00:00
pub fn set_views_visibility ( & self , view_ids : Vec < String > , is_public : bool ) {
self . with_folder (
| | ( ) ,
| folder | {
if is_public {
folder . delete_private_view_ids ( view_ids ) ;
} else {
folder . add_private_view_ids ( view_ids ) ;
}
} ,
) ;
}
2024-04-02 01:57:14 +00:00
/// Only support getting the Favorite and Recent sections.
2023-11-13 02:07:46 +00:00
fn get_sections ( & self , section_type : Section ) -> Vec < SectionItem > {
self . with_folder ( Vec ::new , | folder | {
2024-04-02 01:57:14 +00:00
let views = match section_type {
2024-03-22 09:15:18 +00:00
Section ::Favorite = > folder . get_my_favorite_sections ( ) ,
Section ::Recent = > folder . get_my_recent_sections ( ) ,
2023-11-13 02:07:46 +00:00
_ = > vec! [ ] ,
} ;
2024-04-02 01:57:14 +00:00
let view_ids_should_be_filtered = self . get_view_ids_should_be_filtered ( folder ) ;
2023-11-13 02:07:46 +00:00
views
2024-04-02 01:57:14 +00:00
. into_iter ( )
. filter ( | view | ! view_ids_should_be_filtered . contains ( & view . id ) )
. collect ( )
2023-11-13 02:07:46 +00:00
} )
}
2024-04-02 01:57:14 +00:00
/// Get all the view that are in the trash, including the child views of the child views.
/// For example, if A view which is in the trash has a child view B, this function will return
/// both A and B.
fn get_all_trash_ids ( & self , folder : & Folder ) -> Vec < String > {
let trash_ids = folder
. get_all_trash_sections ( )
. into_iter ( )
. map ( | trash | trash . id )
. collect ::< Vec < String > > ( ) ;
let mut all_trash_ids = trash_ids . clone ( ) ;
for trash_id in trash_ids {
all_trash_ids . extend ( get_all_child_view_ids ( folder , & trash_id ) ) ;
}
all_trash_ids
}
/// Filter the views that are in the trash and belong to the other private sections.
fn get_view_ids_should_be_filtered ( & self , folder : & Folder ) -> Vec < String > {
let trash_ids = self . get_all_trash_ids ( folder ) ;
let other_private_view_ids = self . get_other_private_view_ids ( folder ) ;
[ trash_ids , other_private_view_ids ] . concat ( )
}
fn get_other_private_view_ids ( & self , folder : & Folder ) -> Vec < String > {
let my_private_view_ids = folder
. get_my_private_sections ( )
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < String > > ( ) ;
let all_private_view_ids = folder
. get_all_private_sections ( )
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < String > > ( ) ;
all_private_view_ids
. into_iter ( )
. filter ( | id | ! my_private_view_ids . contains ( id ) )
. collect ( )
}
2023-04-04 00:41:16 +00:00
}
2024-03-21 04:02:03 +00:00
/// Return the views that belong to the workspace. The views are filtered by the trash and all the private views.
2024-04-26 01:44:07 +00:00
pub ( crate ) fn get_workspace_public_view_pbs ( workspace_id : & str , folder : & Folder ) -> Vec < ViewPB > {
2024-03-21 04:02:03 +00:00
// get the trash ids
let trash_ids = folder
2024-03-22 09:15:18 +00:00
. get_all_trash_sections ( )
2023-04-04 06:08:50 +00:00
. into_iter ( )
. map ( | trash | trash . id )
. collect ::< Vec < String > > ( ) ;
2024-03-21 04:02:03 +00:00
// get the private view ids
let private_view_ids = folder
2024-03-22 09:15:18 +00:00
. get_all_private_sections ( )
2024-03-21 04:02:03 +00:00
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < String > > ( ) ;
2024-04-26 01:44:07 +00:00
let mut views = folder . views . get_views_belong_to ( workspace_id ) ;
2024-03-21 04:02:03 +00:00
// filter the views that are in the trash and all the private views
views . retain ( | view | ! trash_ids . contains ( & view . id ) & & ! private_view_ids . contains ( & view . id ) ) ;
views
. into_iter ( )
. map ( | view | {
// Get child views
let child_views = folder
. views
. get_views_belong_to ( & view . id )
. into_iter ( )
. collect ( ) ;
view_pb_with_child_views ( view , child_views )
} )
. collect ( )
}
2024-04-02 01:57:14 +00:00
/// Get all the child views belong to the view id, including the child views of the child views.
fn get_all_child_view_ids ( folder : & Folder , view_id : & str ) -> Vec < String > {
let child_view_ids = folder
. views
. get_views_belong_to ( view_id )
. into_iter ( )
. map ( | view | view . id . clone ( ) )
. collect ::< Vec < String > > ( ) ;
let mut all_child_view_ids = child_view_ids . clone ( ) ;
for child_view_id in child_view_ids {
all_child_view_ids . extend ( get_all_child_view_ids ( folder , & child_view_id ) ) ;
}
all_child_view_ids
}
2024-03-21 04:02:03 +00:00
/// Get the current private views of the user.
2024-04-26 01:44:07 +00:00
pub ( crate ) fn get_workspace_private_view_pbs ( workspace_id : & str , folder : & Folder ) -> Vec < ViewPB > {
2024-03-21 04:02:03 +00:00
// get the trash ids
let trash_ids = folder
2024-03-22 09:15:18 +00:00
. get_all_trash_sections ( )
2024-03-21 04:02:03 +00:00
. into_iter ( )
. map ( | trash | trash . id )
. collect ::< Vec < String > > ( ) ;
// get the private view ids
let private_view_ids = folder
2024-03-22 09:15:18 +00:00
. get_my_private_sections ( )
2024-03-21 04:02:03 +00:00
. into_iter ( )
. map ( | view | view . id )
. collect ::< Vec < String > > ( ) ;
2024-04-26 01:44:07 +00:00
let mut views = folder . views . get_views_belong_to ( workspace_id ) ;
2024-03-21 04:02:03 +00:00
// filter the views that are in the trash and not in the private view ids
views . retain ( | view | ! trash_ids . contains ( & view . id ) & & private_view_ids . contains ( & view . id ) ) ;
2023-04-04 06:08:50 +00:00
views
. into_iter ( )
. map ( | view | {
// Get child views
2023-05-10 11:43:32 +00:00
let child_views = folder
2023-04-04 06:08:50 +00:00
. views
2023-05-10 11:43:32 +00:00
. get_views_belong_to ( & view . id )
2023-04-04 06:08:50 +00:00
. into_iter ( )
. collect ( ) ;
2023-05-10 11:43:32 +00:00
view_pb_with_child_views ( view , child_views )
2023-04-04 06:08:50 +00:00
} )
. collect ( )
}
2024-04-12 08:21:41 +00:00
/// The MutexFolder is a wrapper of the [Folder] that is used to share the folder between different
/// threads.
2023-04-04 00:41:16 +00:00
#[ derive(Clone, Default) ]
2024-04-23 13:46:57 +00:00
pub struct MutexFolder ( Arc < RwLock < Option < Folder > > > ) ;
2023-05-31 09:42:14 +00:00
impl Deref for MutexFolder {
2024-04-23 13:46:57 +00:00
type Target = Arc < RwLock < Option < Folder > > > ;
2023-04-04 00:41:16 +00:00
fn deref ( & self ) -> & Self ::Target {
& self . 0
}
}
2023-05-31 09:42:14 +00:00
unsafe impl Sync for MutexFolder { }
unsafe impl Send for MutexFolder { }
2023-07-14 05:37:13 +00:00
2023-11-08 13:48:17 +00:00
#[ allow(clippy::large_enum_variant) ]
2023-11-05 06:00:24 +00:00
pub enum FolderInitDataSource {
2023-08-17 15:46:39 +00:00
/// It means using the data stored on local disk to initialize the folder
2023-08-28 05:28:24 +00:00
LocalDisk { create_if_not_exist : bool } ,
2023-08-17 15:46:39 +00:00
/// If there is no data stored on local disk, we will use the data from the server to initialize the folder
2024-03-23 01:18:47 +00:00
Cloud ( Vec < u8 > ) ,
2023-07-14 05:37:13 +00:00
}
2023-08-17 15:46:39 +00:00
2023-11-05 06:00:24 +00:00
impl Display for FolderInitDataSource {
fn fmt ( & self , f : & mut Formatter < '_ > ) -> std ::fmt ::Result {
match self {
FolderInitDataSource ::LocalDisk { .. } = > f . write_fmt ( format_args! ( " LocalDisk " ) ) ,
FolderInitDataSource ::Cloud ( _ ) = > f . write_fmt ( format_args! ( " Cloud " ) ) ,
}
}
}