folder_pad.rs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. use crate::errors::internal_error;
  2. use crate::util::cal_diff;
  3. use crate::{
  4. client_folder::builder::FolderPadBuilder,
  5. entities::{
  6. folder::FolderDelta,
  7. revision::{md5, Revision},
  8. },
  9. errors::{CollaborateError, CollaborateResult},
  10. };
  11. use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
  12. use lib_infra::util::move_vec_element;
  13. use lib_ot::core::*;
  14. use serde::{Deserialize, Serialize};
  15. use std::sync::Arc;
  16. #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
  17. pub struct FolderPad {
  18. pub(crate) workspaces: Vec<Arc<WorkspaceRevision>>,
  19. pub(crate) trash: Vec<Arc<TrashRevision>>,
  20. #[serde(skip)]
  21. pub(crate) delta: FolderDelta,
  22. }
  23. impl FolderPad {
  24. pub fn new(workspaces: Vec<WorkspaceRevision>, trash: Vec<TrashRevision>) -> CollaborateResult<Self> {
  25. FolderPadBuilder::new()
  26. .with_workspace(workspaces)
  27. .with_trash(trash)
  28. .build()
  29. }
  30. pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
  31. FolderPadBuilder::new().build_with_revisions(revisions)
  32. }
  33. pub fn from_delta(delta: FolderDelta) -> CollaborateResult<Self> {
  34. FolderPadBuilder::new().build_with_delta(delta)
  35. }
  36. pub fn delta(&self) -> &FolderDelta {
  37. &self.delta
  38. }
  39. pub fn reset_folder(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
  40. let folder = FolderPad::from_delta(delta)?;
  41. self.workspaces = folder.workspaces;
  42. self.trash = folder.trash;
  43. self.delta = folder.delta;
  44. Ok(self.md5())
  45. }
  46. pub fn compose_remote_delta(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
  47. let composed_delta = self.delta.compose(&delta)?;
  48. self.reset_folder(composed_delta)
  49. }
  50. pub fn is_empty(&self) -> bool {
  51. self.workspaces.is_empty() && self.trash.is_empty()
  52. }
  53. #[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)]
  54. pub fn create_workspace(&mut self, workspace_rev: WorkspaceRevision) -> CollaborateResult<Option<FolderChange>> {
  55. let workspace = Arc::new(workspace_rev);
  56. if self.workspaces.contains(&workspace) {
  57. tracing::warn!("[RootFolder]: Duplicate workspace");
  58. return Ok(None);
  59. }
  60. self.modify_workspaces(move |workspaces| {
  61. workspaces.push(workspace);
  62. Ok(Some(()))
  63. })
  64. }
  65. pub fn update_workspace(
  66. &mut self,
  67. workspace_id: &str,
  68. name: Option<String>,
  69. desc: Option<String>,
  70. ) -> CollaborateResult<Option<FolderChange>> {
  71. self.with_workspace(workspace_id, |workspace| {
  72. if let Some(name) = name {
  73. workspace.name = name;
  74. }
  75. if let Some(desc) = desc {
  76. workspace.desc = desc;
  77. }
  78. Ok(Some(()))
  79. })
  80. }
  81. pub fn read_workspaces(&self, workspace_id: Option<String>) -> CollaborateResult<Vec<WorkspaceRevision>> {
  82. match workspace_id {
  83. None => {
  84. let workspaces = self
  85. .workspaces
  86. .iter()
  87. .map(|workspace| workspace.as_ref().clone())
  88. .collect::<Vec<WorkspaceRevision>>();
  89. Ok(workspaces)
  90. }
  91. Some(workspace_id) => {
  92. if let Some(workspace) = self.workspaces.iter().find(|workspace| workspace.id == workspace_id) {
  93. Ok(vec![workspace.as_ref().clone()])
  94. } else {
  95. Err(CollaborateError::record_not_found()
  96. .context(format!("Can't find workspace with id {}", workspace_id)))
  97. }
  98. }
  99. }
  100. }
  101. #[tracing::instrument(level = "trace", skip(self), err)]
  102. pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult<Option<FolderChange>> {
  103. self.modify_workspaces(|workspaces| {
  104. workspaces.retain(|w| w.id != workspace_id);
  105. Ok(Some(()))
  106. })
  107. }
  108. #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app_rev.name), err)]
  109. pub fn create_app(&mut self, app_rev: AppRevision) -> CollaborateResult<Option<FolderChange>> {
  110. let workspace_id = app_rev.workspace_id.clone();
  111. self.with_workspace(&workspace_id, move |workspace| {
  112. if workspace.apps.contains(&app_rev) {
  113. tracing::warn!("[RootFolder]: Duplicate app");
  114. return Ok(None);
  115. }
  116. workspace.apps.push(app_rev);
  117. Ok(Some(()))
  118. })
  119. }
  120. pub fn read_app(&self, app_id: &str) -> CollaborateResult<AppRevision> {
  121. for workspace in &self.workspaces {
  122. if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) {
  123. return Ok(app.clone());
  124. }
  125. }
  126. Err(CollaborateError::record_not_found().context(format!("Can't find app with id {}", app_id)))
  127. }
  128. pub fn update_app(
  129. &mut self,
  130. app_id: &str,
  131. name: Option<String>,
  132. desc: Option<String>,
  133. ) -> CollaborateResult<Option<FolderChange>> {
  134. self.with_app(app_id, move |app| {
  135. if let Some(name) = name {
  136. app.name = name;
  137. }
  138. if let Some(desc) = desc {
  139. app.desc = desc;
  140. }
  141. Ok(Some(()))
  142. })
  143. }
  144. #[tracing::instrument(level = "trace", skip(self), err)]
  145. pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult<Option<FolderChange>> {
  146. let app = self.read_app(app_id)?;
  147. self.with_workspace(&app.workspace_id, |workspace| {
  148. workspace.apps.retain(|app| app.id != app_id);
  149. Ok(Some(()))
  150. })
  151. }
  152. #[tracing::instrument(level = "trace", skip(self), err)]
  153. pub fn move_app(&mut self, app_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
  154. let app = self.read_app(app_id)?;
  155. self.with_workspace(&app.workspace_id, |workspace| {
  156. match move_vec_element(&mut workspace.apps, |app| app.id == app_id, from, to).map_err(internal_error)? {
  157. true => Ok(Some(())),
  158. false => Ok(None),
  159. }
  160. })
  161. }
  162. #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view_rev.name), err)]
  163. pub fn create_view(&mut self, view_rev: ViewRevision) -> CollaborateResult<Option<FolderChange>> {
  164. let app_id = view_rev.belong_to_id.clone();
  165. self.with_app(&app_id, move |app| {
  166. if app.belongings.contains(&view_rev) {
  167. tracing::warn!("[RootFolder]: Duplicate view");
  168. return Ok(None);
  169. }
  170. app.belongings.push(view_rev);
  171. Ok(Some(()))
  172. })
  173. }
  174. pub fn read_view(&self, view_id: &str) -> CollaborateResult<ViewRevision> {
  175. for workspace in &self.workspaces {
  176. for app in &(*workspace.apps) {
  177. if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) {
  178. return Ok(view.clone());
  179. }
  180. }
  181. }
  182. Err(CollaborateError::record_not_found().context(format!("Can't find view with id {}", view_id)))
  183. }
  184. pub fn read_views(&self, belong_to_id: &str) -> CollaborateResult<Vec<ViewRevision>> {
  185. for workspace in &self.workspaces {
  186. for app in &(*workspace.apps) {
  187. if app.id == belong_to_id {
  188. return Ok(app.belongings.to_vec());
  189. }
  190. }
  191. }
  192. Ok(vec![])
  193. }
  194. pub fn update_view(
  195. &mut self,
  196. view_id: &str,
  197. name: Option<String>,
  198. desc: Option<String>,
  199. modified_time: i64,
  200. ) -> CollaborateResult<Option<FolderChange>> {
  201. let view = self.read_view(view_id)?;
  202. self.with_view(&view.belong_to_id, view_id, |view| {
  203. if let Some(name) = name {
  204. view.name = name;
  205. }
  206. if let Some(desc) = desc {
  207. view.desc = desc;
  208. }
  209. view.modified_time = modified_time;
  210. Ok(Some(()))
  211. })
  212. }
  213. #[tracing::instrument(level = "trace", skip(self), err)]
  214. pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult<Option<FolderChange>> {
  215. let view = self.read_view(view_id)?;
  216. self.with_app(&view.belong_to_id, |app| {
  217. app.belongings.retain(|view| view.id != view_id);
  218. Ok(Some(()))
  219. })
  220. }
  221. #[tracing::instrument(level = "trace", skip(self), err)]
  222. pub fn move_view(&mut self, view_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
  223. let view = self.read_view(view_id)?;
  224. self.with_app(&view.belong_to_id, |app| {
  225. match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to).map_err(internal_error)? {
  226. true => Ok(Some(())),
  227. false => Ok(None),
  228. }
  229. })
  230. }
  231. pub fn create_trash(&mut self, trash: Vec<TrashRevision>) -> CollaborateResult<Option<FolderChange>> {
  232. self.with_trash(|t| {
  233. let mut new_trash = trash.into_iter().map(Arc::new).collect::<Vec<Arc<TrashRevision>>>();
  234. t.append(&mut new_trash);
  235. Ok(Some(()))
  236. })
  237. }
  238. pub fn read_trash(&self, trash_id: Option<String>) -> CollaborateResult<Vec<TrashRevision>> {
  239. match trash_id {
  240. None => Ok(self
  241. .trash
  242. .iter()
  243. .map(|t| t.as_ref().clone())
  244. .collect::<Vec<TrashRevision>>()),
  245. Some(trash_id) => match self.trash.iter().find(|t| t.id == trash_id) {
  246. Some(trash) => Ok(vec![trash.as_ref().clone()]),
  247. None => Ok(vec![]),
  248. },
  249. }
  250. }
  251. pub fn delete_trash(&mut self, trash_ids: Option<Vec<String>>) -> CollaborateResult<Option<FolderChange>> {
  252. match trash_ids {
  253. None => self.with_trash(|trash| {
  254. trash.clear();
  255. Ok(Some(()))
  256. }),
  257. Some(trash_ids) => self.with_trash(|trash| {
  258. trash.retain(|t| !trash_ids.contains(&t.id));
  259. Ok(Some(()))
  260. }),
  261. }
  262. }
  263. pub fn md5(&self) -> String {
  264. md5(&self.delta.json_bytes())
  265. }
  266. pub fn to_json(&self) -> CollaborateResult<String> {
  267. serde_json::to_string(self)
  268. .map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))
  269. }
  270. }
  271. impl FolderPad {
  272. fn modify_workspaces<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChange>>
  273. where
  274. F: FnOnce(&mut Vec<Arc<WorkspaceRevision>>) -> CollaborateResult<Option<()>>,
  275. {
  276. let cloned_self = self.clone();
  277. match f(&mut self.workspaces)? {
  278. None => Ok(None),
  279. Some(_) => {
  280. let old = cloned_self.to_json()?;
  281. let new = self.to_json()?;
  282. match cal_diff::<PhantomAttributes>(old, new) {
  283. None => Ok(None),
  284. Some(delta) => {
  285. self.delta = self.delta.compose(&delta)?;
  286. Ok(Some(FolderChange { delta, md5: self.md5() }))
  287. }
  288. }
  289. }
  290. }
  291. }
  292. fn with_workspace<F>(&mut self, workspace_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
  293. where
  294. F: FnOnce(&mut WorkspaceRevision) -> CollaborateResult<Option<()>>,
  295. {
  296. self.modify_workspaces(|workspaces| {
  297. if let Some(workspace) = workspaces.iter_mut().find(|workspace| workspace_id == workspace.id) {
  298. f(Arc::make_mut(workspace))
  299. } else {
  300. tracing::warn!("[FolderPad]: Can't find any workspace with id: {}", workspace_id);
  301. Ok(None)
  302. }
  303. })
  304. }
  305. fn with_trash<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChange>>
  306. where
  307. F: FnOnce(&mut Vec<Arc<TrashRevision>>) -> CollaborateResult<Option<()>>,
  308. {
  309. let cloned_self = self.clone();
  310. match f(&mut self.trash)? {
  311. None => Ok(None),
  312. Some(_) => {
  313. let old = cloned_self.to_json()?;
  314. let new = self.to_json()?;
  315. match cal_diff::<PhantomAttributes>(old, new) {
  316. None => Ok(None),
  317. Some(delta) => {
  318. self.delta = self.delta.compose(&delta)?;
  319. Ok(Some(FolderChange { delta, md5: self.md5() }))
  320. }
  321. }
  322. }
  323. }
  324. }
  325. fn with_app<F>(&mut self, app_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
  326. where
  327. F: FnOnce(&mut AppRevision) -> CollaborateResult<Option<()>>,
  328. {
  329. let workspace_id = match self
  330. .workspaces
  331. .iter()
  332. .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id))
  333. {
  334. None => {
  335. tracing::warn!("[FolderPad]: Can't find any app with id: {}", app_id);
  336. return Ok(None);
  337. }
  338. Some(workspace) => workspace.id.clone(),
  339. };
  340. self.with_workspace(&workspace_id, |workspace| {
  341. // It's ok to unwrap because we get the workspace from the app_id.
  342. f(workspace.apps.iter_mut().find(|app| app_id == app.id).unwrap())
  343. })
  344. }
  345. fn with_view<F>(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
  346. where
  347. F: FnOnce(&mut ViewRevision) -> CollaborateResult<Option<()>>,
  348. {
  349. self.with_app(belong_to_id, |app| {
  350. match app.belongings.iter_mut().find(|view| view_id == view.id) {
  351. None => {
  352. tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id);
  353. Ok(None)
  354. }
  355. Some(view) => f(view),
  356. }
  357. })
  358. }
  359. }
  360. pub fn default_folder_delta() -> FolderDelta {
  361. TextDeltaBuilder::new()
  362. .insert(r#"{"workspaces":[],"trash":[]}"#)
  363. .build()
  364. }
  365. pub fn initial_folder_delta(folder_pad: &FolderPad) -> CollaborateResult<FolderDelta> {
  366. let json = folder_pad.to_json()?;
  367. let delta = TextDeltaBuilder::new().insert(&json).build();
  368. Ok(delta)
  369. }
  370. impl std::default::Default for FolderPad {
  371. fn default() -> Self {
  372. FolderPad {
  373. workspaces: vec![],
  374. trash: vec![],
  375. delta: default_folder_delta(),
  376. }
  377. }
  378. }
  379. pub struct FolderChange {
  380. pub delta: FolderDelta,
  381. /// md5: the md5 of the FolderPad's delta after applying the change.
  382. pub md5: String,
  383. }
  384. #[cfg(test)]
  385. mod tests {
  386. #![allow(clippy::all)]
  387. use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta};
  388. use chrono::Utc;
  389. use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
  390. use lib_ot::core::{OperationTransform, TextDelta, TextDeltaBuilder};
  391. #[test]
  392. fn folder_add_workspace() {
  393. let (mut folder, initial_delta, _) = test_folder();
  394. let _time = Utc::now();
  395. let mut workspace_1 = WorkspaceRevision::default();
  396. workspace_1.name = "My first workspace".to_owned();
  397. let delta_1 = folder.create_workspace(workspace_1).unwrap().unwrap().delta;
  398. let mut workspace_2 = WorkspaceRevision::default();
  399. workspace_2.name = "My second workspace".to_owned();
  400. let delta_2 = folder.create_workspace(workspace_2).unwrap().unwrap().delta;
  401. let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta_1, delta_2]);
  402. assert_eq!(folder, folder_from_delta);
  403. }
  404. #[test]
  405. fn folder_update_workspace() {
  406. let (mut folder, initial_delta, workspace) = test_folder();
  407. assert_folder_equal(
  408. &folder,
  409. &make_folder_from_delta(initial_delta.clone(), vec![]),
  410. r#"{"workspaces":[{"id":"1","name":"😁 my first workspace","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
  411. );
  412. let delta = folder
  413. .update_workspace(&workspace.id, Some("☺️ rename workspace️".to_string()), None)
  414. .unwrap()
  415. .unwrap()
  416. .delta;
  417. let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]);
  418. assert_folder_equal(
  419. &folder,
  420. &folder_from_delta,
  421. r#"{"workspaces":[{"id":"1","name":"☺️ rename workspace️","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
  422. );
  423. }
  424. #[test]
  425. fn folder_add_app() {
  426. let (folder, initial_delta, _app) = test_app_folder();
  427. let folder_from_delta = make_folder_from_delta(initial_delta, vec![]);
  428. assert_eq!(folder, folder_from_delta);
  429. assert_folder_equal(
  430. &folder,
  431. &folder_from_delta,
  432. r#"{
  433. "workspaces": [
  434. {
  435. "id": "1",
  436. "name": "😁 my first workspace",
  437. "desc": "",
  438. "apps": [
  439. {
  440. "id": "",
  441. "workspace_id": "1",
  442. "name": "😁 my first app",
  443. "desc": "",
  444. "belongings": [],
  445. "version": 0,
  446. "modified_time": 0,
  447. "create_time": 0
  448. }
  449. ],
  450. "modified_time": 0,
  451. "create_time": 0
  452. }
  453. ],
  454. "trash": []
  455. }"#,
  456. );
  457. }
  458. #[test]
  459. fn folder_update_app() {
  460. let (mut folder, initial_delta, app) = test_app_folder();
  461. let delta = folder
  462. .update_app(&app.id, Some("🤪 rename app".to_owned()), None)
  463. .unwrap()
  464. .unwrap()
  465. .delta;
  466. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  467. assert_folder_equal(
  468. &folder,
  469. &new_folder,
  470. r#"{
  471. "workspaces": [
  472. {
  473. "id": "1",
  474. "name": "😁 my first workspace",
  475. "desc": "",
  476. "apps": [
  477. {
  478. "id": "",
  479. "workspace_id": "1",
  480. "name": "🤪 rename app",
  481. "desc": "",
  482. "belongings": [],
  483. "version": 0,
  484. "modified_time": 0,
  485. "create_time": 0
  486. }
  487. ],
  488. "modified_time": 0,
  489. "create_time": 0
  490. }
  491. ],
  492. "trash": []
  493. }"#,
  494. );
  495. }
  496. #[test]
  497. fn folder_delete_app() {
  498. let (mut folder, initial_delta, app) = test_app_folder();
  499. let delta = folder.delete_app(&app.id).unwrap().unwrap().delta;
  500. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  501. assert_folder_equal(
  502. &folder,
  503. &new_folder,
  504. r#"{
  505. "workspaces": [
  506. {
  507. "id": "1",
  508. "name": "😁 my first workspace",
  509. "desc": "",
  510. "apps": [],
  511. "modified_time": 0,
  512. "create_time": 0
  513. }
  514. ],
  515. "trash": []
  516. }"#,
  517. );
  518. }
  519. #[test]
  520. fn folder_add_view() {
  521. let (folder, initial_delta, _view) = test_view_folder();
  522. assert_folder_equal(
  523. &folder,
  524. &make_folder_from_delta(initial_delta, vec![]),
  525. r#"
  526. {
  527. "workspaces": [
  528. {
  529. "id": "1",
  530. "name": "😁 my first workspace",
  531. "desc": "",
  532. "apps": [
  533. {
  534. "id": "",
  535. "workspace_id": "1",
  536. "name": "😁 my first app",
  537. "desc": "",
  538. "belongings": [
  539. {
  540. "id": "",
  541. "belong_to_id": "",
  542. "name": "🎃 my first view",
  543. "desc": "",
  544. "view_type": "Blank",
  545. "version": 0,
  546. "belongings": [],
  547. "modified_time": 0,
  548. "create_time": 0
  549. }
  550. ],
  551. "version": 0,
  552. "modified_time": 0,
  553. "create_time": 0
  554. }
  555. ],
  556. "modified_time": 0,
  557. "create_time": 0
  558. }
  559. ],
  560. "trash": []
  561. }"#,
  562. );
  563. }
  564. #[test]
  565. fn folder_update_view() {
  566. let (mut folder, initial_delta, view) = test_view_folder();
  567. let delta = folder
  568. .update_view(&view.id, Some("😦 rename view".to_owned()), None, 123)
  569. .unwrap()
  570. .unwrap()
  571. .delta;
  572. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  573. assert_folder_equal(
  574. &folder,
  575. &new_folder,
  576. r#"{
  577. "workspaces": [
  578. {
  579. "id": "1",
  580. "name": "😁 my first workspace",
  581. "desc": "",
  582. "apps": [
  583. {
  584. "id": "",
  585. "workspace_id": "1",
  586. "name": "😁 my first app",
  587. "desc": "",
  588. "belongings": [
  589. {
  590. "id": "",
  591. "belong_to_id": "",
  592. "name": "😦 rename view",
  593. "desc": "",
  594. "view_type": "Blank",
  595. "version": 0,
  596. "belongings": [],
  597. "modified_time": 123,
  598. "create_time": 0
  599. }
  600. ],
  601. "version": 0,
  602. "modified_time": 0,
  603. "create_time": 0
  604. }
  605. ],
  606. "modified_time": 0,
  607. "create_time": 0
  608. }
  609. ],
  610. "trash": []
  611. }"#,
  612. );
  613. }
  614. #[test]
  615. fn folder_delete_view() {
  616. let (mut folder, initial_delta, view) = test_view_folder();
  617. let delta = folder.delete_view(&view.id).unwrap().unwrap().delta;
  618. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  619. assert_folder_equal(
  620. &folder,
  621. &new_folder,
  622. r#"{
  623. "workspaces": [
  624. {
  625. "id": "1",
  626. "name": "😁 my first workspace",
  627. "desc": "",
  628. "apps": [
  629. {
  630. "id": "",
  631. "workspace_id": "1",
  632. "name": "😁 my first app",
  633. "desc": "",
  634. "belongings": [],
  635. "version": 0,
  636. "modified_time": 0,
  637. "create_time": 0
  638. }
  639. ],
  640. "modified_time": 0,
  641. "create_time": 0
  642. }
  643. ],
  644. "trash": []
  645. }"#,
  646. );
  647. }
  648. #[test]
  649. fn folder_add_trash() {
  650. let (folder, initial_delta, _trash) = test_trash();
  651. assert_folder_equal(
  652. &folder,
  653. &make_folder_from_delta(initial_delta, vec![]),
  654. r#"{
  655. "workspaces": [],
  656. "trash": [
  657. {
  658. "id": "1",
  659. "name": "🚽 my first trash",
  660. "modified_time": 0,
  661. "create_time": 0,
  662. "ty": 0
  663. }
  664. ]
  665. }
  666. "#,
  667. );
  668. }
  669. #[test]
  670. fn folder_delete_trash() {
  671. let (mut folder, initial_delta, trash) = test_trash();
  672. let delta = folder.delete_trash(Some(vec![trash.id])).unwrap().unwrap().delta;
  673. assert_folder_equal(
  674. &folder,
  675. &make_folder_from_delta(initial_delta, vec![delta]),
  676. r#"{
  677. "workspaces": [],
  678. "trash": []
  679. }
  680. "#,
  681. );
  682. }
  683. fn test_folder() -> (FolderPad, FolderDelta, WorkspaceRevision) {
  684. let mut folder = FolderPad::default();
  685. let folder_json = serde_json::to_string(&folder).unwrap();
  686. let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
  687. let mut workspace_rev = WorkspaceRevision::default();
  688. workspace_rev.name = "😁 my first workspace".to_owned();
  689. workspace_rev.id = "1".to_owned();
  690. delta = delta
  691. .compose(&folder.create_workspace(workspace_rev.clone()).unwrap().unwrap().delta)
  692. .unwrap();
  693. (folder, delta, workspace_rev)
  694. }
  695. fn test_app_folder() -> (FolderPad, FolderDelta, AppRevision) {
  696. let (mut folder, mut initial_delta, workspace) = test_folder();
  697. let mut app_rev = AppRevision::default();
  698. app_rev.workspace_id = workspace.id;
  699. app_rev.name = "😁 my first app".to_owned();
  700. initial_delta = initial_delta
  701. .compose(&folder.create_app(app_rev.clone()).unwrap().unwrap().delta)
  702. .unwrap();
  703. (folder, initial_delta, app_rev)
  704. }
  705. fn test_view_folder() -> (FolderPad, FolderDelta, ViewRevision) {
  706. let (mut folder, mut initial_delta, app) = test_app_folder();
  707. let mut view_rev = ViewRevision::default();
  708. view_rev.belong_to_id = app.id.clone();
  709. view_rev.name = "🎃 my first view".to_owned();
  710. initial_delta = initial_delta
  711. .compose(&folder.create_view(view_rev.clone()).unwrap().unwrap().delta)
  712. .unwrap();
  713. (folder, initial_delta, view_rev)
  714. }
  715. fn test_trash() -> (FolderPad, FolderDelta, TrashRevision) {
  716. let mut folder = FolderPad::default();
  717. let folder_json = serde_json::to_string(&folder).unwrap();
  718. let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
  719. let mut trash_rev = TrashRevision::default();
  720. trash_rev.name = "🚽 my first trash".to_owned();
  721. trash_rev.id = "1".to_owned();
  722. delta = delta
  723. .compose(
  724. &folder
  725. .create_trash(vec![trash_rev.clone().into()])
  726. .unwrap()
  727. .unwrap()
  728. .delta,
  729. )
  730. .unwrap();
  731. (folder, delta, trash_rev)
  732. }
  733. fn make_folder_from_delta(mut initial_delta: FolderDelta, deltas: Vec<TextDelta>) -> FolderPad {
  734. for delta in deltas {
  735. initial_delta = initial_delta.compose(&delta).unwrap();
  736. }
  737. FolderPad::from_delta(initial_delta).unwrap()
  738. }
  739. fn assert_folder_equal(old: &FolderPad, new: &FolderPad, expected: &str) {
  740. assert_eq!(old, new);
  741. let json1 = old.to_json().unwrap();
  742. let json2 = new.to_json().unwrap();
  743. let expect_folder: FolderPad = serde_json::from_str(expected).unwrap();
  744. assert_eq!(json1, expect_folder.to_json().unwrap());
  745. assert_eq!(json1, json2);
  746. }
  747. }