folder_pad.rs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  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 = TextDeltaBuilder::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.belong_to_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.belong_to_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.belong_to_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.belong_to_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. serde_json::to_string(&self.folder_rev)
  284. .map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))
  285. }
  286. }
  287. impl FolderPad {
  288. fn modify_workspaces<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChangeset>>
  289. where
  290. F: FnOnce(&mut Vec<Arc<WorkspaceRevision>>) -> CollaborateResult<Option<()>>,
  291. {
  292. let cloned_self = self.clone();
  293. match f(&mut self.folder_rev.workspaces)? {
  294. None => Ok(None),
  295. Some(_) => {
  296. let old = cloned_self.to_json()?;
  297. let new = self.to_json()?;
  298. match cal_diff::<PhantomAttributes>(old, new) {
  299. None => Ok(None),
  300. Some(delta) => {
  301. self.delta = self.delta.compose(&delta)?;
  302. Ok(Some(FolderChangeset { delta, md5: self.md5() }))
  303. }
  304. }
  305. }
  306. }
  307. }
  308. fn with_workspace<F>(&mut self, workspace_id: &str, f: F) -> CollaborateResult<Option<FolderChangeset>>
  309. where
  310. F: FnOnce(&mut WorkspaceRevision) -> CollaborateResult<Option<()>>,
  311. {
  312. self.modify_workspaces(|workspaces| {
  313. if let Some(workspace) = workspaces.iter_mut().find(|workspace| workspace_id == workspace.id) {
  314. f(Arc::make_mut(workspace))
  315. } else {
  316. tracing::warn!("[FolderPad]: Can't find any workspace with id: {}", workspace_id);
  317. Ok(None)
  318. }
  319. })
  320. }
  321. fn with_trash<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChangeset>>
  322. where
  323. F: FnOnce(&mut Vec<Arc<TrashRevision>>) -> CollaborateResult<Option<()>>,
  324. {
  325. let cloned_self = self.clone();
  326. match f(&mut self.folder_rev.trash)? {
  327. None => Ok(None),
  328. Some(_) => {
  329. let old = cloned_self.to_json()?;
  330. let new = self.to_json()?;
  331. match cal_diff::<PhantomAttributes>(old, new) {
  332. None => Ok(None),
  333. Some(delta) => {
  334. self.delta = self.delta.compose(&delta)?;
  335. Ok(Some(FolderChangeset { delta, md5: self.md5() }))
  336. }
  337. }
  338. }
  339. }
  340. }
  341. fn with_app<F>(&mut self, app_id: &str, f: F) -> CollaborateResult<Option<FolderChangeset>>
  342. where
  343. F: FnOnce(&mut AppRevision) -> CollaborateResult<Option<()>>,
  344. {
  345. let workspace_id = match self
  346. .folder_rev
  347. .workspaces
  348. .iter()
  349. .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id))
  350. {
  351. None => {
  352. tracing::warn!("[FolderPad]: Can't find any app with id: {}", app_id);
  353. return Ok(None);
  354. }
  355. Some(workspace) => workspace.id.clone(),
  356. };
  357. self.with_workspace(&workspace_id, |workspace| {
  358. // It's ok to unwrap because we get the workspace from the app_id.
  359. f(workspace.apps.iter_mut().find(|app| app_id == app.id).unwrap())
  360. })
  361. }
  362. fn with_view<F>(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult<Option<FolderChangeset>>
  363. where
  364. F: FnOnce(&mut ViewRevision) -> CollaborateResult<Option<()>>,
  365. {
  366. self.with_app(belong_to_id, |app| {
  367. match app.belongings.iter_mut().find(|view| view_id == view.id) {
  368. None => {
  369. tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id);
  370. Ok(None)
  371. }
  372. Some(view) => f(view),
  373. }
  374. })
  375. }
  376. }
  377. pub fn default_folder_delta() -> FolderDelta {
  378. TextDeltaBuilder::new()
  379. .insert(r#"{"workspaces":[],"trash":[]}"#)
  380. .build()
  381. }
  382. pub fn initial_folder_delta(folder_pad: &FolderPad) -> CollaborateResult<FolderDelta> {
  383. let json = folder_pad.to_json()?;
  384. let delta = TextDeltaBuilder::new().insert(&json).build();
  385. Ok(delta)
  386. }
  387. impl std::default::Default for FolderPad {
  388. fn default() -> Self {
  389. FolderPad {
  390. folder_rev: FolderRevision::default(),
  391. delta: default_folder_delta(),
  392. }
  393. }
  394. }
  395. pub struct FolderChangeset {
  396. pub delta: FolderDelta,
  397. /// md5: the md5 of the FolderPad's delta after applying the change.
  398. pub md5: String,
  399. }
  400. #[cfg(test)]
  401. mod tests {
  402. #![allow(clippy::all)]
  403. use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta};
  404. use chrono::Utc;
  405. use flowy_folder_data_model::revision::{
  406. AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision,
  407. };
  408. use lib_ot::core::{OperationTransform, TextDelta, TextDeltaBuilder};
  409. use serde_json::json;
  410. #[test]
  411. fn folder_add_workspace() {
  412. let (mut folder, initial_delta, _) = test_folder();
  413. let _time = Utc::now();
  414. let mut workspace_1 = WorkspaceRevision::default();
  415. workspace_1.name = "My first workspace".to_owned();
  416. let delta_1 = folder.create_workspace(workspace_1).unwrap().unwrap().delta;
  417. let mut workspace_2 = WorkspaceRevision::default();
  418. workspace_2.name = "My second workspace".to_owned();
  419. let delta_2 = folder.create_workspace(workspace_2).unwrap().unwrap().delta;
  420. let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta_1, delta_2]);
  421. assert_eq!(folder, folder_from_delta);
  422. }
  423. #[test]
  424. fn folder_update_workspace() {
  425. let (mut folder, initial_delta, workspace) = test_folder();
  426. assert_folder_equal(
  427. &folder,
  428. &make_folder_from_delta(initial_delta.clone(), vec![]),
  429. r#"{"workspaces":[{"id":"1","name":"😁 my first workspace","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
  430. );
  431. let delta = folder
  432. .update_workspace(&workspace.id, Some("☺️ rename workspace️".to_string()), None)
  433. .unwrap()
  434. .unwrap()
  435. .delta;
  436. let folder_from_delta = make_folder_from_delta(initial_delta, vec![delta]);
  437. assert_folder_equal(
  438. &folder,
  439. &folder_from_delta,
  440. r#"{"workspaces":[{"id":"1","name":"☺️ rename workspace️","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
  441. );
  442. }
  443. #[test]
  444. fn folder_add_app() {
  445. let (folder, initial_delta, _app) = test_app_folder();
  446. let folder_from_delta = make_folder_from_delta(initial_delta, vec![]);
  447. assert_eq!(folder, folder_from_delta);
  448. assert_folder_equal(
  449. &folder,
  450. &folder_from_delta,
  451. r#"{
  452. "workspaces": [
  453. {
  454. "id": "1",
  455. "name": "😁 my first workspace",
  456. "desc": "",
  457. "apps": [
  458. {
  459. "id": "",
  460. "workspace_id": "1",
  461. "name": "😁 my first app",
  462. "desc": "",
  463. "belongings": [],
  464. "version": 0,
  465. "modified_time": 0,
  466. "create_time": 0
  467. }
  468. ],
  469. "modified_time": 0,
  470. "create_time": 0
  471. }
  472. ],
  473. "trash": []
  474. }"#,
  475. );
  476. }
  477. #[test]
  478. fn folder_update_app() {
  479. let (mut folder, initial_delta, app) = test_app_folder();
  480. let delta = folder
  481. .update_app(&app.id, Some("🤪 rename app".to_owned()), None)
  482. .unwrap()
  483. .unwrap()
  484. .delta;
  485. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  486. assert_folder_equal(
  487. &folder,
  488. &new_folder,
  489. r#"{
  490. "workspaces": [
  491. {
  492. "id": "1",
  493. "name": "😁 my first workspace",
  494. "desc": "",
  495. "apps": [
  496. {
  497. "id": "",
  498. "workspace_id": "1",
  499. "name": "🤪 rename app",
  500. "desc": "",
  501. "belongings": [],
  502. "version": 0,
  503. "modified_time": 0,
  504. "create_time": 0
  505. }
  506. ],
  507. "modified_time": 0,
  508. "create_time": 0
  509. }
  510. ],
  511. "trash": []
  512. }"#,
  513. );
  514. }
  515. #[test]
  516. fn folder_delete_app() {
  517. let (mut folder, initial_delta, app) = test_app_folder();
  518. let delta = folder.delete_app(&app.id).unwrap().unwrap().delta;
  519. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  520. assert_folder_equal(
  521. &folder,
  522. &new_folder,
  523. r#"{
  524. "workspaces": [
  525. {
  526. "id": "1",
  527. "name": "😁 my first workspace",
  528. "desc": "",
  529. "apps": [],
  530. "modified_time": 0,
  531. "create_time": 0
  532. }
  533. ],
  534. "trash": []
  535. }"#,
  536. );
  537. }
  538. #[test]
  539. fn folder_add_view() {
  540. let (folder, initial_delta, _view) = test_view_folder();
  541. assert_folder_equal(
  542. &folder,
  543. &make_folder_from_delta(initial_delta, vec![]),
  544. r#"
  545. {
  546. "workspaces": [
  547. {
  548. "id": "1",
  549. "name": "😁 my first workspace",
  550. "desc": "",
  551. "apps": [
  552. {
  553. "id": "",
  554. "workspace_id": "1",
  555. "name": "😁 my first app",
  556. "desc": "",
  557. "belongings": [
  558. {
  559. "id": "",
  560. "belong_to_id": "",
  561. "name": "🎃 my first view",
  562. "desc": "",
  563. "view_type": "Blank",
  564. "version": 0,
  565. "belongings": [],
  566. "modified_time": 0,
  567. "create_time": 0
  568. }
  569. ],
  570. "version": 0,
  571. "modified_time": 0,
  572. "create_time": 0
  573. }
  574. ],
  575. "modified_time": 0,
  576. "create_time": 0
  577. }
  578. ],
  579. "trash": []
  580. }"#,
  581. );
  582. }
  583. #[test]
  584. fn folder_update_view() {
  585. let (mut folder, initial_delta, view) = test_view_folder();
  586. let delta = folder
  587. .update_view(&view.id, Some("😦 rename view".to_owned()), None, 123)
  588. .unwrap()
  589. .unwrap()
  590. .delta;
  591. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  592. assert_folder_equal(
  593. &folder,
  594. &new_folder,
  595. r#"{
  596. "workspaces": [
  597. {
  598. "id": "1",
  599. "name": "😁 my first workspace",
  600. "desc": "",
  601. "apps": [
  602. {
  603. "id": "",
  604. "workspace_id": "1",
  605. "name": "😁 my first app",
  606. "desc": "",
  607. "belongings": [
  608. {
  609. "id": "",
  610. "belong_to_id": "",
  611. "name": "😦 rename view",
  612. "desc": "",
  613. "view_type": "Blank",
  614. "version": 0,
  615. "belongings": [],
  616. "modified_time": 123,
  617. "create_time": 0
  618. }
  619. ],
  620. "version": 0,
  621. "modified_time": 0,
  622. "create_time": 0
  623. }
  624. ],
  625. "modified_time": 0,
  626. "create_time": 0
  627. }
  628. ],
  629. "trash": []
  630. }"#,
  631. );
  632. }
  633. #[test]
  634. fn folder_delete_view() {
  635. let (mut folder, initial_delta, view) = test_view_folder();
  636. let delta = folder.delete_view(&view.id).unwrap().unwrap().delta;
  637. let new_folder = make_folder_from_delta(initial_delta, vec![delta]);
  638. assert_folder_equal(
  639. &folder,
  640. &new_folder,
  641. r#"{
  642. "workspaces": [
  643. {
  644. "id": "1",
  645. "name": "😁 my first workspace",
  646. "desc": "",
  647. "apps": [
  648. {
  649. "id": "",
  650. "workspace_id": "1",
  651. "name": "😁 my first app",
  652. "desc": "",
  653. "belongings": [],
  654. "version": 0,
  655. "modified_time": 0,
  656. "create_time": 0
  657. }
  658. ],
  659. "modified_time": 0,
  660. "create_time": 0
  661. }
  662. ],
  663. "trash": []
  664. }"#,
  665. );
  666. }
  667. #[test]
  668. fn folder_add_trash() {
  669. let (folder, initial_delta, _trash) = test_trash();
  670. assert_folder_equal(
  671. &folder,
  672. &make_folder_from_delta(initial_delta, vec![]),
  673. r#"{
  674. "workspaces": [],
  675. "trash": [
  676. {
  677. "id": "1",
  678. "name": "🚽 my first trash",
  679. "modified_time": 0,
  680. "create_time": 0,
  681. "ty": 0
  682. }
  683. ]
  684. }
  685. "#,
  686. );
  687. }
  688. #[test]
  689. fn folder_delete_trash() {
  690. let (mut folder, initial_delta, trash) = test_trash();
  691. let delta = folder.delete_trash(Some(vec![trash.id])).unwrap().unwrap().delta;
  692. assert_folder_equal(
  693. &folder,
  694. &make_folder_from_delta(initial_delta, vec![delta]),
  695. r#"{
  696. "workspaces": [],
  697. "trash": []
  698. }
  699. "#,
  700. );
  701. }
  702. fn test_folder() -> (FolderPad, FolderDelta, WorkspaceRevision) {
  703. let folder_rev = FolderRevision::default();
  704. let folder_json = serde_json::to_string(&folder_rev).unwrap();
  705. let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
  706. let mut workspace_rev = WorkspaceRevision::default();
  707. workspace_rev.name = "😁 my first workspace".to_owned();
  708. workspace_rev.id = "1".to_owned();
  709. let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap();
  710. delta = delta
  711. .compose(&folder.create_workspace(workspace_rev.clone()).unwrap().unwrap().delta)
  712. .unwrap();
  713. (folder, delta, workspace_rev)
  714. }
  715. fn test_app_folder() -> (FolderPad, FolderDelta, AppRevision) {
  716. let (mut folder_rev, mut initial_delta, workspace) = test_folder();
  717. let mut app_rev = AppRevision::default();
  718. app_rev.workspace_id = workspace.id;
  719. app_rev.name = "😁 my first app".to_owned();
  720. initial_delta = initial_delta
  721. .compose(&folder_rev.create_app(app_rev.clone()).unwrap().unwrap().delta)
  722. .unwrap();
  723. (folder_rev, initial_delta, app_rev)
  724. }
  725. fn test_view_folder() -> (FolderPad, FolderDelta, ViewRevision) {
  726. let (mut folder, mut initial_delta, app) = test_app_folder();
  727. let mut view_rev = ViewRevision::default();
  728. view_rev.belong_to_id = app.id.clone();
  729. view_rev.name = "🎃 my first view".to_owned();
  730. initial_delta = initial_delta
  731. .compose(&folder.create_view(view_rev.clone()).unwrap().unwrap().delta)
  732. .unwrap();
  733. (folder, initial_delta, view_rev)
  734. }
  735. fn test_trash() -> (FolderPad, FolderDelta, TrashRevision) {
  736. let folder_rev = FolderRevision::default();
  737. let folder_json = serde_json::to_string(&folder_rev).unwrap();
  738. let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
  739. let mut trash_rev = TrashRevision::default();
  740. trash_rev.name = "🚽 my first trash".to_owned();
  741. trash_rev.id = "1".to_owned();
  742. let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap();
  743. delta = delta
  744. .compose(
  745. &folder
  746. .create_trash(vec![trash_rev.clone().into()])
  747. .unwrap()
  748. .unwrap()
  749. .delta,
  750. )
  751. .unwrap();
  752. (folder, delta, trash_rev)
  753. }
  754. fn make_folder_from_delta(mut initial_delta: FolderDelta, deltas: Vec<TextDelta>) -> FolderPad {
  755. for delta in deltas {
  756. initial_delta = initial_delta.compose(&delta).unwrap();
  757. }
  758. FolderPad::from_delta(initial_delta).unwrap()
  759. }
  760. fn assert_folder_equal(old: &FolderPad, new: &FolderPad, expected: &str) {
  761. assert_eq!(old, new);
  762. let json1 = old.to_json().unwrap();
  763. let json2 = new.to_json().unwrap();
  764. // format the json str
  765. let folder_rev: FolderRevision = serde_json::from_str(expected).unwrap();
  766. let expected = serde_json::to_string(&folder_rev).unwrap();
  767. assert_eq!(json1, expected);
  768. assert_eq!(json1, json2);
  769. }
  770. }