2023-06-06 09:19:53 +00:00
use std ::collections ::HashSet ;
2023-04-28 06:08:53 +00:00
use std ::ops ::Deref ;
2023-05-31 09:42:14 +00:00
use std ::sync ::{ Arc , Weak } ;
2023-04-28 06:08:53 +00:00
2023-07-14 05:37:13 +00:00
use collab ::core ::collab ::{ CollabRawData , MutexCollab } ;
2023-07-05 12:57:09 +00:00
use collab ::core ::collab_state ::SyncState ;
2023-10-10 11:05:55 +00:00
use collab_entity ::CollabType ;
2023-04-28 06:08:53 +00:00
use collab_folder ::core ::{
2023-08-02 13:20:51 +00:00
FavoritesInfo , Folder , FolderData , FolderNotify , TrashChange , TrashChangeReceiver , TrashInfo ,
2023-08-04 11:27:14 +00:00
View , ViewChange , ViewChangeReceiver , ViewLayout , ViewUpdate , Workspace ,
2023-04-28 06:08:53 +00:00
} ;
2023-08-21 16:19:15 +00:00
use parking_lot ::{ Mutex , RwLock } ;
2023-06-05 01:42:11 +00:00
use tokio_stream ::wrappers ::WatchStream ;
use tokio_stream ::StreamExt ;
2023-10-07 01:58:44 +00:00
use tracing ::{ event , info , instrument , Level } ;
2023-04-28 06:08:53 +00:00
2023-09-17 09:14:34 +00:00
use collab_integrate ::collab_builder ::AppFlowyCollabBuilder ;
use collab_integrate ::{ CollabPersistenceConfig , RocksCollabDB , YrsDocAction } ;
2023-05-31 06:08:54 +00:00
use flowy_error ::{ ErrorCode , FlowyError , FlowyResult } ;
2023-08-28 05:28:24 +00:00
use flowy_folder_deps ::cloud ::{ gen_view_id , FolderCloudService } ;
2023-04-28 06:08:53 +00:00
2023-08-04 11:27:14 +00:00
use crate ::entities ::icon ::UpdateViewIconParams ;
2023-04-04 00:41:16 +00:00
use crate ::entities ::{
2023-06-20 15:48:34 +00:00
view_pb_with_child_views , view_pb_without_child_views , ChildViewUpdatePB , CreateViewParams ,
2023-07-05 12:57:09 +00:00
CreateWorkspaceParams , DeletedViewPB , FolderSnapshotPB , FolderSnapshotStatePB , FolderSyncStatePB ,
2023-08-21 16:19:15 +00:00
RepeatedTrashPB , RepeatedViewPB , RepeatedWorkspacePB , UpdateViewParams , UserFolderPB , ViewPB ,
WorkspacePB ,
2023-04-04 00:41:16 +00:00
} ;
use crate ::notification ::{
send_notification , send_workspace_notification , send_workspace_setting_notification ,
FolderNotification ,
} ;
2023-05-31 06:08:54 +00:00
use crate ::share ::ImportParams ;
2023-05-23 15:55:21 +00:00
use crate ::user_default ::DefaultFolderBuilder ;
2023-08-28 05:28:24 +00:00
use crate ::view_operation ::{ create_view , FolderOperationHandler , FolderOperationHandlers } ;
2023-04-04 00:41:16 +00:00
2023-07-29 01:46:24 +00:00
/// [FolderUser] represents the user for folder.
pub trait FolderUser : Send + Sync {
fn user_id ( & self ) -> Result < i64 , FlowyError > ;
fn token ( & self ) -> Result < Option < String > , FlowyError > ;
fn collab_db ( & self , uid : i64 ) -> Result < Weak < RocksCollabDB > , FlowyError > ;
}
2023-07-05 12:57:09 +00:00
pub struct FolderManager {
2023-08-21 16:19:15 +00:00
workspace_id : RwLock < Option < String > > ,
2023-05-31 09:42:14 +00:00
mutex_folder : Arc < MutexFolder > ,
2023-05-15 14:16:05 +00:00
collab_builder : Arc < AppFlowyCollabBuilder > ,
2023-04-04 00:41:16 +00:00
user : Arc < dyn FolderUser > ,
2023-05-31 06:08:54 +00:00
operation_handlers : FolderOperationHandlers ,
2023-05-23 15:55:21 +00:00
cloud_service : Arc < dyn FolderCloudService > ,
2023-04-04 00:41:16 +00:00
}
2023-07-05 12:57:09 +00:00
unsafe impl Send for FolderManager { }
unsafe impl Sync for FolderManager { }
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 > ,
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 ,
2023-08-21 16:19:15 +00:00
workspace_id : Default ::default ( ) ,
2023-04-04 00:41:16 +00:00
} ;
Ok ( manager )
}
2023-06-06 16:05:27 +00:00
pub async fn get_current_workspace ( & self ) -> FlowyResult < WorkspacePB > {
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 ( ) ? ;
let workspace_id = self . workspace_id . read ( ) . as_ref ( ) . cloned ( ) . ok_or (
FlowyError ::from ( ErrorCode ::WorkspaceIdInvalid )
. with_context ( " Unexpected empty workspace 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 | {
let views = get_workspace_view_pbs ( & workspace . id , folder ) ;
let workspace : WorkspacePB = ( workspace , views ) . into ( ) ;
Ok ::< WorkspacePB , FlowyError > ( workspace )
} ;
2023-06-07 14:27:13 +00:00
2023-07-14 05:37:13 +00:00
match folder . get_current_workspace ( ) {
None = > {
// The current workspace should always exist. If not, try to find the first workspace.
// from the folder. Otherwise, return an error.
let mut workspaces = folder . workspaces . get_all_workspaces ( ) ;
if workspaces . is_empty ( ) {
2023-08-21 16:19:15 +00:00
Err ( FlowyError ::record_not_found ( ) . with_context ( " Can not find the workspace " ) )
2023-07-14 05:37:13 +00:00
} else {
tracing ::error! ( " Can't find the current workspace, use the first workspace " ) ;
let workspace = workspaces . remove ( 0 ) ;
folder . set_current_workspace ( & workspace . id ) ;
workspace_pb_from_workspace ( workspace , folder )
}
} ,
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.
2023-04-04 06:08:50 +00:00
pub async fn get_current_workspace_views ( & self ) -> FlowyResult < Vec < ViewPB > > {
let workspace_id = self
2023-05-31 09:42:14 +00:00
. mutex_folder
2023-04-04 06:08:50 +00:00
. lock ( )
. as_ref ( )
. map ( | folder | folder . get_current_workspace_id ( ) ) ;
if let Some ( Some ( workspace_id ) ) = workspace_id {
self . get_workspace_views ( & workspace_id ) . await
} else {
2023-07-14 05:37:13 +00:00
tracing ::warn! ( " Can't get current workspace views " ) ;
2023-04-04 06:08:50 +00:00
Ok ( vec! [ ] )
}
}
2023-04-04 00:41:16 +00:00
2023-04-04 06:08:50 +00:00
pub async fn get_workspace_views ( & self , workspace_id : & str ) -> FlowyResult < Vec < ViewPB > > {
2023-08-28 05:28:24 +00:00
let views = self . with_folder ( std ::vec ::Vec ::new , | folder | {
get_workspace_view_pbs ( workspace_id , folder )
} ) ;
2023-04-04 00:41:16 +00:00
Ok ( views )
}
2023-04-28 06:08:53 +00:00
/// Called immediately after the application launched fi the user already sign in/sign up.
2023-07-14 05:37:13 +00:00
#[ tracing::instrument(level = " info " , skip(self, initial_data), err) ]
pub async fn initialize (
& self ,
uid : i64 ,
workspace_id : & str ,
2023-08-28 05:28:24 +00:00
initial_data : FolderInitializeDataSource ,
2023-07-14 05:37:13 +00:00
) -> FlowyResult < ( ) > {
2023-08-21 16:19:15 +00:00
* self . workspace_id . write ( ) = Some ( workspace_id . to_string ( ) ) ;
2023-05-31 09:42:14 +00:00
let workspace_id = workspace_id . to_string ( ) ;
2023-07-14 05:37:13 +00:00
if let Ok ( collab_db ) = self . user . collab_db ( uid ) {
2023-05-21 10:53:59 +00:00
let ( view_tx , view_rx ) = tokio ::sync ::broadcast ::channel ( 100 ) ;
let ( trash_tx , trash_rx ) = tokio ::sync ::broadcast ::channel ( 100 ) ;
2023-07-14 05:37:13 +00:00
let folder_notifier = FolderNotify {
2023-05-31 09:42:14 +00:00
view_change_tx : view_tx ,
trash_change_tx : trash_tx ,
2023-05-21 10:53:59 +00:00
} ;
2023-07-14 05:37:13 +00:00
let folder = match initial_data {
2023-08-28 05:28:24 +00:00
FolderInitializeDataSource ::LocalDisk {
create_if_not_exist ,
} = > {
2023-08-17 15:46:39 +00:00
let is_exist = is_exist_in_local_disk ( & self . user , & workspace_id ) . unwrap_or ( false ) ;
2023-08-28 05:28:24 +00:00
if is_exist {
2023-10-07 01:58:44 +00:00
let collab = self
. collab_for_folder ( uid , & workspace_id , collab_db , vec! [ ] )
. await ? ;
2023-08-28 05:28:24 +00:00
Folder ::open ( collab , Some ( folder_notifier ) )
} else if create_if_not_exist {
let folder_data =
DefaultFolderBuilder ::build ( uid , workspace_id . to_string ( ) , & self . operation_handlers )
. await ;
2023-10-07 01:58:44 +00:00
let collab = self
. collab_for_folder ( uid , & workspace_id , collab_db , vec! [ ] )
. await ? ;
2023-08-28 05:28:24 +00:00
Folder ::create ( collab , Some ( folder_notifier ) , Some ( folder_data ) )
} else {
2023-08-17 15:46:39 +00:00
return Err ( FlowyError ::new (
ErrorCode ::RecordNotFound ,
" Can't find any workspace data " ,
) ) ;
}
2023-07-14 05:37:13 +00:00
} ,
2023-08-28 05:28:24 +00:00
FolderInitializeDataSource ::Cloud ( raw_data ) = > {
2023-08-17 15:46:39 +00:00
if raw_data . is_empty ( ) {
2023-08-21 16:19:15 +00:00
return Err ( workspace_data_not_sync_error ( uid , & workspace_id ) ) ;
2023-08-17 15:46:39 +00:00
}
2023-10-07 01:58:44 +00:00
let collab = self
. collab_for_folder ( uid , & workspace_id , collab_db , raw_data )
. await ? ;
2023-07-14 05:37:13 +00:00
Folder ::open ( collab , Some ( folder_notifier ) )
} ,
2023-08-28 05:28:24 +00:00
FolderInitializeDataSource ::FolderData ( folder_data ) = > {
2023-10-07 01:58:44 +00:00
let collab = self
. collab_for_folder ( uid , & workspace_id , collab_db , vec! [ ] )
. await ? ;
2023-07-14 05:37:13 +00:00
Folder ::create ( collab , Some ( folder_notifier ) , Some ( folder_data ) )
} ,
} ;
tracing ::debug! ( " Current workspace_id: {} " , workspace_id ) ;
2023-07-05 12:57:09 +00:00
let folder_state_rx = folder . subscribe_sync_state ( ) ;
2023-05-31 09:42:14 +00:00
* self . mutex_folder . lock ( ) = Some ( folder ) ;
let weak_mutex_folder = Arc ::downgrade ( & self . mutex_folder ) ;
2023-07-05 12:57:09 +00:00
subscribe_folder_sync_state_changed (
workspace_id . clone ( ) ,
folder_state_rx ,
& weak_mutex_folder ,
) ;
subscribe_folder_snapshot_state_changed ( workspace_id , & weak_mutex_folder ) ;
subscribe_folder_trash_changed ( trash_rx , & weak_mutex_folder ) ;
subscribe_folder_view_changed ( view_rx , & weak_mutex_folder ) ;
2023-04-04 00:41:16 +00:00
}
Ok ( ( ) )
}
2023-10-07 01:58:44 +00:00
async fn collab_for_folder (
2023-07-14 05:37:13 +00:00
& self ,
uid : i64 ,
workspace_id : & str ,
2023-07-29 01:46:24 +00:00
collab_db : Weak < RocksCollabDB > ,
2023-07-14 05:37:13 +00:00
raw_data : CollabRawData ,
) -> Result < Arc < MutexCollab > , FlowyError > {
2023-10-07 01:58:44 +00:00
let collab = self
. collab_builder
. build_with_config (
uid ,
workspace_id ,
CollabType ::Folder ,
collab_db ,
raw_data ,
& CollabPersistenceConfig ::new ( ) . enable_snapshot ( true ) ,
)
. await ? ;
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-07-14 05:37:13 +00:00
#[ tracing::instrument(level = " debug " , skip(self, user_id), err) ]
2023-07-29 01:46:24 +00:00
pub async fn initialize_with_workspace_id (
& self ,
user_id : i64 ,
workspace_id : & str ,
) -> FlowyResult < ( ) > {
2023-07-14 05:37:13 +00:00
let folder_updates = self
. cloud_service
. get_folder_updates ( workspace_id , user_id )
. await ? ;
2023-10-07 01:58:44 +00:00
info! (
2023-07-14 05:37:13 +00:00
" Get folder updates via {}, number of updates: {} " ,
self . cloud_service . service_name ( ) ,
folder_updates . len ( )
) ;
self
. initialize (
user_id ,
workspace_id ,
2023-08-28 05:28:24 +00:00
FolderInitializeDataSource ::Cloud ( folder_updates ) ,
2023-07-14 05:37:13 +00:00
)
. await ? ;
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-10-07 01:58:44 +00:00
#[ instrument(level = " debug " , 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-08-28 05:28:24 +00:00
data_source : FolderInitializeDataSource ,
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
. get_folder_updates ( workspace_id , user_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 {
Ok ( folder_updates ) = > {
info! (
" Get folder updates via {}, number of updates: {} " ,
self . cloud_service . service_name ( ) ,
folder_updates . len ( )
) ;
self
. initialize (
user_id ,
workspace_id ,
FolderInitializeDataSource ::Cloud ( folder_updates ) ,
)
. 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) ]
2023-04-04 00:41:16 +00:00
pub async fn create_workspace ( & self , params : CreateWorkspaceParams ) -> FlowyResult < Workspace > {
2023-05-23 15:55:21 +00:00
let workspace = self
. cloud_service
. create_workspace ( self . user . user_id ( ) ? , & params . name )
. await ? ;
2023-06-07 14:27:13 +00:00
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . workspaces . create_workspace ( workspace . clone ( ) ) ;
folder . set_current_workspace ( & workspace . id ) ;
} ,
) ;
2023-04-04 00:41:16 +00:00
let repeated_workspace = RepeatedWorkspacePB {
items : vec ! [ workspace . clone ( ) . into ( ) ] ,
} ;
send_workspace_notification ( FolderNotification ::DidCreateWorkspace , repeated_workspace ) ;
Ok ( workspace )
}
2023-06-08 06:20:31 +00:00
#[ tracing::instrument(level = " info " , skip_all, err) ]
2023-04-04 00:41:16 +00:00
pub async fn open_workspace ( & self , workspace_id : & str ) -> FlowyResult < Workspace > {
2023-08-21 16:19:15 +00:00
self . with_folder (
| | Err ( FlowyError ::internal ( ) ) ,
| folder | {
let workspace = folder
. workspaces
. get_workspace ( workspace_id )
. ok_or_else ( | | {
FlowyError ::record_not_found ( ) . with_context ( " Can't open not existing workspace " )
} ) ? ;
folder . set_current_workspace ( & workspace . id ) ;
Ok ::< Workspace , FlowyError > ( workspace )
} ,
)
2023-04-04 00:41:16 +00:00
}
pub async fn get_workspace ( & self , workspace_id : & str ) -> Option < Workspace > {
2023-08-21 16:19:15 +00:00
self . with_folder (
| | None ,
| folder | folder . workspaces . get_workspace ( workspace_id ) ,
)
2023-04-04 00:41:16 +00:00
}
2023-07-29 01:46:24 +00:00
async fn get_current_workspace_id ( & self ) -> FlowyResult < String > {
self
. mutex_folder
. lock ( )
. as_ref ( )
. and_then ( | folder | folder . get_current_workspace_id ( ) )
2023-08-21 16:19:15 +00:00
. ok_or ( FlowyError ::internal ( ) . with_context ( " Unexpected empty workspace id " ) )
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
{
2023-05-31 09:42:14 +00:00
let folder = self . mutex_folder . lock ( ) ;
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 get_all_workspaces ( & self ) -> Vec < Workspace > {
2023-08-28 05:28:24 +00:00
self . with_folder ( std ::vec ::Vec ::new , | folder | {
folder . workspaces . get_all_workspaces ( )
} )
2023-04-04 00:41:16 +00:00
}
pub async fn create_view_with_params ( & self , params : CreateViewParams ) -> FlowyResult < View > {
let view_layout : ViewLayout = params . layout . clone ( ) . into ( ) ;
2023-07-29 01:46:24 +00:00
let _workspace_id = self . get_current_workspace_id ( ) . await ? ;
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 ;
2023-05-31 06:08:54 +00:00
let view = create_view ( params , view_layout ) ;
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . insert_view ( view . clone ( ) , index ) ;
} ,
) ;
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 ( ) ;
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 ? ;
let view = create_view ( 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 ( ( ) )
}
2023-06-03 05:40:12 +00:00
/// Returns the view with the given view id.
/// The child views of the view will only access the first. So if you want to get the child view's
/// child view, you need to call this method again.
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " debug " , skip(self, view_id), err) ]
pub async fn get_view ( & self , view_id : & str ) -> FlowyResult < ViewPB > {
let view_id = view_id . to_string ( ) ;
2023-05-31 09:42:14 +00:00
let folder = self . mutex_folder . lock ( ) ;
2023-04-04 00:41:16 +00:00
let folder = folder . as_ref ( ) . ok_or_else ( folder_not_init_error ) ? ;
let trash_ids = folder
. get_all_trash ( )
. into_iter ( )
. map ( | trash | trash . id )
. collect ::< Vec < String > > ( ) ;
if trash_ids . contains ( & view_id ) {
return Err ( FlowyError ::record_not_found ( ) ) ;
}
match folder . views . get_view ( & view_id ) {
None = > 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 ( )
. filter ( | view | ! trash_ids . 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 )
} ,
}
}
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 ) ;
folder . add_trash ( vec! [ view_id . to_string ( ) ] ) ;
// 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 (
view_pb_without_child_views ( view ) ,
ChildViewChangeReason ::DidDeleteView ,
) ;
}
} ,
) ;
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 )
. map ( | view | view_pb_without_child_views ( view . clone ( ) ) )
. collect ( ) ;
if ! favorite_descendant_views . is_empty ( ) {
folder . delete_favorites (
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) ]
pub async fn move_nested_view (
& self ,
view_id : String ,
new_parent_id : String ,
prev_view_id : Option < String > ,
) -> FlowyResult < ( ) > {
let view = self . get_view ( & view_id ) . await ? ;
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 ) ;
} ,
) ;
2023-07-26 08:49:50 +00:00
notify_parent_view_did_change (
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 < ( ) > {
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
. get_view ( & parent_view_id )
. 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 ) ;
} ,
) ;
2023-06-06 16:05:27 +00:00
notify_parent_view_did_change ( self . mutex_folder . clone ( ) , vec! [ parent_view_id ] ) ;
}
}
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-08-28 05:28:24 +00:00
let views = self . with_folder ( std ::vec ::Vec ::new , | folder | {
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
} ;
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 ,
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 > {
2023-05-31 09:42:14 +00:00
let folder = self . mutex_folder . lock ( ) ;
2023-04-04 00:41:16 +00:00
let folder = folder . as_ref ( ) . ok_or_else ( folder_not_init_error ) ? ;
folder . set_current_view ( view_id ) ;
let workspace = folder . get_current_workspace ( ) ;
let view = folder
. get_current_view ( )
. and_then ( | view_id | folder . views . get_view ( & view_id ) ) ;
send_workspace_setting_notification ( workspace , view ) ;
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-04-04 00:41:16 +00:00
self . get_view ( & view_id ) . await . ok ( )
}
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 {
folder . delete_favorites ( vec! [ view_id . to_string ( ) ] ) ;
} else {
folder . add_favorites ( vec! [ view_id . to_string ( ) ] ) ;
}
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 ( ( ) )
}
// 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 ) {
if let Ok ( view ) = self . get_view ( view_id ) . await {
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 ( )
}
}
#[ tracing::instrument(level = " trace " , skip(self)) ]
pub ( crate ) async fn get_all_favorites ( & self ) -> Vec < FavoritesInfo > {
2023-08-28 05:28:24 +00:00
self . with_folder ( std ::vec ::Vec ::new , | folder | {
let trash_ids = folder
. get_all_trash ( )
. into_iter ( )
. map ( | trash | trash . id )
. collect ::< Vec < String > > ( ) ;
2023-08-02 13:20:51 +00:00
2023-08-28 05:28:24 +00:00
let mut views = folder . get_all_favorites ( ) ;
views . retain ( | view | ! trash_ids . contains ( & view . id ) ) ;
views
} )
2023-08-02 13:20:51 +00:00
}
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " trace " , skip(self)) ]
pub ( crate ) async fn get_all_trash ( & self ) -> Vec < TrashInfo > {
2023-08-28 05:28:24 +00:00
self . with_folder ( std ::vec ::Vec ::new , | folder | folder . get_all_trash ( ) )
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 | {
folder . remote_all_trash ( ) ;
} ,
) ;
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 | {
folder . delete_trash ( vec! [ trash_id . to_string ( ) ] ) ;
} ,
) ;
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)) ]
pub ( crate ) async fn delete_all_trash ( & self ) {
2023-08-28 05:28:24 +00:00
let deleted_trash = self . with_folder ( std ::vec ::Vec ::new , | folder | folder . get_all_trash ( ) ) ;
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 | {
folder . delete_trash ( vec! [ view_id . to_string ( ) ] ) ;
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 > {
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 ,
2023-05-31 06:08:54 +00:00
} ;
let view = create_view ( params , import_data . view_layout ) ;
2023-08-21 16:19:15 +00:00
self . with_folder (
| | ( ) ,
| folder | {
folder . insert_view ( view . clone ( ) , None ) ;
} ,
) ;
2023-06-01 12:23:27 +00:00
notify_parent_view_did_change ( 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 > ,
{
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 ? ;
}
}
if let Ok ( view_pb ) = self . get_view ( view_id ) . await {
send_notification ( & view_pb . id , FolderNotification ::DidUpdateView )
. payload ( view_pb )
. send ( ) ;
}
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 > ) > {
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 ) {
None = > folder . get_current_workspace ( ) . map ( | workspace | {
(
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 )
}
/// Only expose this method for testing
#[ cfg(debug_assertions) ]
pub fn get_mutex_folder ( & self ) -> & Arc < MutexFolder > {
& self . mutex_folder
}
/// Only expose this method for testing
#[ cfg(debug_assertions) ]
pub fn get_cloud_service ( & self ) -> & Arc < dyn FolderCloudService > {
& self . cloud_service
}
2023-04-04 00:41:16 +00:00
}
/// Listen on the [ViewChange] after create/delete/update events happened
2023-07-05 12:57:09 +00:00
fn subscribe_folder_view_changed (
mut rx : ViewChangeReceiver ,
weak_mutex_folder : & Weak < MutexFolder > ,
) {
2023-05-31 09:42:14 +00:00
let weak_mutex_folder = weak_mutex_folder . clone ( ) ;
2023-04-04 00:41:16 +00:00
tokio ::spawn ( async move {
while let Ok ( value ) = rx . recv ( ) . await {
2023-05-31 09:42:14 +00:00
if let Some ( folder ) = weak_mutex_folder . upgrade ( ) {
tracing ::trace! ( " Did receive view change: {:?} " , value ) ;
match value {
ViewChange ::DidCreateView { view } = > {
2023-06-20 15:48:34 +00:00
notify_child_views_changed (
2023-06-23 15:19:34 +00:00
view_pb_without_child_views ( Arc ::new ( view . clone ( ) ) ) ,
2023-06-20 15:48:34 +00:00
ChildViewChangeReason ::DidCreateView ,
) ;
2023-06-01 12:23:27 +00:00
notify_parent_view_did_change ( folder . clone ( ) , vec! [ view . parent_view_id ] ) ;
2023-05-31 09:42:14 +00:00
} ,
2023-06-20 15:48:34 +00:00
ViewChange ::DidDeleteView { views } = > {
for view in views {
notify_child_views_changed (
view_pb_without_child_views ( view ) ,
ChildViewChangeReason ::DidDeleteView ,
) ;
}
} ,
2023-05-31 09:42:14 +00:00
ViewChange ::DidUpdate { view } = > {
2023-06-20 15:48:34 +00:00
notify_child_views_changed (
2023-06-23 15:19:34 +00:00
view_pb_without_child_views ( Arc ::new ( view . clone ( ) ) ) ,
2023-06-20 15:48:34 +00:00
ChildViewChangeReason ::DidUpdateView ,
) ;
2023-06-01 12:23:27 +00:00
notify_parent_view_did_change ( folder . clone ( ) , vec! [ view . parent_view_id ] ) ;
2023-05-31 09:42:14 +00:00
} ,
} ;
}
}
} ) ;
}
2023-07-05 12:57:09 +00:00
fn subscribe_folder_snapshot_state_changed (
workspace_id : String ,
weak_mutex_folder : & Weak < MutexFolder > ,
) {
let weak_mutex_folder = weak_mutex_folder . clone ( ) ;
tokio ::spawn ( async move {
if let Some ( mutex_folder ) = weak_mutex_folder . upgrade ( ) {
let stream = mutex_folder
. lock ( )
. as_ref ( )
. map ( | folder | folder . subscribe_snapshot_state ( ) ) ;
if let Some ( mut state_stream ) = stream {
while let Some ( snapshot_state ) = state_stream . next ( ) . await {
if let Some ( new_snapshot_id ) = snapshot_state . snapshot_id ( ) {
2023-07-14 05:37:13 +00:00
tracing ::debug! ( " Did create folder remote snapshot: {} " , new_snapshot_id ) ;
2023-07-05 12:57:09 +00:00
send_notification (
& workspace_id ,
FolderNotification ::DidUpdateFolderSnapshotState ,
)
. payload ( FolderSnapshotStatePB { new_snapshot_id } )
. send ( ) ;
}
}
}
}
} ) ;
}
fn subscribe_folder_sync_state_changed (
2023-05-31 09:42:14 +00:00
workspace_id : String ,
2023-07-14 05:37:13 +00:00
mut folder_sync_state_rx : WatchStream < SyncState > ,
_weak_mutex_folder : & Weak < MutexFolder > ,
2023-05-31 09:42:14 +00:00
) {
tokio ::spawn ( async move {
2023-07-14 05:37:13 +00:00
while let Some ( state ) = folder_sync_state_rx . next ( ) . await {
2023-07-05 12:57:09 +00:00
send_notification ( & workspace_id , FolderNotification ::DidUpdateFolderSyncUpdate )
. payload ( FolderSyncStatePB ::from ( state ) )
. send ( ) ;
2023-04-04 00:41:16 +00:00
}
} ) ;
}
/// Listen on the [TrashChange]s and notify the frontend some views were changed.
2023-07-05 12:57:09 +00:00
fn subscribe_folder_trash_changed (
mut rx : TrashChangeReceiver ,
weak_mutex_folder : & Weak < MutexFolder > ,
) {
2023-05-31 09:42:14 +00:00
let weak_mutex_folder = weak_mutex_folder . clone ( ) ;
2023-04-04 00:41:16 +00:00
tokio ::spawn ( async move {
while let Ok ( value ) = rx . recv ( ) . await {
2023-05-31 09:42:14 +00:00
if let Some ( folder ) = weak_mutex_folder . upgrade ( ) {
let mut unique_ids = HashSet ::new ( ) ;
tracing ::trace! ( " Did receive trash change: {:?} " , value ) ;
let ids = match value {
TrashChange ::DidCreateTrash { ids } = > ids ,
TrashChange ::DidDeleteTrash { ids } = > ids ,
} ;
if let Some ( folder ) = folder . lock ( ) . as_ref ( ) {
let views = folder . views . get_views ( & ids ) ;
for view in views {
2023-06-23 15:19:34 +00:00
unique_ids . insert ( view . parent_view_id . clone ( ) ) ;
2023-05-31 09:42:14 +00:00
}
2023-06-08 06:20:31 +00:00
let repeated_trash : RepeatedTrashPB = folder . get_all_trash ( ) . into ( ) ;
2023-05-31 09:42:14 +00:00
send_notification ( " trash " , FolderNotification ::DidUpdateTrash )
. payload ( repeated_trash )
. send ( ) ;
2023-04-04 00:41:16 +00:00
}
2023-05-31 09:42:14 +00:00
let parent_view_ids = unique_ids . into_iter ( ) . collect ( ) ;
notify_parent_view_did_change ( folder . clone ( ) , parent_view_ids ) ;
2023-04-04 00:41:16 +00:00
}
}
} ) ;
}
2023-06-06 16:05:27 +00:00
/// Return the views that belong to the workspace. The views are filtered by the trash.
2023-05-31 09:42:14 +00:00
fn get_workspace_view_pbs ( workspace_id : & str , folder : & Folder ) -> Vec < ViewPB > {
2023-04-04 06:08:50 +00:00
let trash_ids = folder
. get_all_trash ( )
. into_iter ( )
. map ( | trash | trash . id )
. collect ::< Vec < String > > ( ) ;
let mut views = folder . get_workspace_views ( workspace_id ) ;
views . retain ( | view | ! trash_ids . contains ( & view . id ) ) ;
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 ( )
}
2023-05-31 09:42:14 +00:00
fn notify_did_update_workspace ( workspace_id : & str , folder : & Folder ) {
let repeated_view : RepeatedViewPB = get_workspace_view_pbs ( workspace_id , folder ) . into ( ) ;
tracing ::trace! ( " Did update workspace views: {:?} " , repeated_view ) ;
send_notification ( workspace_id , FolderNotification ::DidUpdateWorkspaceViews )
. payload ( repeated_view )
. send ( ) ;
}
2023-05-24 00:57:58 +00:00
/// Notify the the list of parent view ids that its child views were changed.
2023-04-04 00:41:16 +00:00
#[ tracing::instrument(level = " debug " , skip(folder, parent_view_ids)) ]
fn notify_parent_view_did_change < T : AsRef < str > > (
2023-05-31 09:42:14 +00:00
folder : Arc < MutexFolder > ,
2023-04-04 00:41:16 +00:00
parent_view_ids : Vec < T > ,
) -> Option < ( ) > {
let folder = folder . lock ( ) ;
let folder = folder . as_ref ( ) ? ;
let workspace_id = folder . get_current_workspace_id ( ) ? ;
let trash_ids = folder
. get_all_trash ( )
. into_iter ( )
. map ( | trash | trash . id )
. collect ::< Vec < String > > ( ) ;
for parent_view_id in parent_view_ids {
let parent_view_id = parent_view_id . as_ref ( ) ;
2023-05-21 10:53:59 +00:00
// if the view's parent id equal to workspace id. Then it will fetch the current
2023-04-04 00:41:16 +00:00
// workspace views. Because the the workspace is not a view stored in the views map.
if parent_view_id = = workspace_id {
2023-05-31 09:42:14 +00:00
notify_did_update_workspace ( & workspace_id , folder )
2023-04-04 00:41:16 +00:00
} else {
// Parent view can contain a list of child views. Currently, only get the first level
// child views.
let parent_view = folder . views . get_view ( parent_view_id ) ? ;
let mut child_views = folder . views . get_views_belong_to ( parent_view_id ) ;
child_views . retain ( | view | ! trash_ids . contains ( & view . id ) ) ;
event! ( Level ::DEBUG , child_views_count = child_views . len ( ) ) ;
// Post the notification
2023-05-10 11:43:32 +00:00
let parent_view_pb = view_pb_with_child_views ( parent_view , child_views ) ;
2023-06-20 15:48:34 +00:00
send_notification ( parent_view_id , FolderNotification ::DidUpdateView )
2023-04-04 00:41:16 +00:00
. payload ( parent_view_pb )
. send ( ) ;
}
}
None
}
2023-06-20 15:48:34 +00:00
pub enum ChildViewChangeReason {
DidCreateView ,
DidDeleteView ,
DidUpdateView ,
}
/// Notify the the list of parent view ids that its child views were changed.
#[ tracing::instrument(level = " debug " , skip_all) ]
fn notify_child_views_changed ( view_pb : ViewPB , reason : ChildViewChangeReason ) {
let parent_view_id = view_pb . parent_view_id . clone ( ) ;
let mut payload = ChildViewUpdatePB {
parent_view_id : view_pb . parent_view_id . clone ( ) ,
.. Default ::default ( )
} ;
match reason {
ChildViewChangeReason ::DidCreateView = > {
payload . create_child_views . push ( view_pb ) ;
} ,
ChildViewChangeReason ::DidDeleteView = > {
payload . delete_child_views . push ( view_pb . id ) ;
} ,
ChildViewChangeReason ::DidUpdateView = > {
payload . update_child_views . push ( view_pb ) ;
} ,
}
send_notification ( & parent_view_id , FolderNotification ::DidUpdateChildViews )
. payload ( payload )
. send ( ) ;
}
2023-04-04 00:41:16 +00:00
fn folder_not_init_error ( ) -> FlowyError {
2023-08-21 16:19:15 +00:00
FlowyError ::internal ( ) . with_context ( " Folder not initialized " )
2023-04-04 00:41:16 +00:00
}
#[ derive(Clone, Default) ]
2023-05-31 09:42:14 +00:00
pub struct MutexFolder ( Arc < Mutex < Option < Folder > > > ) ;
impl Deref for MutexFolder {
type Target = Arc < Mutex < 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-08-28 05:28:24 +00:00
pub enum FolderInitializeDataSource {
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
2023-08-28 05:28:24 +00:00
Cloud ( CollabRawData ) ,
2023-08-17 15:46:39 +00:00
/// If the user is new, we use the [DefaultFolderBuilder] to create the default folder.
2023-08-28 05:28:24 +00:00
FolderData ( FolderData ) ,
2023-07-14 05:37:13 +00:00
}
2023-08-17 15:46:39 +00:00
fn is_exist_in_local_disk ( user : & Arc < dyn FolderUser > , doc_id : & str ) -> FlowyResult < bool > {
let uid = user . user_id ( ) ? ;
if let Some ( collab_db ) = user . collab_db ( uid ) ? . upgrade ( ) {
let read_txn = collab_db . read_txn ( ) ;
Ok ( read_txn . is_exist ( uid , doc_id ) )
} else {
Ok ( false )
}
}
2023-08-21 16:19:15 +00:00
fn workspace_data_not_sync_error ( uid : i64 , workspace_id : & str ) -> FlowyError {
FlowyError ::from ( ErrorCode ::WorkspaceDataNotSync ) . with_payload ( UserFolderPB {
uid ,
workspace_id : workspace_id . to_string ( ) ,
} )
}