web: worker-route WBLOCK/SAVEALL/ARCHIVE; close the follow-up review

WBLOCK, SAVEALL, and web ARCHIVE now serialize through the Web Worker
like SAVE/AUTOSAVE: prepare on the main thread, writer pass on the
worker, inline fallback on any worker failure. SAVEALL clears each
tab's dirty flag under the same unchanged-tab race guard the single
save uses; ARCHIVE assembles the ZIP in the completion handler so the
transmittal matches the snapshot taken at command time.

The review of the previous fix commit found the collision refusal
still REPLACED the registry bytes — the exact silent retargeting it
was meant to prevent. The refusal now keeps the old registration, the
extraction settings picker reports refused picks in the wizard's error
slot (previously silent, and the stale collision flag could surface as
a wrong-file warning on an unrelated browse), and the xref registry's
same-name ambiguity is documented as a browser fundamental (native
distinguishes by folder): newest registration wins consistently for
resolution, ARCHIVE, and XRELOAD — matching what the drawing displays.
Also: the Quit pending-close survives a successful web tab-save
(continue the quit instead of dropping it), the last ZIP64 sentinel
(central size) is rejected like the other four, and the new behaviors
gained regression tests (collision keeps old bytes, registry eviction,
including the deadlock fix for the guard-scoping that test exposed).

(cherry picked from commit b635d56c)

Ported onto main (#1717 review): ARCHIVE and SAVEALL serialize
document_for_save() like a single web Save; the ZIP is built with the
zip crate; WBLOCK keeps its native background-task title.
This commit is contained in:
Afonsop authored and Hakan Seven committed 2026-10-12 12:02:04 +03:00
1 parent 8968e79942
commit 108ef045b8
6 files changed
+406 -81

No files matched your search

+70 -22
View File
@@ -82,8 +82,9 @@ impl OpenCADStudio {
{
// Web: every dirty saved tab is serialized and downloaded
// in one pass — see `saveall_web` for the flow.
self.saveall_web();
return Some(self.saveall_web());
}
#[cfg(not(target_arch = "wasm32"))]
return Some(Task::none());
}
"SAVEAS" => return Some(Task::done(Message::SaveAs)),
@@ -243,7 +244,13 @@ impl OpenCADStudio {
}
}
#[cfg(target_arch = "wasm32")]
self.archive_web(i, cmd);
{
// The drawing serialization runs on the serializer
// worker; the ZIP assembly + download continue in the
// completion message (`archive_web` below).
let task = self.archive_web(i, cmd);
return Some(Task::batch([task, self.finish_dispatch(cmd)]));
}
}
"EXIT" | "QUIT" => {
@@ -564,12 +571,12 @@ fn parse_background_color(args: &[&str]) -> Option<[f32; 4]> {
// silently dropped, the web counterpart of native's missing-file skip.
#[cfg(target_arch = "wasm32")]
impl OpenCADStudio {
pub(in crate::app) fn archive_web(&mut self, i: usize, cmd: &str) {
pub(in crate::app) fn archive_web(&mut self, i: usize, cmd: &str) -> Task<Message> {
let Some(src) = self.tabs[i].current_path.clone() else {
self.command_line.push_error(
crate::t!("ARCHIVE: save the drawing first (it has no file path yet).").as_ref(),
);
return;
return Task::none();
};
let stem = src
.file_stem()
@@ -593,20 +600,12 @@ impl OpenCADStudio {
self.stamp_header_sysvars(i);
self.sync_solid_models_for_save(i);
self.tabs[i].scene.sync_native_parametric_graph();
// The drawing itself: the same serialization a web Save performs
// (native copies the last-saved file; the web build has none, so the
// current document is re-serialized).
let document = self.tabs[i].scene.document_for_save();
let drawing = match crate::io::save_to_bytes(&document, &ext, version) {
Ok(bytes) => bytes,
Err(e) => {
self.command_line.push_error(
crate::tf!("{cmd}: cannot package the drawing ({e}).").as_ref(),
);
return;
}
};
let mut entries: Vec<(String, Vec<u8>)> = vec![(filename, drawing)];
// The dependency set (xrefs, raster images, PDF underlays, plot
// tables) is collected here, at command time — cheap in-memory
// registry reads — so the transmittal matches the drawing snapshot
// serialized below even if the tab changes while the writer runs.
let mut deps: Vec<(String, Vec<u8>)> = Vec::new();
let mut skipped: Vec<String> = Vec::new();
// Xrefs — the block-record walk native copies; the bytes come from
@@ -619,7 +618,7 @@ impl OpenCADStudio {
.and_then(|key| crate::io::xref::registered_bytes(&key))
{
Some(bytes) => push_entry(
&mut entries,
&mut deps,
transmit_file_name(&br.xref_path),
(*bytes).clone(),
),
@@ -636,7 +635,7 @@ impl OpenCADStudio {
}
match crate::scene::model::image_model::registered_source_bytes(&img.file_path) {
Some(bytes) => push_entry(
&mut entries,
&mut deps,
transmit_file_name(&img.file_path),
(*bytes).clone(),
),
@@ -655,7 +654,7 @@ impl OpenCADStudio {
}
match crate::scene::model::pdf_raster::registered_source_bytes(&def.file_path) {
Some(bytes) => push_entry(
&mut entries,
&mut deps,
transmit_file_name(&def.file_path),
(*bytes).clone(),
),
@@ -690,12 +689,60 @@ impl OpenCADStudio {
continue;
}
match crate::io::plot_style::session_table(&name) {
Some(bytes) => push_entry(&mut entries, name, (*bytes).clone()),
Some(bytes) => push_entry(&mut deps, name, (*bytes).clone()),
None => push_skipped(&mut skipped, &name),
}
}
// The drawing itself: the same worker-routed serialization a web
// Save performs (native copies the last-saved file; the web build
// has none, so the current document is re-serialized). The ZIP
// assembly and download are cheap and stay on the main thread —
// they continue in the completion message.
let zip_name = format!("{stem}_transmit.zip");
let prepared =
crate::io::prepare_document_for_save(self.tabs[i].scene.document_for_save(), version);
let cmd = cmd.to_string();
Task::perform(
async move { crate::io::serialize_prepared_document(&prepared, &ext).await },
move |result| Message::WebArchiveSerialized {
cmd,
zip_name,
filename,
deps,
skipped,
result,
},
)
}
/// Web ARCHIVE completion: the serializer worker (or the inline
/// fallback) produced the drawing bytes. Assemble the transmittal ZIP —
/// the drawing entry first, then the dependencies under the same
/// name-collision rule the synchronous pass used — download it and
/// report, including the skipped-dependency lines.
pub(in crate::app) fn on_web_archive_serialized(
&mut self,
cmd: String,
zip_name: String,
filename: String,
deps: Vec<(String, Vec<u8>)>,
skipped: Vec<String>,
result: Result<Vec<u8>, String>,
) -> Task<Message> {
let drawing = match result {
Ok(bytes) => bytes,
Err(e) => {
self.command_line.push_error(
crate::tf!("{cmd}: cannot package the drawing ({e}).").as_ref(),
);
return Task::none();
}
};
let mut entries: Vec<(String, Vec<u8>)> = vec![(filename, drawing)];
for (name, data) in deps {
push_entry(&mut entries, name, data);
}
match build_zip(&entries) {
Ok(zip) => {
crate::sys::download_bytes(&zip_name, &zip);
@@ -717,6 +764,7 @@ impl OpenCADStudio {
.as_ref(),
);
}
Task::none()
}
}