diff --git a/crates/notebook/src/session.rs b/crates/notebook/src/session.rs index 1121973f9355e47048787048211bbcd1003655cc..6adfb5898b32570119fe5ba05af88437d4a73134 100644 --- a/crates/notebook/src/session.rs +++ b/crates/notebook/src/session.rs @@ -1384,6 +1384,15 @@ impl Notebook { { return Err(io::Error::from(io::ErrorKind::InvalidInput).into()); } + let registered = if let (Some(identity), Some(toc)) = (identity, &self.folder(from)?.toc) { + lists( + &self.storage.read(&catalog_path(from, &toc.filename))?, + identity, + )? + .then_some(identity) + } else { + None + }; let (_, ancestor) = self.toc(folder)?; self.storage.rename(path, &target)?; if !copy { @@ -1404,7 +1413,7 @@ impl Notebook { )?; } } - if let Some(identity) = identity { + if let Some(identity) = registered { self.edit_toc(from, &[onestore::TocEdit::Remove { identity }])?; } self.refresh()?; diff --git a/crates/notebook/src/session/tests.rs b/crates/notebook/src/session/tests.rs index 25c906074b66a512b56dd3f6263bef4a961d16de..9c949a74b5b8a82c695bfad91d2fe33554e9248d 100644 --- a/crates/notebook/src/session/tests.rs +++ b/crates/notebook/src/session/tests.rs @@ -73,6 +73,119 @@ fn renaming_an_unlisted_section_creates_its_toc_entry_and_preserves_its_neighbor } } +#[test] +fn moving_discovered_entries_preserves_unregistered_source_tocs() { + for group in [false, true] { + for source_toc in ["missing", "empty", "unlisted", "registered"] { + let temporary = tempfile::tempdir().unwrap(); + let folder = temporary.path().join("notebook"); + let cache = temporary.path().join("cache"); + let page = PageCreation::new(None, Some("Garden plan"), "Fixture").unwrap(); + let mut notebook = + Notebook::create(&folder, &cache, Notebook::NEW_COLOR, &page).unwrap(); + let first = notebook.catalog().sections[0].path.clone(); + let neighbor = notebook.create_section("", "Neighbor", &page).unwrap(); + let target = notebook.create_group("", "Target").unwrap(); + let existing = notebook.create_section(&target, "Existing", &page).unwrap(); + let (entry, section, identity) = if group { + let entry = notebook.create_group("", "Source").unwrap(); + let section = notebook.create_section(&entry, "Inside", &page).unwrap(); + let identity = notebook + .folder(&entry) + .unwrap() + .toc + .as_ref() + .unwrap() + .file_id; + (entry, section, identity) + } else { + let identity = notebook.section_path(&first).unwrap().file_id; + (first.clone(), first, identity) + }; + let section_id = notebook.section_path(§ion).unwrap().file_id; + let neighbor_bytes = fs::read(folder.join(&neighbor)).unwrap(); + let existing_bytes = fs::read(folder.join(&existing)).unwrap(); + if source_toc == "unlisted" { + notebook + .edit_toc("", &[onestore::TocEdit::Remove { identity }]) + .unwrap(); + } + drop(notebook); + if source_toc == "missing" { + fs::remove_file(folder.join(TOC)).unwrap(); + } else if source_toc == "empty" { + fs::write( + folder.join(TOC), + onestore::create_table_of_contents(TOC, &[]).unwrap(), + ) + .unwrap(); + } + let before = fs::read(folder.join(TOC)).ok(); + let mut notebook = Notebook::open(&folder, &cache).unwrap(); + let order: Vec<_> = notebook + .catalog() + .order + .iter() + .filter(|path| **path != entry) + .cloned() + .collect(); + let moved = catalog_path(&target, &entry); + assert_eq!(notebook.move_entry(&entry, &target).unwrap(), moved); + assert!(!folder.join(&entry).exists()); + let moved_section = catalog_path(&target, §ion); + let check = |notebook: &Notebook| { + assert_eq!(notebook.catalog().order, order); + assert_eq!( + notebook.section_path(&moved_section).unwrap().file_id, + section_id + ); + assert!(lists(¬ebook.read_toc(&target).unwrap(), identity).unwrap()); + assert_eq!( + stored_pages(¬ebook.read_section(&moved_section).unwrap()).unwrap()[0] + .page + .title, + "Garden plan" + ); + if source_toc == "registered" { + let toc = notebook.read_toc("").unwrap(); + assert!(!lists(&toc, identity).unwrap()); + assert_eq!( + notebook.catalog().toc.as_ref().unwrap().color, + Some(Notebook::NEW_COLOR) + ); + } else { + assert_eq!(fs::read(folder.join(TOC)).ok(), before); + } + assert_eq!(fs::read(folder.join(&neighbor)).unwrap(), neighbor_bytes); + assert_eq!(fs::read(folder.join(&existing)).unwrap(), existing_bytes); + }; + check(¬ebook); + drop(notebook); + check(&Notebook::open(&folder, temporary.path().join("cold-cache")).unwrap()); + } + } +} + +#[test] +fn moving_an_entry_reads_its_registered_source_toc_before_renaming() { + let temporary = tempfile::tempdir().unwrap(); + let folder = temporary.path().join("notebook"); + let page = PageCreation::new(None, Some("Garden plan"), "Fixture").unwrap(); + let mut notebook = Notebook::create( + &folder, + temporary.path().join("cache"), + Notebook::NEW_COLOR, + &page, + ) + .unwrap(); + let section = notebook.catalog().sections[0].path.clone(); + notebook.create_group("", "Target").unwrap(); + fs::write(folder.join(TOC), b"unreadable source TOC").unwrap(); + assert!(notebook.move_entry(§ion, "Target").is_err()); + assert!(folder.join(§ion).exists()); + assert!(!folder.join("Target").join(§ion).exists()); +} + struct FaultStorage { inner: Directory, revision: Mutex>,