diff --git a/crates/ui-egui/src/placing.rs b/crates/ui-egui/src/placing.rs index 294f22770..7bdc1f9d6 100644 --- a/crates/ui-egui/src/placing.rs +++ b/crates/ui-egui/src/placing.rs @@ -137,6 +137,9 @@ pub struct Pending { /// The number of the paste's chain ([`Placing::chains`]). chain: Option, done: Receiver>, + /// The automation roots in force when the read started, kept in force when it is placed: a + /// DXF placed against an artboard that changed meanwhile is read again then. + scope: ScopeHandle, } /// A read waiting for the thread that reads the files. @@ -318,6 +321,7 @@ fn spawn_labeled( let (tx, done) = channel(); // The automation roots in force here are in force there. let scope = ScopeHandle::capture(); + let completion = scope.clone(); #[cfg(test)] let reading = app.placing.reading.clone(); let job: Job = Box::new(move || { @@ -352,7 +356,7 @@ fn spawn_labeled( let _ = reader.send(job); app.placing.reader = Some(reader); } - app.placing.pending.push(Pending { name, label: label.clone(), doc, dropped, paste: chain.is_some(), chain, done }); + app.placing.pending.push(Pending { name, label: label.clone(), doc, dropped, paste: chain.is_some(), chain, done, scope: completion }); let status = format!("{label}…"); app.status(status.clone()); Ok(Some(json!({ "background": true, "status": status }))) @@ -384,7 +388,7 @@ pub fn poll(app: &mut VectorcraftApp) { if app.ui.status == format!("{}…", p.label) { app.ui.status.clear(); } - finish(app, &p, r); + p.scope.clone().run(|| finish(app, &p, r)); if let Some(chain) = p.chain && !app.placing.swatch_paste.as_ref().is_some_and(|waiting| waiting.chain == chain) { diff --git a/crates/ui-egui/src/placing/tests.rs b/crates/ui-egui/src/placing/tests.rs index 90d1a551a..fcd209016 100644 --- a/crates/ui-egui/src/placing/tests.rs +++ b/crates/ui-egui/src/placing/tests.rs @@ -1284,3 +1284,42 @@ fn a_file_read_between_a_tools_clicks_is_placed() { assert_eq!(app.session.tool_busy(), !other_document, "other document: {other_document}"); } } + +/// A DXF placed within automation roots is read again within them when it is placed against an +/// artboard that changed meanwhile: a path redirected outside the roots after the read is refused. +#[cfg(unix)] +#[test] +fn a_file_read_again_when_placed_stays_within_the_roots_it_was_read_in() { + let mut src = Session::new(); + src.execute("file.new", &json!({"width": 100, "height": 50})).unwrap(); + src.execute("shape.rectangle", &json!({"x": 5, "y": 5, "width": 20, "height": 10})).unwrap(); + let v = src.execute("document.serialize", &json!({"format": "dxf"})).unwrap(); + let dxf = vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap(); + let base = std::env::temp_dir().join(format!("vc-placing-reread-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&base); + for d in ["in", "out"] { + std::fs::create_dir_all(base.join(d)).unwrap(); + std::fs::write(base.join(d).join("real.dxf"), &dxf).unwrap(); + } + let link = base.join("in").join("plan.dxf"); + std::os::unix::fs::symlink(base.join("in").join("real.dxf"), &link).unwrap(); + let roots = vectorcraft_engine::file_access::AutomationRoots::new(&[&base.join("in")], &[]).unwrap().unwrap(); + let mut app = app(true); + let p = json!({"path": link.to_string_lossy(), "dxf": {"fit": true}, "at": [100, 150]}); + vectorcraft_engine::file_access::confine(Some(&roots), || start(&mut app, &p, false)).unwrap(); + let t0 = std::time::Instant::now(); + while !(app.placing.reading[1].load(std::sync::atomic::Ordering::SeqCst) == 1 + && app.placing.reading[0].load(std::sync::atomic::Ordering::SeqCst) == 0) + { + assert!(t0.elapsed().as_secs() < 60, "read within 60 s"); + std::thread::yield_now(); + } + // Read; now the path leads outside the roots and the artboard under `at` changes. + std::fs::remove_file(&link).unwrap(); + std::os::unix::fs::symlink(base.join("out").join("real.dxf"), &link).unwrap(); + app.run("artboard.setProps", json!({"index": 0, "width": 200})).unwrap(); + placed(&mut app); + assert_eq!(app.session.doc().unwrap().doc.layers.iter().flat_map(|l| l.children().into_iter().flatten()).count(), 0, "nothing placed"); + assert!(app.ui.status.contains("outside the read root"), "{}", app.ui.status); + let _ = std::fs::remove_dir_all(base); +}