folder_pad.rs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  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)
  31. .map_err(|e| SyncError::internal().context(format!("Serialize to folder json str failed: {}", e)))?;
  32. let operations = FolderOperationsBuilder::new().insert(&json).build();
  33. Ok(Self { folder_rev, operations })
  34. }
  35. pub fn from_revisions(revisions: Vec<Revision>) -> SyncResult<Self> {
  36. FolderPadBuilder::new().build_with_revisions(revisions)
  37. }
  38. pub fn from_operations(operations: FolderOperations) -> SyncResult<Self> {
  39. let content = operations.content()?;
  40. let mut deserializer = serde_json::Deserializer::from_reader(content.as_bytes());
  41. let folder_rev = FolderRevision::deserialize(&mut deserializer).map_err(|e| {
  42. tracing::error!("Deserialize folder from {} failed", content);
  43. SyncError::internal().context(format!("Deserialize operations to folder failed: {}", e))
  44. })?;
  45. Ok(Self { folder_rev, operations })
  46. }
  47. pub fn get_operations(&self) -> &FolderOperations {
  48. &self.operations
  49. }
  50. pub fn reset_folder(&mut self, operations: FolderOperations) -> SyncResult<String> {
  51. let folder = FolderPad::from_operations(operations)?;
  52. self.folder_rev = folder.folder_rev;
  53. self.operations = folder.operations;
  54. Ok(self.folder_md5())
  55. }
  56. pub fn compose_remote_operations(&mut self, operations: FolderOperations) -> SyncResult<String> {
  57. let composed_operations = self.operations.compose(&operations)?;
  58. self.reset_folder(composed_operations)
  59. }
  60. pub fn is_empty(&self) -> bool {
  61. self.folder_rev.workspaces.is_empty() && self.folder_rev.trash.is_empty()
  62. }
  63. #[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)]
  64. pub fn create_workspace(&mut self, workspace_rev: WorkspaceRevision) -> SyncResult<Option<FolderChangeset>> {
  65. let workspace = Arc::new(workspace_rev);
  66. if self.folder_rev.workspaces.contains(&workspace) {
  67. tracing::warn!("[RootFolder]: Duplicate workspace");
  68. return Ok(None);
  69. }
  70. self.modify_workspaces(move |workspaces| {
  71. workspaces.push(workspace);
  72. Ok(Some(()))
  73. })
  74. }
  75. pub fn update_workspace(
  76. &mut self,
  77. workspace_id: &str,
  78. name: Option<String>,
  79. desc: Option<String>,
  80. ) -> SyncResult<Option<FolderChangeset>> {
  81. self.with_workspace(workspace_id, |workspace| {
  82. if let Some(name) = name {
  83. workspace.name = name;
  84. }
  85. if let Some(desc) = desc {
  86. workspace.desc = desc;
  87. }
  88. Ok(Some(()))
  89. })
  90. }
  91. pub fn read_workspaces(&self, workspace_id: Option<String>) -> SyncResult<Vec<WorkspaceRevision>> {
  92. match workspace_id {
  93. None => {
  94. let workspaces = self
  95. .folder_rev
  96. .workspaces
  97. .iter()
  98. .map(|workspace| workspace.as_ref().clone())
  99. .collect::<Vec<WorkspaceRevision>>();
  100. Ok(workspaces)
  101. }
  102. Some(workspace_id) => {
  103. if let Some(workspace) = self
  104. .folder_rev
  105. .workspaces
  106. .iter()
  107. .find(|workspace| workspace.id == workspace_id)
  108. {
  109. Ok(vec![workspace.as_ref().clone()])
  110. } else {
  111. Err(SyncError::record_not_found().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) -> SyncResult<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) -> SyncResult<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) -> SyncResult<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(SyncError::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. ) -> SyncResult<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) -> SyncResult<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) -> SyncResult<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)
  172. .map_err(internal_sync_error)?
  173. {
  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) -> SyncResult<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) -> SyncResult<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(SyncError::record_not_found().context(format!("Can't find view with id {}", view_id)))
  200. }
  201. pub fn read_views(&self, belong_to_id: &str) -> SyncResult<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. ) -> SyncResult<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, app_id: &str, view_id: &str) -> SyncResult<Option<FolderChangeset>> {
  232. self.with_app(app_id, |app| {
  233. app.belongings.retain(|view| view.id != view_id);
  234. Ok(Some(()))
  235. })
  236. }
  237. #[tracing::instrument(level = "trace", skip(self), err)]
  238. pub fn move_view(&mut self, view_id: &str, from: usize, to: usize) -> SyncResult<Option<FolderChangeset>> {
  239. let view = self.read_view(view_id)?;
  240. self.with_app(&view.app_id, |app| {
  241. match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to)
  242. .map_err(internal_sync_error)?
  243. {
  244. true => Ok(Some(())),
  245. false => Ok(None),
  246. }
  247. })
  248. }
  249. pub fn create_trash(&mut self, trash: Vec<TrashRevision>) -> SyncResult<Option<FolderChangeset>> {
  250. self.with_trash(|original_trash| {
  251. let mut new_trash = trash
  252. .into_iter()
  253. .flat_map(|new_trash| {
  254. if original_trash.iter().any(|old_trash| old_trash.id == new_trash.id) {
  255. None
  256. } else {
  257. Some(Arc::new(new_trash))
  258. }
  259. })
  260. .collect::<Vec<Arc<TrashRevision>>>();
  261. if new_trash.is_empty() {
  262. Ok(None)
  263. } else {
  264. original_trash.append(&mut new_trash);
  265. Ok(Some(()))
  266. }
  267. })
  268. }
  269. pub fn read_trash(&self, trash_id: Option<String>) -> SyncResult<Vec<TrashRevision>> {
  270. match trash_id {
  271. None => {
  272. // Removes the duplicate items if exist
  273. let mut trash_items = Vec::<TrashRevision>::with_capacity(self.folder_rev.trash.len());
  274. for trash_item in self.folder_rev.trash.iter() {
  275. if !trash_items.iter().any(|item| item.id == trash_item.id) {
  276. trash_items.push(trash_item.as_ref().clone());
  277. }
  278. }
  279. Ok(trash_items)
  280. }
  281. Some(trash_id) => match self.folder_rev.trash.iter().find(|t| t.id == trash_id) {
  282. Some(trash) => Ok(vec![trash.as_ref().clone()]),
  283. None => Ok(vec![]),
  284. },
  285. }
  286. }
  287. pub fn delete_trash(&mut self, trash_ids: Option<Vec<String>>) -> SyncResult<Option<FolderChangeset>> {
  288. match trash_ids {
  289. None => self.with_trash(|trash| {
  290. trash.clear();
  291. Ok(Some(()))
  292. }),
  293. Some(trash_ids) => self.with_trash(|trash| {
  294. trash.retain(|t| !trash_ids.contains(&t.id));
  295. Ok(Some(()))
  296. }),
  297. }
  298. }
  299. pub fn folder_md5(&self) -> String {
  300. md5(&self.operations.json_bytes())
  301. }
  302. pub fn to_json(&self) -> SyncResult<String> {
  303. make_folder_rev_json_str(&self.folder_rev)
  304. }
  305. }
  306. pub fn make_folder_rev_json_str(folder_rev: &FolderRevision) -> SyncResult<String> {
  307. let json = serde_json::to_string(folder_rev)
  308. .map_err(|err| internal_sync_error(format!("Serialize folder to json str failed. {:?}", err)))?;
  309. Ok(json)
  310. }
  311. impl FolderPad {
  312. fn modify_workspaces<F>(&mut self, f: F) -> SyncResult<Option<FolderChangeset>>
  313. where
  314. F: FnOnce(&mut Vec<Arc<WorkspaceRevision>>) -> SyncResult<Option<()>>,
  315. {
  316. let cloned_self = self.clone();
  317. match f(&mut self.folder_rev.workspaces)? {
  318. None => Ok(None),
  319. Some(_) => {
  320. let old = cloned_self.to_json()?;
  321. let new = self.to_json()?;
  322. match cal_diff::<EmptyAttributes>(old, new) {
  323. None => Ok(None),
  324. Some(operations) => {
  325. self.operations = self.operations.compose(&operations)?;
  326. Ok(Some(FolderChangeset {
  327. operations,
  328. md5: self.folder_md5(),
  329. }))
  330. }
  331. }
  332. }
  333. }
  334. }
  335. fn with_workspace<F>(&mut self, workspace_id: &str, f: F) -> SyncResult<Option<FolderChangeset>>
  336. where
  337. F: FnOnce(&mut WorkspaceRevision) -> SyncResult<Option<()>>,
  338. {
  339. self.modify_workspaces(|workspaces| {
  340. if let Some(workspace) = workspaces.iter_mut().find(|workspace| workspace_id == workspace.id) {
  341. f(Arc::make_mut(workspace))
  342. } else {
  343. tracing::warn!("[FolderPad]: Can't find any workspace with id: {}", workspace_id);
  344. Ok(None)
  345. }
  346. })
  347. }
  348. fn with_trash<F>(&mut self, f: F) -> SyncResult<Option<FolderChangeset>>
  349. where
  350. F: FnOnce(&mut Vec<Arc<TrashRevision>>) -> SyncResult<Option<()>>,
  351. {
  352. let cloned_self = self.clone();
  353. match f(&mut self.folder_rev.trash)? {
  354. None => Ok(None),
  355. Some(_) => {
  356. let old = cloned_self.to_json()?;
  357. let new = self.to_json()?;
  358. match cal_diff::<EmptyAttributes>(old, new) {
  359. None => Ok(None),
  360. Some(operations) => {
  361. self.operations = self.operations.compose(&operations)?;
  362. Ok(Some(FolderChangeset {
  363. operations,
  364. md5: self.folder_md5(),
  365. }))
  366. }
  367. }
  368. }
  369. }
  370. }
  371. fn with_app<F>(&mut self, app_id: &str, f: F) -> SyncResult<Option<FolderChangeset>>
  372. where
  373. F: FnOnce(&mut AppRevision) -> SyncResult<Option<()>>,
  374. {
  375. let workspace_id = match self
  376. .folder_rev
  377. .workspaces
  378. .iter()
  379. .find(|workspace| workspace.apps.iter().any(|app| app.id == app_id))
  380. {
  381. None => {
  382. tracing::warn!("[FolderPad]: Can't find any app with id: {}", app_id);
  383. return Ok(None);
  384. }
  385. Some(workspace) => workspace.id.clone(),
  386. };
  387. self.with_workspace(&workspace_id, |workspace| {
  388. // It's ok to unwrap because we get the workspace from the app_id.
  389. f(workspace.apps.iter_mut().find(|app| app_id == app.id).unwrap())
  390. })
  391. }
  392. fn with_view<F>(&mut self, belong_to_id: &str, view_id: &str, f: F) -> SyncResult<Option<FolderChangeset>>
  393. where
  394. F: FnOnce(&mut ViewRevision) -> SyncResult<Option<()>>,
  395. {
  396. self.with_app(belong_to_id, |app| {
  397. match app.belongings.iter_mut().find(|view| view_id == view.id) {
  398. None => {
  399. tracing::warn!("[FolderPad]: Can't find any view with id: {}", view_id);
  400. Ok(None)
  401. }
  402. Some(view) => f(view),
  403. }
  404. })
  405. }
  406. }
  407. pub fn default_folder_operations() -> FolderOperations {
  408. FolderOperationsBuilder::new()
  409. .insert(r#"{"workspaces":[],"trash":[]}"#)
  410. .build()
  411. }
  412. pub fn initial_folder_operations(folder_pad: &FolderPad) -> SyncResult<FolderOperations> {
  413. let json = folder_pad.to_json()?;
  414. let operations = FolderOperationsBuilder::new().insert(&json).build();
  415. Ok(operations)
  416. }
  417. pub struct FolderChangeset {
  418. pub operations: FolderOperations,
  419. /// md5: the md5 of the FolderPad's operations after applying the change.
  420. pub md5: String,
  421. }
  422. #[cfg(test)]
  423. mod tests {
  424. #![allow(clippy::all)]
  425. use crate::client_folder::folder_pad::FolderPad;
  426. use crate::client_folder::{FolderOperations, FolderOperationsBuilder};
  427. use chrono::Utc;
  428. use folder_model::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision};
  429. use lib_ot::core::OperationTransform;
  430. use serde::Deserialize;
  431. #[test]
  432. fn folder_add_workspace() {
  433. let (mut folder, initial_operations, _) = test_folder();
  434. let _time = Utc::now();
  435. let mut workspace_1 = WorkspaceRevision::default();
  436. workspace_1.name = "My first workspace".to_owned();
  437. let operations_1 = folder.create_workspace(workspace_1).unwrap().unwrap().operations;
  438. let mut workspace_2 = WorkspaceRevision::default();
  439. workspace_2.name = "My second workspace".to_owned();
  440. let operations_2 = folder.create_workspace(workspace_2).unwrap().unwrap().operations;
  441. let folder_from_operations = make_folder_from_operations(initial_operations, vec![operations_1, operations_2]);
  442. assert_eq!(folder, folder_from_operations);
  443. }
  444. #[test]
  445. fn folder_deserialize_invalid_json_test() {
  446. for json in vec![
  447. // No timestamp
  448. r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}"#,
  449. // Trailing characters
  450. r#"{"workspaces":[{"id":"1","name":"first workspace","desc":"","apps":[]}],"trash":[]}123"#,
  451. ] {
  452. let mut deserializer = serde_json::Deserializer::from_reader(json.as_bytes());
  453. let folder_rev = FolderRevision::deserialize(&mut deserializer).unwrap();
  454. assert_eq!(folder_rev.workspaces.first().as_ref().unwrap().name, "first workspace");
  455. }
  456. }
  457. #[test]
  458. fn folder_update_workspace() {
  459. let (mut folder, initial_operation, workspace) = test_folder();
  460. assert_folder_equal(
  461. &folder,
  462. &make_folder_from_operations(initial_operation.clone(), vec![]),
  463. r#"{"workspaces":[{"id":"1","name":"😁 my first workspace","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
  464. );
  465. let operations = folder
  466. .update_workspace(&workspace.id, Some("☺️ rename workspace️".to_string()), None)
  467. .unwrap()
  468. .unwrap()
  469. .operations;
  470. let folder_from_operations = make_folder_from_operations(initial_operation, vec![operations]);
  471. assert_folder_equal(
  472. &folder,
  473. &folder_from_operations,
  474. r#"{"workspaces":[{"id":"1","name":"☺️ rename workspace️","desc":"","apps":[],"modified_time":0,"create_time":0}],"trash":[]}"#,
  475. );
  476. }
  477. #[test]
  478. fn folder_add_app() {
  479. let (folder, initial_operations, _app) = test_app_folder();
  480. let folder_from_operations = make_folder_from_operations(initial_operations, vec![]);
  481. assert_eq!(folder, folder_from_operations);
  482. assert_folder_equal(
  483. &folder,
  484. &folder_from_operations,
  485. r#"{
  486. "workspaces": [
  487. {
  488. "id": "1",
  489. "name": "😁 my first workspace",
  490. "desc": "",
  491. "apps": [
  492. {
  493. "id": "",
  494. "workspace_id": "1",
  495. "name": "😁 my first app",
  496. "desc": "",
  497. "belongings": [],
  498. "version": 0,
  499. "modified_time": 0,
  500. "create_time": 0
  501. }
  502. ],
  503. "modified_time": 0,
  504. "create_time": 0
  505. }
  506. ],
  507. "trash": []
  508. }"#,
  509. );
  510. }
  511. #[test]
  512. fn folder_update_app() {
  513. let (mut folder, initial_operations, app) = test_app_folder();
  514. let operations = folder
  515. .update_app(&app.id, Some("🤪 rename app".to_owned()), None)
  516. .unwrap()
  517. .unwrap()
  518. .operations;
  519. let new_folder = make_folder_from_operations(initial_operations, vec![operations]);
  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. {
  531. "id": "",
  532. "workspace_id": "1",
  533. "name": "🤪 rename app",
  534. "desc": "",
  535. "belongings": [],
  536. "version": 0,
  537. "modified_time": 0,
  538. "create_time": 0
  539. }
  540. ],
  541. "modified_time": 0,
  542. "create_time": 0
  543. }
  544. ],
  545. "trash": []
  546. }"#,
  547. );
  548. }
  549. #[test]
  550. fn folder_delete_app() {
  551. let (mut folder, initial_operations, app) = test_app_folder();
  552. let operations = folder.delete_app(&app.id).unwrap().unwrap().operations;
  553. let new_folder = make_folder_from_operations(initial_operations, vec![operations]);
  554. assert_folder_equal(
  555. &folder,
  556. &new_folder,
  557. r#"{
  558. "workspaces": [
  559. {
  560. "id": "1",
  561. "name": "😁 my first workspace",
  562. "desc": "",
  563. "apps": [],
  564. "modified_time": 0,
  565. "create_time": 0
  566. }
  567. ],
  568. "trash": []
  569. }"#,
  570. );
  571. }
  572. #[test]
  573. fn folder_add_view() {
  574. let (folder, initial_operations, _view) = test_view_folder();
  575. assert_folder_equal(
  576. &folder,
  577. &make_folder_from_operations(initial_operations, vec![]),
  578. r#"
  579. {
  580. "workspaces": [
  581. {
  582. "id": "1",
  583. "name": "😁 my first workspace",
  584. "desc": "",
  585. "apps": [
  586. {
  587. "id": "",
  588. "workspace_id": "1",
  589. "name": "😁 my first app",
  590. "desc": "",
  591. "belongings": [
  592. {
  593. "id": "",
  594. "belong_to_id": "",
  595. "name": "🎃 my first view",
  596. "desc": "",
  597. "view_type": "Blank",
  598. "version": 0,
  599. "belongings": [],
  600. "modified_time": 0,
  601. "create_time": 0
  602. }
  603. ],
  604. "version": 0,
  605. "modified_time": 0,
  606. "create_time": 0
  607. }
  608. ],
  609. "modified_time": 0,
  610. "create_time": 0
  611. }
  612. ],
  613. "trash": []
  614. }"#,
  615. );
  616. }
  617. #[test]
  618. fn folder_update_view() {
  619. let (mut folder, initial_operations, view) = test_view_folder();
  620. let operations = folder
  621. .update_view(&view.id, Some("😦 rename view".to_owned()), None, 123)
  622. .unwrap()
  623. .unwrap()
  624. .operations;
  625. let new_folder = make_folder_from_operations(initial_operations, vec![operations]);
  626. assert_folder_equal(
  627. &folder,
  628. &new_folder,
  629. r#"{
  630. "workspaces": [
  631. {
  632. "id": "1",
  633. "name": "😁 my first workspace",
  634. "desc": "",
  635. "apps": [
  636. {
  637. "id": "",
  638. "workspace_id": "1",
  639. "name": "😁 my first app",
  640. "desc": "",
  641. "belongings": [
  642. {
  643. "id": "",
  644. "belong_to_id": "",
  645. "name": "😦 rename view",
  646. "desc": "",
  647. "view_type": "Blank",
  648. "version": 0,
  649. "belongings": [],
  650. "modified_time": 123,
  651. "create_time": 0
  652. }
  653. ],
  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_delete_view() {
  669. let (mut folder, initial_operations, view) = test_view_folder();
  670. let operations = folder.delete_view(&view.app_id, &view.id).unwrap().unwrap().operations;
  671. let new_folder = make_folder_from_operations(initial_operations, vec![operations]);
  672. assert_folder_equal(
  673. &folder,
  674. &new_folder,
  675. r#"{
  676. "workspaces": [
  677. {
  678. "id": "1",
  679. "name": "😁 my first workspace",
  680. "desc": "",
  681. "apps": [
  682. {
  683. "id": "",
  684. "workspace_id": "1",
  685. "name": "😁 my first app",
  686. "desc": "",
  687. "belongings": [],
  688. "version": 0,
  689. "modified_time": 0,
  690. "create_time": 0
  691. }
  692. ],
  693. "modified_time": 0,
  694. "create_time": 0
  695. }
  696. ],
  697. "trash": []
  698. }"#,
  699. );
  700. }
  701. #[test]
  702. fn folder_add_trash() {
  703. let (folder, initial_operations, _trash) = test_trash();
  704. assert_folder_equal(
  705. &folder,
  706. &make_folder_from_operations(initial_operations, vec![]),
  707. r#"{
  708. "workspaces": [],
  709. "trash": [
  710. {
  711. "id": "1",
  712. "name": "🚽 my first trash",
  713. "modified_time": 0,
  714. "create_time": 0,
  715. "ty": 0
  716. }
  717. ]
  718. }
  719. "#,
  720. );
  721. }
  722. #[test]
  723. fn folder_delete_trash() {
  724. let (mut folder, initial_operations, trash) = test_trash();
  725. let operations = folder.delete_trash(Some(vec![trash.id])).unwrap().unwrap().operations;
  726. assert_folder_equal(
  727. &folder,
  728. &make_folder_from_operations(initial_operations, vec![operations]),
  729. r#"{
  730. "workspaces": [],
  731. "trash": []
  732. }
  733. "#,
  734. );
  735. }
  736. fn test_folder() -> (FolderPad, FolderOperations, WorkspaceRevision) {
  737. let folder_rev = FolderRevision::default();
  738. let folder_json = serde_json::to_string(&folder_rev).unwrap();
  739. let mut operations = FolderOperationsBuilder::new().insert(&folder_json).build();
  740. let mut workspace_rev = WorkspaceRevision::default();
  741. workspace_rev.name = "😁 my first workspace".to_owned();
  742. workspace_rev.id = "1".to_owned();
  743. let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap();
  744. operations = operations
  745. .compose(
  746. &folder
  747. .create_workspace(workspace_rev.clone())
  748. .unwrap()
  749. .unwrap()
  750. .operations,
  751. )
  752. .unwrap();
  753. (folder, operations, workspace_rev)
  754. }
  755. fn test_app_folder() -> (FolderPad, FolderOperations, AppRevision) {
  756. let (mut folder_rev, mut initial_operations, workspace) = test_folder();
  757. let mut app_rev = AppRevision::default();
  758. app_rev.workspace_id = workspace.id;
  759. app_rev.name = "😁 my first app".to_owned();
  760. initial_operations = initial_operations
  761. .compose(&folder_rev.create_app(app_rev.clone()).unwrap().unwrap().operations)
  762. .unwrap();
  763. (folder_rev, initial_operations, app_rev)
  764. }
  765. fn test_view_folder() -> (FolderPad, FolderOperations, ViewRevision) {
  766. let (mut folder, mut initial_operations, app) = test_app_folder();
  767. let mut view_rev = ViewRevision::default();
  768. view_rev.app_id = app.id.clone();
  769. view_rev.name = "🎃 my first view".to_owned();
  770. initial_operations = initial_operations
  771. .compose(&folder.create_view(view_rev.clone()).unwrap().unwrap().operations)
  772. .unwrap();
  773. (folder, initial_operations, view_rev)
  774. }
  775. fn test_trash() -> (FolderPad, FolderOperations, TrashRevision) {
  776. let folder_rev = FolderRevision::default();
  777. let folder_json = serde_json::to_string(&folder_rev).unwrap();
  778. let mut operations = FolderOperationsBuilder::new().insert(&folder_json).build();
  779. let mut trash_rev = TrashRevision::default();
  780. trash_rev.name = "🚽 my first trash".to_owned();
  781. trash_rev.id = "1".to_owned();
  782. let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap();
  783. operations = operations
  784. .compose(
  785. &folder
  786. .create_trash(vec![trash_rev.clone().into()])
  787. .unwrap()
  788. .unwrap()
  789. .operations,
  790. )
  791. .unwrap();
  792. (folder, operations, trash_rev)
  793. }
  794. fn make_folder_from_operations(
  795. mut initial_operation: FolderOperations,
  796. operations: Vec<FolderOperations>,
  797. ) -> FolderPad {
  798. for operation in operations {
  799. initial_operation = initial_operation.compose(&operation).unwrap();
  800. }
  801. FolderPad::from_operations(initial_operation).unwrap()
  802. }
  803. fn assert_folder_equal(old: &FolderPad, new: &FolderPad, expected: &str) {
  804. assert_eq!(old, new);
  805. let json1 = old.to_json().unwrap();
  806. let json2 = new.to_json().unwrap();
  807. // format the json str
  808. let folder_rev: FolderRevision = serde_json::from_str(expected).unwrap();
  809. let expected = serde_json::to_string(&folder_rev).unwrap();
  810. assert_eq!(json1, expected);
  811. assert_eq!(json1, json2);
  812. }
  813. }