manager.rs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165
  1. use std::collections::HashSet;
  2. use std::ops::Deref;
  3. use std::sync::{Arc, Weak};
  4. use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
  5. use appflowy_integrate::{CollabPersistenceConfig, CollabType, RocksCollabDB};
  6. use collab::core::collab::{CollabRawData, MutexCollab};
  7. use collab::core::collab_state::SyncState;
  8. use collab_folder::core::{
  9. FavoritesInfo, Folder, FolderData, FolderNotify, TrashChange, TrashChangeReceiver, TrashInfo,
  10. View, ViewChange, ViewChangeReceiver, ViewLayout, ViewUpdate, Workspace,
  11. };
  12. use parking_lot::Mutex;
  13. use tokio_stream::wrappers::WatchStream;
  14. use tokio_stream::StreamExt;
  15. use tracing::{event, Level};
  16. use flowy_error::{ErrorCode, FlowyError, FlowyResult};
  17. use flowy_folder_deps::cloud::FolderCloudService;
  18. use crate::entities::icon::UpdateViewIconParams;
  19. use crate::entities::{
  20. view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, CreateViewParams,
  21. CreateWorkspaceParams, DeletedViewPB, FolderSnapshotPB, FolderSnapshotStatePB, FolderSyncStatePB,
  22. RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB, UpdateViewParams, ViewPB, WorkspacePB,
  23. };
  24. use crate::notification::{
  25. send_notification, send_workspace_notification, send_workspace_setting_notification,
  26. FolderNotification,
  27. };
  28. use crate::share::ImportParams;
  29. use crate::user_default::DefaultFolderBuilder;
  30. use crate::view_operation::{
  31. create_view, gen_view_id, FolderOperationHandler, FolderOperationHandlers,
  32. };
  33. /// [FolderUser] represents the user for folder.
  34. pub trait FolderUser: Send + Sync {
  35. fn user_id(&self) -> Result<i64, FlowyError>;
  36. fn token(&self) -> Result<Option<String>, FlowyError>;
  37. fn collab_db(&self, uid: i64) -> Result<Weak<RocksCollabDB>, FlowyError>;
  38. }
  39. pub struct FolderManager {
  40. mutex_folder: Arc<MutexFolder>,
  41. collab_builder: Arc<AppFlowyCollabBuilder>,
  42. user: Arc<dyn FolderUser>,
  43. operation_handlers: FolderOperationHandlers,
  44. cloud_service: Arc<dyn FolderCloudService>,
  45. }
  46. unsafe impl Send for FolderManager {}
  47. unsafe impl Sync for FolderManager {}
  48. impl FolderManager {
  49. pub async fn new(
  50. user: Arc<dyn FolderUser>,
  51. collab_builder: Arc<AppFlowyCollabBuilder>,
  52. operation_handlers: FolderOperationHandlers,
  53. cloud_service: Arc<dyn FolderCloudService>,
  54. ) -> FlowyResult<Self> {
  55. let mutex_folder = Arc::new(MutexFolder::default());
  56. let manager = Self {
  57. user,
  58. mutex_folder,
  59. collab_builder,
  60. operation_handlers,
  61. cloud_service,
  62. };
  63. Ok(manager)
  64. }
  65. pub async fn get_current_workspace(&self) -> FlowyResult<WorkspacePB> {
  66. self.with_folder(
  67. Err(FlowyError::internal().context("Folder is not initialized".to_string())),
  68. |folder| {
  69. let workspace_pb_from_workspace = |workspace: Workspace, folder: &Folder| {
  70. let views = get_workspace_view_pbs(&workspace.id, folder);
  71. let workspace: WorkspacePB = (workspace, views).into();
  72. Ok::<WorkspacePB, FlowyError>(workspace)
  73. };
  74. match folder.get_current_workspace() {
  75. None => {
  76. // The current workspace should always exist. If not, try to find the first workspace.
  77. // from the folder. Otherwise, return an error.
  78. let mut workspaces = folder.workspaces.get_all_workspaces();
  79. if workspaces.is_empty() {
  80. Err(FlowyError::record_not_found().context("Can not find the workspace"))
  81. } else {
  82. tracing::error!("Can't find the current workspace, use the first workspace");
  83. let workspace = workspaces.remove(0);
  84. folder.set_current_workspace(&workspace.id);
  85. workspace_pb_from_workspace(workspace, folder)
  86. }
  87. },
  88. Some(workspace) => workspace_pb_from_workspace(workspace, folder),
  89. }
  90. },
  91. )
  92. }
  93. /// Return a list of views of the current workspace.
  94. /// Only the first level of child views are included.
  95. pub async fn get_current_workspace_views(&self) -> FlowyResult<Vec<ViewPB>> {
  96. let workspace_id = self
  97. .mutex_folder
  98. .lock()
  99. .as_ref()
  100. .map(|folder| folder.get_current_workspace_id());
  101. if let Some(Some(workspace_id)) = workspace_id {
  102. self.get_workspace_views(&workspace_id).await
  103. } else {
  104. tracing::warn!("Can't get current workspace views");
  105. Ok(vec![])
  106. }
  107. }
  108. pub async fn get_workspace_views(&self, workspace_id: &str) -> FlowyResult<Vec<ViewPB>> {
  109. let views = self.with_folder(vec![], |folder| {
  110. get_workspace_view_pbs(workspace_id, folder)
  111. });
  112. Ok(views)
  113. }
  114. /// Called immediately after the application launched fi the user already sign in/sign up.
  115. #[tracing::instrument(level = "info", skip(self, initial_data), err)]
  116. pub async fn initialize(
  117. &self,
  118. uid: i64,
  119. workspace_id: &str,
  120. initial_data: FolderInitializeData,
  121. ) -> FlowyResult<()> {
  122. let workspace_id = workspace_id.to_string();
  123. if let Ok(collab_db) = self.user.collab_db(uid) {
  124. let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
  125. let (trash_tx, trash_rx) = tokio::sync::broadcast::channel(100);
  126. let folder_notifier = FolderNotify {
  127. view_change_tx: view_tx,
  128. trash_change_tx: trash_tx,
  129. };
  130. let folder = match initial_data {
  131. FolderInitializeData::Empty => {
  132. let collab = self.collab_for_folder(uid, &workspace_id, collab_db, vec![])?;
  133. Folder::open(collab, Some(folder_notifier))
  134. },
  135. FolderInitializeData::Raw(raw_data) => {
  136. let collab = self.collab_for_folder(uid, &workspace_id, collab_db, raw_data)?;
  137. Folder::open(collab, Some(folder_notifier))
  138. },
  139. FolderInitializeData::Data(folder_data) => {
  140. let collab = self.collab_for_folder(uid, &workspace_id, collab_db, vec![])?;
  141. Folder::create(collab, Some(folder_notifier), Some(folder_data))
  142. },
  143. };
  144. tracing::debug!("Current workspace_id: {}", workspace_id);
  145. let folder_state_rx = folder.subscribe_sync_state();
  146. *self.mutex_folder.lock() = Some(folder);
  147. let weak_mutex_folder = Arc::downgrade(&self.mutex_folder);
  148. subscribe_folder_sync_state_changed(
  149. workspace_id.clone(),
  150. folder_state_rx,
  151. &weak_mutex_folder,
  152. );
  153. subscribe_folder_snapshot_state_changed(workspace_id, &weak_mutex_folder);
  154. subscribe_folder_trash_changed(trash_rx, &weak_mutex_folder);
  155. subscribe_folder_view_changed(view_rx, &weak_mutex_folder);
  156. }
  157. Ok(())
  158. }
  159. fn collab_for_folder(
  160. &self,
  161. uid: i64,
  162. workspace_id: &str,
  163. collab_db: Weak<RocksCollabDB>,
  164. raw_data: CollabRawData,
  165. ) -> Result<Arc<MutexCollab>, FlowyError> {
  166. let collab = self.collab_builder.build_with_config(
  167. uid,
  168. workspace_id,
  169. CollabType::Folder,
  170. collab_db,
  171. raw_data,
  172. &CollabPersistenceConfig::new().enable_snapshot(true),
  173. )?;
  174. Ok(collab)
  175. }
  176. /// Initialize the folder with the given workspace id.
  177. /// Fetch the folder updates from the cloud service and initialize the folder.
  178. #[tracing::instrument(level = "debug", skip(self, user_id), err)]
  179. pub async fn initialize_with_workspace_id(
  180. &self,
  181. user_id: i64,
  182. workspace_id: &str,
  183. ) -> FlowyResult<()> {
  184. let folder_updates = self
  185. .cloud_service
  186. .get_folder_updates(workspace_id, user_id)
  187. .await?;
  188. tracing::trace!(
  189. "Get folder updates via {}, number of updates: {}",
  190. self.cloud_service.service_name(),
  191. folder_updates.len()
  192. );
  193. self
  194. .initialize(
  195. user_id,
  196. workspace_id,
  197. FolderInitializeData::Raw(folder_updates),
  198. )
  199. .await?;
  200. Ok(())
  201. }
  202. /// Initialize the folder for the new user.
  203. /// Using the [DefaultFolderBuilder] to create the default workspace for the new user.
  204. pub async fn initialize_with_new_user(
  205. &self,
  206. user_id: i64,
  207. _token: &str,
  208. is_new: bool,
  209. folder_data: Option<FolderData>,
  210. workspace_id: &str,
  211. ) -> FlowyResult<()> {
  212. // Create the default workspace if the user is new
  213. tracing::info!("initialize_when_sign_up: is_new: {}", is_new);
  214. if is_new {
  215. let folder_data = match folder_data {
  216. None => {
  217. DefaultFolderBuilder::build(
  218. self.user.user_id()?,
  219. workspace_id.to_string(),
  220. &self.operation_handlers,
  221. )
  222. .await
  223. },
  224. Some(folder_data) => folder_data,
  225. };
  226. self
  227. .initialize(
  228. user_id,
  229. workspace_id,
  230. FolderInitializeData::Data(folder_data),
  231. )
  232. .await?;
  233. } else {
  234. // The folder data is loaded through the [FolderCloudService]. If the cloud service in use is
  235. // [LocalServerFolderCloudServiceImpl], the folder data will be None because the Folder will load
  236. // the data directly from the disk. If any other cloud service is in use, the folder data will be loaded remotely.
  237. let folder_updates = self
  238. .cloud_service
  239. .get_folder_updates(workspace_id, user_id)
  240. .await?;
  241. if !folder_updates.is_empty() {
  242. tracing::trace!(
  243. "Get folder updates via {}",
  244. self.cloud_service.service_name()
  245. );
  246. }
  247. self
  248. .initialize(
  249. user_id,
  250. workspace_id,
  251. FolderInitializeData::Raw(folder_updates),
  252. )
  253. .await?;
  254. }
  255. Ok(())
  256. }
  257. /// Called when the current user logout
  258. ///
  259. pub async fn clear(&self, _user_id: i64) {}
  260. #[tracing::instrument(level = "info", skip_all, err)]
  261. pub async fn create_workspace(&self, params: CreateWorkspaceParams) -> FlowyResult<Workspace> {
  262. let workspace = self
  263. .cloud_service
  264. .create_workspace(self.user.user_id()?, &params.name)
  265. .await?;
  266. self.with_folder((), |folder| {
  267. folder.workspaces.create_workspace(workspace.clone());
  268. folder.set_current_workspace(&workspace.id);
  269. });
  270. let repeated_workspace = RepeatedWorkspacePB {
  271. items: vec![workspace.clone().into()],
  272. };
  273. send_workspace_notification(FolderNotification::DidCreateWorkspace, repeated_workspace);
  274. Ok(workspace)
  275. }
  276. #[tracing::instrument(level = "info", skip_all, err)]
  277. pub async fn open_workspace(&self, workspace_id: &str) -> FlowyResult<Workspace> {
  278. self.with_folder(Err(FlowyError::internal()), |folder| {
  279. let workspace = folder
  280. .workspaces
  281. .get_workspace(workspace_id)
  282. .ok_or_else(|| {
  283. FlowyError::record_not_found().context("Can't open not existing workspace")
  284. })?;
  285. folder.set_current_workspace(&workspace.id);
  286. Ok::<Workspace, FlowyError>(workspace)
  287. })
  288. }
  289. pub async fn get_workspace(&self, workspace_id: &str) -> Option<Workspace> {
  290. self.with_folder(None, |folder| folder.workspaces.get_workspace(workspace_id))
  291. }
  292. async fn get_current_workspace_id(&self) -> FlowyResult<String> {
  293. self
  294. .mutex_folder
  295. .lock()
  296. .as_ref()
  297. .and_then(|folder| folder.get_current_workspace_id())
  298. .ok_or(FlowyError::internal().context("Unexpected empty workspace id"))
  299. }
  300. fn with_folder<F, Output>(&self, default_value: Output, f: F) -> Output
  301. where
  302. F: FnOnce(&Folder) -> Output,
  303. {
  304. let folder = self.mutex_folder.lock();
  305. match &*folder {
  306. None => default_value,
  307. Some(folder) => f(folder),
  308. }
  309. }
  310. pub async fn get_all_workspaces(&self) -> Vec<Workspace> {
  311. self.with_folder(vec![], |folder| folder.workspaces.get_all_workspaces())
  312. }
  313. pub async fn create_view_with_params(&self, params: CreateViewParams) -> FlowyResult<View> {
  314. let view_layout: ViewLayout = params.layout.clone().into();
  315. let _workspace_id = self.get_current_workspace_id().await?;
  316. let handler = self.get_handler(&view_layout)?;
  317. let user_id = self.user.user_id()?;
  318. let meta = params.meta.clone();
  319. if meta.is_empty() && params.initial_data.is_empty() {
  320. tracing::trace!("Create view with build-in data");
  321. handler
  322. .create_built_in_view(user_id, &params.view_id, &params.name, view_layout.clone())
  323. .await?;
  324. } else {
  325. tracing::trace!("Create view with view data");
  326. handler
  327. .create_view_with_view_data(
  328. user_id,
  329. &params.view_id,
  330. &params.name,
  331. params.initial_data.clone(),
  332. view_layout.clone(),
  333. meta,
  334. )
  335. .await?;
  336. }
  337. let index = params.index;
  338. let view = create_view(params, view_layout);
  339. self.with_folder((), |folder| {
  340. folder.insert_view(view.clone(), index);
  341. });
  342. Ok(view)
  343. }
  344. /// The orphan view is meant to be a view that is not attached to any parent view. By default, this
  345. /// view will not be shown in the view list unless it is attached to a parent view that is shown in
  346. /// the view list.
  347. pub async fn create_orphan_view_with_params(
  348. &self,
  349. params: CreateViewParams,
  350. ) -> FlowyResult<View> {
  351. let view_layout: ViewLayout = params.layout.clone().into();
  352. let handler = self.get_handler(&view_layout)?;
  353. let user_id = self.user.user_id()?;
  354. handler
  355. .create_built_in_view(user_id, &params.view_id, &params.name, view_layout.clone())
  356. .await?;
  357. let view = create_view(params, view_layout);
  358. self.with_folder((), |folder| {
  359. folder.insert_view(view.clone(), None);
  360. });
  361. Ok(view)
  362. }
  363. #[tracing::instrument(level = "debug", skip(self), err)]
  364. pub(crate) async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> {
  365. if let Some(view) = self.with_folder(None, |folder| folder.views.get_view(view_id)) {
  366. let handler = self.get_handler(&view.layout)?;
  367. handler.close_view(view_id).await?;
  368. }
  369. Ok(())
  370. }
  371. /// Returns the view with the given view id.
  372. /// The child views of the view will only access the first. So if you want to get the child view's
  373. /// child view, you need to call this method again.
  374. #[tracing::instrument(level = "debug", skip(self, view_id), err)]
  375. pub async fn get_view(&self, view_id: &str) -> FlowyResult<ViewPB> {
  376. let view_id = view_id.to_string();
  377. let folder = self.mutex_folder.lock();
  378. let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
  379. let trash_ids = folder
  380. .get_all_trash()
  381. .into_iter()
  382. .map(|trash| trash.id)
  383. .collect::<Vec<String>>();
  384. if trash_ids.contains(&view_id) {
  385. return Err(FlowyError::record_not_found());
  386. }
  387. match folder.views.get_view(&view_id) {
  388. None => Err(FlowyError::record_not_found()),
  389. Some(view) => {
  390. let child_views = folder
  391. .views
  392. .get_views_belong_to(&view.id)
  393. .into_iter()
  394. .filter(|view| !trash_ids.contains(&view.id))
  395. .collect::<Vec<_>>();
  396. let view_pb = view_pb_with_child_views(view, child_views);
  397. Ok(view_pb)
  398. },
  399. }
  400. }
  401. /// Move the view to trash. If the view is the current view, then set the current view to empty.
  402. /// When the view is moved to trash, all the child views will be moved to trash as well.
  403. /// 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()`
  404. #[tracing::instrument(level = "debug", skip(self), err)]
  405. pub async fn move_view_to_trash(&self, view_id: &str) -> FlowyResult<()> {
  406. self.with_folder((), |folder| {
  407. if let Some(view) = folder.views.get_view(view_id) {
  408. self.unfavorite_view_and_decendants(view.clone(), folder);
  409. folder.add_trash(vec![view_id.to_string()]);
  410. // notify the parent view that the view is moved to trash
  411. send_notification(view_id, FolderNotification::DidMoveViewToTrash)
  412. .payload(DeletedViewPB {
  413. view_id: view_id.to_string(),
  414. index: None,
  415. })
  416. .send();
  417. notify_child_views_changed(
  418. view_pb_without_child_views(view),
  419. ChildViewChangeReason::DidDeleteView,
  420. );
  421. }
  422. });
  423. Ok(())
  424. }
  425. fn unfavorite_view_and_decendants(&self, view: Arc<View>, folder: &Folder) {
  426. let mut all_descendant_views: Vec<Arc<View>> = vec![view.clone()];
  427. all_descendant_views.extend(folder.views.get_views_belong_to(&view.id));
  428. let favorite_descendant_views: Vec<ViewPB> = all_descendant_views
  429. .iter()
  430. .filter(|view| view.is_favorite)
  431. .map(|view| view_pb_without_child_views(view.clone()))
  432. .collect();
  433. if !favorite_descendant_views.is_empty() {
  434. folder.delete_favorites(
  435. favorite_descendant_views
  436. .iter()
  437. .map(|v| v.id.clone())
  438. .collect(),
  439. );
  440. send_notification("favorite", FolderNotification::DidUnfavoriteView)
  441. .payload(RepeatedViewPB {
  442. items: favorite_descendant_views,
  443. })
  444. .send();
  445. }
  446. }
  447. /// Moves a nested view to a new location in the hierarchy.
  448. ///
  449. /// This function takes the `view_id` of the view to be moved,
  450. /// `new_parent_id` of the view under which the `view_id` should be moved,
  451. /// and an optional `prev_view_id` to position the `view_id` right after
  452. /// this specific view.
  453. ///
  454. /// If `prev_view_id` is provided, the moved view will be placed right after
  455. /// the view corresponding to `prev_view_id` under the `new_parent_id`.
  456. /// If `prev_view_id` is `None`, the moved view will become the first child of the new parent.
  457. ///
  458. /// # Arguments
  459. ///
  460. /// * `view_id` - A string slice that holds the id of the view to be moved.
  461. /// * `new_parent_id` - A string slice that holds the id of the new parent view.
  462. /// * `prev_view_id` - An `Option<String>` that holds the id of the view after which the `view_id` should be positioned.
  463. ///
  464. #[tracing::instrument(level = "trace", skip(self), err)]
  465. pub async fn move_nested_view(
  466. &self,
  467. view_id: String,
  468. new_parent_id: String,
  469. prev_view_id: Option<String>,
  470. ) -> FlowyResult<()> {
  471. let view = self.get_view(&view_id).await?;
  472. let old_parent_id = view.parent_view_id;
  473. self.with_folder((), |folder| {
  474. folder.move_nested_view(&view_id, &new_parent_id, prev_view_id);
  475. });
  476. notify_parent_view_did_change(
  477. self.mutex_folder.clone(),
  478. vec![new_parent_id, old_parent_id],
  479. );
  480. Ok(())
  481. }
  482. /// Move the view with given id from one position to another position.
  483. /// The view will be moved to the new position in the same parent view.
  484. /// The passed in index is the index of the view that displayed in the UI.
  485. /// We need to convert the index to the real index of the view in the parent view.
  486. #[tracing::instrument(level = "trace", skip(self), err)]
  487. pub async fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> {
  488. if let Some((is_workspace, parent_view_id, child_views)) = self.get_view_relation(view_id).await
  489. {
  490. // The display parent view is the view that is displayed in the UI
  491. let display_views = if is_workspace {
  492. self
  493. .get_current_workspace()
  494. .await?
  495. .views
  496. .into_iter()
  497. .map(|view| view.id)
  498. .collect::<Vec<_>>()
  499. } else {
  500. self
  501. .get_view(&parent_view_id)
  502. .await?
  503. .child_views
  504. .into_iter()
  505. .map(|view| view.id)
  506. .collect::<Vec<_>>()
  507. };
  508. if display_views.len() > to {
  509. let to_view_id = display_views[to].clone();
  510. // Find the actual index of the view in the parent view
  511. let actual_from_index = child_views.iter().position(|id| id == view_id);
  512. let actual_to_index = child_views.iter().position(|id| id == &to_view_id);
  513. if let (Some(actual_from_index), Some(actual_to_index)) =
  514. (actual_from_index, actual_to_index)
  515. {
  516. self.with_folder((), |folder| {
  517. folder.move_view(view_id, actual_from_index as u32, actual_to_index as u32);
  518. });
  519. notify_parent_view_did_change(self.mutex_folder.clone(), vec![parent_view_id]);
  520. }
  521. }
  522. }
  523. Ok(())
  524. }
  525. /// Return a list of views that belong to the given parent view id.
  526. #[tracing::instrument(level = "debug", skip(self, parent_view_id), err)]
  527. pub async fn get_views_belong_to(&self, parent_view_id: &str) -> FlowyResult<Vec<Arc<View>>> {
  528. let views = self.with_folder(vec![], |folder| {
  529. folder.views.get_views_belong_to(parent_view_id)
  530. });
  531. Ok(views)
  532. }
  533. /// Update the view with the given params.
  534. #[tracing::instrument(level = "trace", skip(self), err)]
  535. pub async fn update_view_with_params(&self, params: UpdateViewParams) -> FlowyResult<()> {
  536. self
  537. .update_view(&params.view_id, |update| {
  538. update
  539. .set_name_if_not_none(params.name)
  540. .set_desc_if_not_none(params.desc)
  541. .set_layout_if_not_none(params.layout)
  542. .set_favorite_if_not_none(params.is_favorite)
  543. .done()
  544. })
  545. .await
  546. }
  547. /// Update the icon of the view with the given params.
  548. #[tracing::instrument(level = "trace", skip(self), err)]
  549. pub async fn update_view_icon_with_params(
  550. &self,
  551. params: UpdateViewIconParams,
  552. ) -> FlowyResult<()> {
  553. self
  554. .update_view(&params.view_id, |update| {
  555. update.set_icon(params.icon).done()
  556. })
  557. .await
  558. }
  559. /// Duplicate the view with the given view id.
  560. #[tracing::instrument(level = "debug", skip(self), err)]
  561. pub(crate) async fn duplicate_view(&self, view_id: &str) -> Result<(), FlowyError> {
  562. let view = self
  563. .with_folder(None, |folder| folder.views.get_view(view_id))
  564. .ok_or_else(|| FlowyError::record_not_found().context("Can't duplicate the view"))?;
  565. let handler = self.get_handler(&view.layout)?;
  566. let view_data = handler.duplicate_view(&view.id).await?;
  567. // get the current view index in the parent view, because we need to insert the duplicated view below the current view.
  568. let index = if let Some((_, __, views)) = self.get_view_relation(&view.parent_view_id).await {
  569. views.iter().position(|id| id == view_id).map(|i| i as u32)
  570. } else {
  571. None
  572. };
  573. let duplicate_params = CreateViewParams {
  574. parent_view_id: view.parent_view_id.clone(),
  575. name: format!("{} (copy)", &view.name),
  576. desc: view.desc.clone(),
  577. layout: view.layout.clone().into(),
  578. initial_data: view_data.to_vec(),
  579. view_id: gen_view_id(),
  580. meta: Default::default(),
  581. set_as_current: true,
  582. index,
  583. };
  584. self.create_view_with_params(duplicate_params).await?;
  585. Ok(())
  586. }
  587. #[tracing::instrument(level = "trace", skip(self), err)]
  588. pub(crate) async fn set_current_view(&self, view_id: &str) -> Result<(), FlowyError> {
  589. let folder = self.mutex_folder.lock();
  590. let folder = folder.as_ref().ok_or_else(folder_not_init_error)?;
  591. folder.set_current_view(view_id);
  592. let workspace = folder.get_current_workspace();
  593. let view = folder
  594. .get_current_view()
  595. .and_then(|view_id| folder.views.get_view(&view_id));
  596. send_workspace_setting_notification(workspace, view);
  597. Ok(())
  598. }
  599. #[tracing::instrument(level = "trace", skip(self))]
  600. pub(crate) async fn get_current_view(&self) -> Option<ViewPB> {
  601. let view_id = self.with_folder(None, |folder| folder.get_current_view())?;
  602. self.get_view(&view_id).await.ok()
  603. }
  604. /// 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.
  605. #[tracing::instrument(level = "debug", skip(self), err)]
  606. pub async fn toggle_favorites(&self, view_id: &str) -> FlowyResult<()> {
  607. self.with_folder((), |folder| {
  608. if let Some(old_view) = folder.views.get_view(view_id) {
  609. if old_view.is_favorite {
  610. folder.delete_favorites(vec![view_id.to_string()]);
  611. } else {
  612. folder.add_favorites(vec![view_id.to_string()]);
  613. }
  614. }
  615. });
  616. self.send_toggle_favorite_notification(view_id).await;
  617. Ok(())
  618. }
  619. // 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.
  620. async fn send_toggle_favorite_notification(&self, view_id: &str) {
  621. if let Ok(view) = self.get_view(view_id).await {
  622. let notification_type = if view.is_favorite {
  623. FolderNotification::DidFavoriteView
  624. } else {
  625. FolderNotification::DidUnfavoriteView
  626. };
  627. send_notification("favorite", notification_type)
  628. .payload(RepeatedViewPB {
  629. items: vec![view.clone()],
  630. })
  631. .send();
  632. send_notification(&view.id, FolderNotification::DidUpdateView)
  633. .payload(view)
  634. .send()
  635. }
  636. }
  637. #[tracing::instrument(level = "trace", skip(self))]
  638. pub(crate) async fn get_all_favorites(&self) -> Vec<FavoritesInfo> {
  639. self.with_folder(vec![], |folder| {
  640. let trash_ids = folder
  641. .get_all_trash()
  642. .into_iter()
  643. .map(|trash| trash.id)
  644. .collect::<Vec<String>>();
  645. let mut views = folder.get_all_favorites();
  646. views.retain(|view| !trash_ids.contains(&view.id));
  647. views
  648. })
  649. }
  650. #[tracing::instrument(level = "trace", skip(self))]
  651. pub(crate) async fn get_all_trash(&self) -> Vec<TrashInfo> {
  652. self.with_folder(vec![], |folder| folder.get_all_trash())
  653. }
  654. #[tracing::instrument(level = "trace", skip(self))]
  655. pub(crate) async fn restore_all_trash(&self) {
  656. self.with_folder((), |folder| {
  657. folder.remote_all_trash();
  658. });
  659. send_notification("trash", FolderNotification::DidUpdateTrash)
  660. .payload(RepeatedTrashPB { items: vec![] })
  661. .send();
  662. }
  663. #[tracing::instrument(level = "trace", skip(self))]
  664. pub(crate) async fn restore_trash(&self, trash_id: &str) {
  665. self.with_folder((), |folder| {
  666. folder.delete_trash(vec![trash_id.to_string()]);
  667. });
  668. }
  669. /// Delete all the trash permanently.
  670. #[tracing::instrument(level = "trace", skip(self))]
  671. pub(crate) async fn delete_all_trash(&self) {
  672. let deleted_trash = self.with_folder(vec![], |folder| folder.get_all_trash());
  673. for trash in deleted_trash {
  674. let _ = self.delete_trash(&trash.id).await;
  675. }
  676. send_notification("trash", FolderNotification::DidUpdateTrash)
  677. .payload(RepeatedTrashPB { items: vec![] })
  678. .send();
  679. }
  680. /// Delete the trash permanently.
  681. /// Delete the view will delete all the resources that the view holds. For example, if the view
  682. /// is a database view. Then the database will be deleted as well.
  683. #[tracing::instrument(level = "debug", skip(self, view_id), err)]
  684. pub async fn delete_trash(&self, view_id: &str) -> FlowyResult<()> {
  685. let view = self.with_folder(None, |folder| folder.views.get_view(view_id));
  686. self.with_folder((), |folder| {
  687. folder.delete_trash(vec![view_id.to_string()]);
  688. folder.views.delete_views(vec![view_id]);
  689. });
  690. if let Some(view) = view {
  691. if let Ok(handler) = self.get_handler(&view.layout) {
  692. handler.delete_view(view_id).await?;
  693. }
  694. }
  695. Ok(())
  696. }
  697. pub(crate) async fn import(&self, import_data: ImportParams) -> FlowyResult<View> {
  698. if import_data.data.is_none() && import_data.file_path.is_none() {
  699. return Err(FlowyError::new(
  700. ErrorCode::InvalidParams,
  701. "data or file_path is required",
  702. ));
  703. }
  704. let handler = self.get_handler(&import_data.view_layout)?;
  705. let view_id = gen_view_id();
  706. if let Some(data) = import_data.data {
  707. handler
  708. .import_from_bytes(&view_id, &import_data.name, import_data.import_type, data)
  709. .await?;
  710. }
  711. if let Some(file_path) = import_data.file_path {
  712. handler
  713. .import_from_file_path(&view_id, &import_data.name, file_path)
  714. .await?;
  715. }
  716. let params = CreateViewParams {
  717. parent_view_id: import_data.parent_view_id,
  718. name: import_data.name,
  719. desc: "".to_string(),
  720. layout: import_data.view_layout.clone().into(),
  721. initial_data: vec![],
  722. view_id,
  723. meta: Default::default(),
  724. set_as_current: false,
  725. index: None,
  726. };
  727. let view = create_view(params, import_data.view_layout);
  728. self.with_folder((), |folder| {
  729. folder.insert_view(view.clone(), None);
  730. });
  731. notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]);
  732. Ok(view)
  733. }
  734. /// Update the view with the provided view_id using the specified function.
  735. async fn update_view<F>(&self, view_id: &str, f: F) -> FlowyResult<()>
  736. where
  737. F: FnOnce(ViewUpdate) -> Option<View>,
  738. {
  739. let value = self.with_folder(None, |folder| {
  740. let old_view = folder.views.get_view(view_id);
  741. let new_view = folder.views.update_view(view_id, f);
  742. Some((old_view, new_view))
  743. });
  744. if let Some((Some(old_view), Some(new_view))) = value {
  745. if let Ok(handler) = self.get_handler(&old_view.layout) {
  746. handler.did_update_view(&old_view, &new_view).await?;
  747. }
  748. }
  749. if let Ok(view_pb) = self.get_view(view_id).await {
  750. send_notification(&view_pb.id, FolderNotification::DidUpdateView)
  751. .payload(view_pb)
  752. .send();
  753. }
  754. Ok(())
  755. }
  756. /// Returns a handler that implements the [FolderOperationHandler] trait
  757. fn get_handler(
  758. &self,
  759. view_layout: &ViewLayout,
  760. ) -> FlowyResult<Arc<dyn FolderOperationHandler + Send + Sync>> {
  761. match self.operation_handlers.get(view_layout) {
  762. None => Err(FlowyError::internal().context(format!(
  763. "Get data processor failed. Unknown layout type: {:?}",
  764. view_layout
  765. ))),
  766. Some(processor) => Ok(processor.clone()),
  767. }
  768. }
  769. /// Returns the relation of the view. The relation is a tuple of (is_workspace, parent_view_id,
  770. /// child_view_ids). If the view is a workspace, then the parent_view_id is the workspace id.
  771. /// Otherwise, the parent_view_id is the parent view id of the view. The child_view_ids is the
  772. /// child view ids of the view.
  773. async fn get_view_relation(&self, view_id: &str) -> Option<(bool, String, Vec<String>)> {
  774. self.with_folder(None, |folder| {
  775. let view = folder.views.get_view(view_id)?;
  776. match folder.views.get_view(&view.parent_view_id) {
  777. None => folder.get_current_workspace().map(|workspace| {
  778. (
  779. true,
  780. workspace.id,
  781. workspace
  782. .child_views
  783. .items
  784. .into_iter()
  785. .map(|view| view.id)
  786. .collect::<Vec<String>>(),
  787. )
  788. }),
  789. Some(parent_view) => Some((
  790. false,
  791. parent_view.id.clone(),
  792. parent_view
  793. .children
  794. .items
  795. .clone()
  796. .into_iter()
  797. .map(|view| view.id)
  798. .collect::<Vec<String>>(),
  799. )),
  800. }
  801. })
  802. }
  803. pub async fn get_folder_snapshots(
  804. &self,
  805. workspace_id: &str,
  806. ) -> FlowyResult<Vec<FolderSnapshotPB>> {
  807. let mut snapshots = vec![];
  808. if let Some(snapshot) = self
  809. .cloud_service
  810. .get_folder_latest_snapshot(workspace_id)
  811. .await?
  812. .map(|snapshot| FolderSnapshotPB {
  813. snapshot_id: snapshot.snapshot_id,
  814. snapshot_desc: "".to_string(),
  815. created_at: snapshot.created_at,
  816. data: snapshot.data,
  817. })
  818. {
  819. snapshots.push(snapshot);
  820. }
  821. Ok(snapshots)
  822. }
  823. /// Only expose this method for testing
  824. #[cfg(debug_assertions)]
  825. pub fn get_mutex_folder(&self) -> &Arc<MutexFolder> {
  826. &self.mutex_folder
  827. }
  828. /// Only expose this method for testing
  829. #[cfg(debug_assertions)]
  830. pub fn get_cloud_service(&self) -> &Arc<dyn FolderCloudService> {
  831. &self.cloud_service
  832. }
  833. }
  834. /// Listen on the [ViewChange] after create/delete/update events happened
  835. fn subscribe_folder_view_changed(
  836. mut rx: ViewChangeReceiver,
  837. weak_mutex_folder: &Weak<MutexFolder>,
  838. ) {
  839. let weak_mutex_folder = weak_mutex_folder.clone();
  840. tokio::spawn(async move {
  841. while let Ok(value) = rx.recv().await {
  842. if let Some(folder) = weak_mutex_folder.upgrade() {
  843. tracing::trace!("Did receive view change: {:?}", value);
  844. match value {
  845. ViewChange::DidCreateView { view } => {
  846. notify_child_views_changed(
  847. view_pb_without_child_views(Arc::new(view.clone())),
  848. ChildViewChangeReason::DidCreateView,
  849. );
  850. notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
  851. },
  852. ViewChange::DidDeleteView { views } => {
  853. for view in views {
  854. notify_child_views_changed(
  855. view_pb_without_child_views(view),
  856. ChildViewChangeReason::DidDeleteView,
  857. );
  858. }
  859. },
  860. ViewChange::DidUpdate { view } => {
  861. notify_child_views_changed(
  862. view_pb_without_child_views(Arc::new(view.clone())),
  863. ChildViewChangeReason::DidUpdateView,
  864. );
  865. notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
  866. },
  867. };
  868. }
  869. }
  870. });
  871. }
  872. fn subscribe_folder_snapshot_state_changed(
  873. workspace_id: String,
  874. weak_mutex_folder: &Weak<MutexFolder>,
  875. ) {
  876. let weak_mutex_folder = weak_mutex_folder.clone();
  877. tokio::spawn(async move {
  878. if let Some(mutex_folder) = weak_mutex_folder.upgrade() {
  879. let stream = mutex_folder
  880. .lock()
  881. .as_ref()
  882. .map(|folder| folder.subscribe_snapshot_state());
  883. if let Some(mut state_stream) = stream {
  884. while let Some(snapshot_state) = state_stream.next().await {
  885. if let Some(new_snapshot_id) = snapshot_state.snapshot_id() {
  886. tracing::debug!("Did create folder remote snapshot: {}", new_snapshot_id);
  887. send_notification(
  888. &workspace_id,
  889. FolderNotification::DidUpdateFolderSnapshotState,
  890. )
  891. .payload(FolderSnapshotStatePB { new_snapshot_id })
  892. .send();
  893. }
  894. }
  895. }
  896. }
  897. });
  898. }
  899. fn subscribe_folder_sync_state_changed(
  900. workspace_id: String,
  901. mut folder_sync_state_rx: WatchStream<SyncState>,
  902. _weak_mutex_folder: &Weak<MutexFolder>,
  903. ) {
  904. tokio::spawn(async move {
  905. while let Some(state) = folder_sync_state_rx.next().await {
  906. send_notification(&workspace_id, FolderNotification::DidUpdateFolderSyncUpdate)
  907. .payload(FolderSyncStatePB::from(state))
  908. .send();
  909. }
  910. });
  911. }
  912. /// Listen on the [TrashChange]s and notify the frontend some views were changed.
  913. fn subscribe_folder_trash_changed(
  914. mut rx: TrashChangeReceiver,
  915. weak_mutex_folder: &Weak<MutexFolder>,
  916. ) {
  917. let weak_mutex_folder = weak_mutex_folder.clone();
  918. tokio::spawn(async move {
  919. while let Ok(value) = rx.recv().await {
  920. if let Some(folder) = weak_mutex_folder.upgrade() {
  921. let mut unique_ids = HashSet::new();
  922. tracing::trace!("Did receive trash change: {:?}", value);
  923. let ids = match value {
  924. TrashChange::DidCreateTrash { ids } => ids,
  925. TrashChange::DidDeleteTrash { ids } => ids,
  926. };
  927. if let Some(folder) = folder.lock().as_ref() {
  928. let views = folder.views.get_views(&ids);
  929. for view in views {
  930. unique_ids.insert(view.parent_view_id.clone());
  931. }
  932. let repeated_trash: RepeatedTrashPB = folder.get_all_trash().into();
  933. send_notification("trash", FolderNotification::DidUpdateTrash)
  934. .payload(repeated_trash)
  935. .send();
  936. }
  937. let parent_view_ids = unique_ids.into_iter().collect();
  938. notify_parent_view_did_change(folder.clone(), parent_view_ids);
  939. }
  940. }
  941. });
  942. }
  943. /// Return the views that belong to the workspace. The views are filtered by the trash.
  944. fn get_workspace_view_pbs(workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
  945. let trash_ids = folder
  946. .get_all_trash()
  947. .into_iter()
  948. .map(|trash| trash.id)
  949. .collect::<Vec<String>>();
  950. let mut views = folder.get_workspace_views(workspace_id);
  951. views.retain(|view| !trash_ids.contains(&view.id));
  952. views
  953. .into_iter()
  954. .map(|view| {
  955. // Get child views
  956. let child_views = folder
  957. .views
  958. .get_views_belong_to(&view.id)
  959. .into_iter()
  960. .collect();
  961. view_pb_with_child_views(view, child_views)
  962. })
  963. .collect()
  964. }
  965. fn notify_did_update_workspace(workspace_id: &str, folder: &Folder) {
  966. let repeated_view: RepeatedViewPB = get_workspace_view_pbs(workspace_id, folder).into();
  967. tracing::trace!("Did update workspace views: {:?}", repeated_view);
  968. send_notification(workspace_id, FolderNotification::DidUpdateWorkspaceViews)
  969. .payload(repeated_view)
  970. .send();
  971. }
  972. /// Notify the the list of parent view ids that its child views were changed.
  973. #[tracing::instrument(level = "debug", skip(folder, parent_view_ids))]
  974. fn notify_parent_view_did_change<T: AsRef<str>>(
  975. folder: Arc<MutexFolder>,
  976. parent_view_ids: Vec<T>,
  977. ) -> Option<()> {
  978. let folder = folder.lock();
  979. let folder = folder.as_ref()?;
  980. let workspace_id = folder.get_current_workspace_id()?;
  981. let trash_ids = folder
  982. .get_all_trash()
  983. .into_iter()
  984. .map(|trash| trash.id)
  985. .collect::<Vec<String>>();
  986. for parent_view_id in parent_view_ids {
  987. let parent_view_id = parent_view_id.as_ref();
  988. // if the view's parent id equal to workspace id. Then it will fetch the current
  989. // workspace views. Because the the workspace is not a view stored in the views map.
  990. if parent_view_id == workspace_id {
  991. notify_did_update_workspace(&workspace_id, folder)
  992. } else {
  993. // Parent view can contain a list of child views. Currently, only get the first level
  994. // child views.
  995. let parent_view = folder.views.get_view(parent_view_id)?;
  996. let mut child_views = folder.views.get_views_belong_to(parent_view_id);
  997. child_views.retain(|view| !trash_ids.contains(&view.id));
  998. event!(Level::DEBUG, child_views_count = child_views.len());
  999. // Post the notification
  1000. let parent_view_pb = view_pb_with_child_views(parent_view, child_views);
  1001. send_notification(parent_view_id, FolderNotification::DidUpdateView)
  1002. .payload(parent_view_pb)
  1003. .send();
  1004. }
  1005. }
  1006. None
  1007. }
  1008. pub enum ChildViewChangeReason {
  1009. DidCreateView,
  1010. DidDeleteView,
  1011. DidUpdateView,
  1012. }
  1013. /// Notify the the list of parent view ids that its child views were changed.
  1014. #[tracing::instrument(level = "debug", skip_all)]
  1015. fn notify_child_views_changed(view_pb: ViewPB, reason: ChildViewChangeReason) {
  1016. let parent_view_id = view_pb.parent_view_id.clone();
  1017. let mut payload = ChildViewUpdatePB {
  1018. parent_view_id: view_pb.parent_view_id.clone(),
  1019. ..Default::default()
  1020. };
  1021. match reason {
  1022. ChildViewChangeReason::DidCreateView => {
  1023. payload.create_child_views.push(view_pb);
  1024. },
  1025. ChildViewChangeReason::DidDeleteView => {
  1026. payload.delete_child_views.push(view_pb.id);
  1027. },
  1028. ChildViewChangeReason::DidUpdateView => {
  1029. payload.update_child_views.push(view_pb);
  1030. },
  1031. }
  1032. send_notification(&parent_view_id, FolderNotification::DidUpdateChildViews)
  1033. .payload(payload)
  1034. .send();
  1035. }
  1036. fn folder_not_init_error() -> FlowyError {
  1037. FlowyError::internal().context("Folder not initialized")
  1038. }
  1039. #[derive(Clone, Default)]
  1040. pub struct MutexFolder(Arc<Mutex<Option<Folder>>>);
  1041. impl Deref for MutexFolder {
  1042. type Target = Arc<Mutex<Option<Folder>>>;
  1043. fn deref(&self) -> &Self::Target {
  1044. &self.0
  1045. }
  1046. }
  1047. unsafe impl Sync for MutexFolder {}
  1048. unsafe impl Send for MutexFolder {}
  1049. pub enum FolderInitializeData {
  1050. Empty,
  1051. Raw(CollabRawData),
  1052. Data(FolderData),
  1053. }