folder_pad.rs 29 KB

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