folder_pad.rs 30 KB

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