folder_pad.rs 31 KB

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