| ... | @@ -411,7 +411,7 @@ impl Site { | ... | @@ -411,7 +411,7 @@ impl Site { |
| 411 | /// Does `change` to the section's pages, `shown` the page open; `None` where what it | 411 | /// Does `change` to the section's pages, `shown` the page open; `None` where what it |
| 412 | /// changes is gone or holds what it did not make, which it leaves alone. | 412 | /// changes is gone or holds what it did not make, which it leaves alone. |
| 413 | async fn change( | 413 | async fn change( |
| 414 | &self, | 414 | &mut self, |
| 415 | change: Change, | 415 | change: Change, |
| 416 | shown: ExGuid, | 416 | shown: ExGuid, |
| 417 | ) -> Result<Option<Applied>, Box<dyn Error>> { | 417 | ) -> Result<Option<Applied>, Box<dyn Error>> { |
| ... | @@ -454,10 +454,9 @@ impl Site { | ... | @@ -454,10 +454,9 @@ impl Site { |
| 454 | let (show, added) = self.after(&listed, &pages, show, &mut ops); | 454 | let (show, added) = self.after(&listed, &pages, show, &mut ops); |
| 455 | if !created && binned { | 455 | if !created && binned { |
| 456 | let pages: Vec<Page> = kept.iter().map(|kept| kept.page.clone()).collect(); | 456 | let pages: Vec<Page> = kept.iter().map(|kept| kept.page.clone()).collect(); |
| 457 | self.library | 457 | let mut notebook = self.library.reopen()?; |
| 458 | .reopen()? | 458 | notebook.recycle_pages_async(&pages, &self.author).await?; |
| 459 | .recycle_pages_async(&pages, &self.author) | 459 | self.library = Arc::new(self.library.with(notebook)); |
| 460 | .await?; | | |
| 461 | self.replica.written_async().await?; | 460 | self.replica.written_async().await?; |
| 462 | } | 461 | } |
| 463 | self.apply(ops).await?; | 462 | self.apply(ops).await?; |
| ... | @@ -508,10 +507,9 @@ impl Site { | ... | @@ -508,10 +507,9 @@ impl Site { |
| 508 | if !created && binned { | 507 | if !created && binned { |
| 509 | let identities: Vec<[u8; 16]> = | 508 | let identities: Vec<[u8; 16]> = |
| 510 | back.iter().filter_map(|kept| kept.page.identity).collect(); | 509 | back.iter().filter_map(|kept| kept.page.identity).collect(); |
| 511 | self.library | 510 | let mut notebook = self.library.reopen()?; |
| 512 | .reopen()? | 511 | notebook.unrecycle_pages_async(&identities).await?; |
| 513 | .unrecycle_pages_async(&identities) | 512 | self.library = Arc::new(self.library.with(notebook)); |
| 514 | .await?; | | |
| 515 | } | 513 | } |
| 516 | Applied { | 514 | Applied { |
| 517 | undo: Change::Delete { | 515 | undo: Change::Delete { |
| ... | @@ -1062,13 +1060,29 @@ impl State { | ... | @@ -1062,13 +1060,29 @@ impl State { |
| 1062 | }; | 1060 | }; |
| 1063 | self.persist()?; | 1061 | self.persist()?; |
| 1064 | let session = self.session.as_ref().ok_or("No section is open")?; | 1062 | let session = self.session.as_ref().ok_or("No section is open")?; |
| 1065 | let site = Site { | 1063 | let changing = if matches!( |
| | 1064 | &change, |
| | 1065 | Change::Delete { created: false, .. } | Change::Restore { created: false, .. } |
| | 1066 | ) && matches!(session.library.notebook, Ok(Some(_))) |
| | 1067 | { |
| | 1068 | Some( |
| | 1069 | session |
| | 1070 | .library |
| | 1071 | .structure |
| | 1072 | .try_lock_arc() |
| | 1073 | .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::WouldBlock))?, |
| | 1074 | ) |
| | 1075 | } else { |
| | 1076 | None |
| | 1077 | }; |
| | 1078 | let mut site = Site { |
| 1066 | replica: Arc::clone(session.section.replica()), | 1079 | replica: Arc::clone(session.section.replica()), |
| 1067 | library: Arc::clone(&session.library), | 1080 | library: Arc::clone(&session.library), |
| 1068 | author: self.author.clone(), | 1081 | author: self.author.clone(), |
| 1069 | fresh: self.dated_page(None, "")?, | 1082 | fresh: self.dated_page(None, "")?, |
| 1070 | }; | 1083 | }; |
| 1071 | let shown = session.space; | 1084 | let shown = session.space; |
| | 1085 | let original = Arc::clone(&site.library); |
| 1072 | let proxy = self.proxy.clone(); | 1086 | let proxy = self.proxy.clone(); |
| 1073 | self.undo.busy += 1; | 1087 | self.undo.busy += 1; |
| 1074 | self.switching = Some((None, web_time::Instant::now())); | 1088 | self.switching = Some((None, web_time::Instant::now())); |
| ... | @@ -1080,8 +1094,34 @@ impl State { | ... | @@ -1080,8 +1094,34 @@ impl State { |
| 1080 | }; | 1094 | }; |
| 1081 | let failed = applied.as_ref().err().map(ToString::to_string); | 1095 | let failed = applied.as_ref().err().map(ToString::to_string); |
| 1082 | let (stale, applied) = (failed.is_none(), applied.ok().flatten()); | 1096 | let (stale, applied) = (failed.is_none(), applied.ok().flatten()); |
| | 1097 | let library = Arc::clone(&site.library); |
| 1083 | let _ = proxy.send_event(UserEvent::Then(Box::new(move |state: &mut State| { | 1098 | let _ = proxy.send_event(UserEvent::Then(Box::new(move |state: &mut State| { |
| 1084 | state.undo.busy -= 1; | 1099 | state.undo.busy -= 1; |
| | 1100 | let lineage = |listed: &Library| { |
| | 1101 | listed.location == original.location |
| | 1102 | && Arc::ptr_eq(&listed.structure, &original.structure) |
| | 1103 | }; |
| | 1104 | if !Arc::ptr_eq(&original, &library) |
| | 1105 | && state.notebooks.iter().any(|listed| lineage(listed)) |
| | 1106 | { |
| | 1107 | let path = state |
| | 1108 | .session |
| | 1109 | .as_ref() |
| | 1110 | .filter(|session| lineage(&session.library)) |
| | 1111 | .map(|session| session.tabs[session.tab].path.clone()); |
| | 1112 | if let Some(path) = path { |
| | 1113 | state.adopt(Arc::clone(&library), Some(&path))?; |
| | 1114 | } else { |
| | 1115 | state.list(&library); |
| | 1116 | if state |
| | 1117 | .sectionless |
| | 1118 | .as_ref() |
| | 1119 | .is_some_and(|listed| lineage(listed)) |
| | 1120 | { |
| | 1121 | state.sectionless = Some(library); |
| | 1122 | } |
| | 1123 | } |
| | 1124 | } |
| 1085 | let Some(applied) = applied else { | 1125 | let Some(applied) = applied else { |
| 1086 | // What it would change is gone: Undo goes on to the step before. | 1126 | // What it would change is gone: Undo goes on to the step before. |
| 1087 | return match taken { | 1127 | return match taken { |
| ... | @@ -1102,6 +1142,7 @@ impl State { | ... | @@ -1102,6 +1142,7 @@ impl State { |
| 1102 | state.edited(Vec::new()); | 1142 | state.edited(Vec::new()); |
| 1103 | Ok(()) | 1143 | Ok(()) |
| 1104 | }))); | 1144 | }))); |
| | 1145 | drop(changing); |
| 1105 | if let Some(failed) = failed { | 1146 | if let Some(failed) = failed { |
| 1106 | return Err(failed.into()); | 1147 | return Err(failed.into()); |
| 1107 | } | 1148 | } |
| ... | @@ -1426,21 +1467,41 @@ mod tests { | ... | @@ -1426,21 +1467,41 @@ mod tests { |
| 1426 | fn deleted_pages_come_back_where_they_were_and_leave_the_bin() { | 1467 | fn deleted_pages_come_back_where_they_were_and_leave_the_bin() { |
| 1427 | let fixture = Fixture::new("delete", &["First", "Second", "Third"]); | 1468 | let fixture = Fixture::new("delete", &["First", "Second", "Third"]); |
| 1428 | let second = fixture.space("Second"); | 1469 | let second = fixture.space("Second"); |
| 1429 | let deleted = fixture | 1470 | let mut site = fixture.site(); |
| 1430 | .change(Change::Delete { | 1471 | assert!(site.library.tabs(crate::recycle::BIN).is_empty()); |
| | 1472 | let deleted = pollster::block_on(site.change( |
| | 1473 | Change::Delete { |
| 1431 | pages: vec![second], | 1474 | pages: vec![second], |
| 1432 | show: None, | 1475 | show: None, |
| 1433 | created: false, | 1476 | created: false, |
| 1434 | }) | 1477 | }, |
| 1435 | .unwrap(); | 1478 | fixture.space("First"), |
| | 1479 | )) |
| | 1480 | .unwrap() |
| | 1481 | .unwrap(); |
| | 1482 | assert!(!site.library.tabs(crate::recycle::BIN).is_empty()); |
| | 1483 | assert!(!Arc::ptr_eq(&site.library, &fixture.library)); |
| | 1484 | assert!(Arc::ptr_eq( |
| | 1485 | &site.library.structure, |
| | 1486 | &fixture.library.structure |
| | 1487 | )); |
| | 1488 | assert!(Arc::ptr_eq(&site.replica, fixture.section().replica())); |
| | 1489 | assert!(fixture.library.tabs(crate::recycle::BIN).is_empty()); |
| | 1490 | let deleted_library = Arc::clone(&site.library); |
| 1436 | assert_eq!(fixture.titles(), titled(&["First", "Third"])); | 1491 | assert_eq!(fixture.titles(), titled(&["First", "Third"])); |
| 1437 | assert_eq!(deleted.show, fixture.space("Third")); | 1492 | assert_eq!(deleted.show, fixture.space("Third")); |
| 1438 | assert_eq!(fixture.binned(), ["Second"]); | 1493 | assert_eq!(fixture.binned(), ["Second"]); |
| 1439 | let restored = fixture.change(deleted.undo).unwrap(); | 1494 | let restored = pollster::block_on(site.change(deleted.undo, deleted.show)) |
| | 1495 | .unwrap() |
| | 1496 | .unwrap(); |
| | 1497 | assert!(!Arc::ptr_eq(&site.library, &deleted_library)); |
| | 1498 | assert!(!site.library.tabs(crate::recycle::BIN).is_empty()); |
| 1440 | assert_eq!(fixture.titles(), titled(&["First", "Second", "Third"])); | 1499 | assert_eq!(fixture.titles(), titled(&["First", "Second", "Third"])); |
| 1441 | assert_eq!(restored.show, fixture.space("Second")); | 1500 | assert_eq!(restored.show, fixture.space("Second")); |
| 1442 | assert!(fixture.binned().is_empty()); | 1501 | assert!(fixture.binned().is_empty()); |
| 1443 | let again = fixture.change(restored.undo).unwrap(); | 1502 | let again = pollster::block_on(site.change(restored.undo, restored.show)) |
| | 1503 | .unwrap() |
| | 1504 | .unwrap(); |
| 1444 | assert_eq!(fixture.titles(), titled(&["First", "Third"])); | 1505 | assert_eq!(fixture.titles(), titled(&["First", "Third"])); |
| 1445 | assert_eq!(fixture.binned(), ["Second"]); | 1506 | assert_eq!(fixture.binned(), ["Second"]); |
| 1446 | | 1507 | |