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<(),...@@ -208,6 +208,9 @@ fn prepare(job: Job, layouts: &Mutex<TextEngine>, scenes: &Scenes) -> Result<(),
208 last,208 last,
209 notify,209 notify,
210 } => {210 } => {
211 let Some(_structure) = library.structure.try_lock() else {
212 return Ok(());
213 };
211 let replica = library.prefetch(&path, notify)?;214 let replica = library.prefetch(&path, notify)?;
212 let pages = replica.pages()?;215 let pages = replica.pages()?;
213 let space = last216 let space = last
...@@ -360,6 +363,47 @@ impl State {...@@ -360,6 +363,47 @@ impl State {
360mod tests {363mod tests {
361 use super::*;364 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
363 #[test]407 #[test]
364 fn the_least_recent_go_first_past_the_count() {408 fn the_least_recent_go_first_past_the_count() {
365 let mut recent = Recent::new(2);409 let mut recent = Recent::new(2);