folder_pad.rs 28 KB

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