authorclover caruso <git@paperclover.net> 2026-10-10 19:53:09-07:00
committerclover caruso <git@paperclover.net> 2026-10-10 22:56:05-07:00
logfd02000fb5fe394ee268e31464c27af71d189e99
tree5c2c5c0f124e47157c3248c77784627351c4c4b0
parente7e347121c236326f4a147b72e90b321d81adebc
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU (~clover)

fix(desktop): skip section prefetch during structure changes

Hold the notebook structure guard while section prefetch opens its replica and reads an owned page. Optional prefetch skips busy notebooks, preventing queued native jobs from reopening replicas during folder rename. Add a real notebook regression proving a guarded prefetch does not create its section replica. Fixes #109 Assisted-by: gpt-6.1-sol

1 files changed, 44 insertions(+), 0 deletions(-)

crates/snowbound/src/prefetch.rs+44
......@@ -208,6 +208,9 @@ fn prepare(job: Job, layouts: &Mutex<TextEngine>, scenes: &Scenes) -> Result<(),
208208 last,
209209 notify,
210210 } => {
211 let Some(_structure) = library.structure.try_lock() else {
212 return Ok(());
213 };
211214 let replica = library.prefetch(&path, notify)?;
212215 let pages = replica.pages()?;
213216 let space = last
......@@ -360,6 +363,47 @@ impl State {
360363mod tests {
361364 use super::*;
362365
366 #[test]
367 fn section_prefetch_skips_while_notebook_structure_changes() {
368 use notebook::session::Notebook;
369
370 let root = std::env::temp_dir().join(format!("snowbound-prefetch-{}", std::process::id()));
371 let _ = notebook::fs::remove_dir_all(&root);
372 notebook::fs::create_dir_all(&root).unwrap();
373 let cache = root.join("cache");
374 let page = onestore::PageCreation::new(None, Some("original"), "Fixture").unwrap();
375 let notebook =
376 Notebook::create(root.join("Personal"), &cache, Notebook::NEW_COLOR, &page).unwrap();
377 let path = "New Section 1.one";
378 let replica = notebook.replica_path(path).unwrap();
379 drop(notebook);
380 let library = Arc::new(Library::notebook(
381 root.join("Personal").to_str().unwrap(),
382 &cache,
383 ));
384 if let Some(background) = &library.background {
385 background.stop();
386 }
387 assert!(notebook::fs::metadata(&replica).is_err());
388 {
389 let _changing = library.structure.try_lock().unwrap();
390 prepare(
391 Job::Section {
392 library: Arc::clone(&library),
393 path: path.into(),
394 last: None,
395 notify: Box::new(|| {}),
396 },
397 &Mutex::new(TextEngine::default()),
398 &Scenes::new(Mutex::new(Recent::new(SCENES))),
399 )
400 .unwrap();
401 assert!(notebook::fs::metadata(&replica).is_err());
402 }
403 drop(library);
404 notebook::fs::remove_dir_all(root).unwrap();
405 }
406
363407 #[test]
364408 fn the_least_recent_go_first_past_the_count() {
365409 let mut recent = Recent::new(2);