Merge #2260: a save to a missing folder writes the file and says so

The folders are made by a new writer only user-named targets use, so autosave
and a dangling symlink's target keep the strict write_atomic. Save, Save As,
Export, Plot, WBLOCK, layout templates, render and the CLI report the topmost
new folder at the command line and in createdFolder.
This commit is contained in:
Tanish Manku
2026-11-02 16:48:16 -07:00
11 changed files with 187 additions and 17 deletions
+16 -4
View File
@@ -122,10 +122,21 @@ pub(crate) fn export_window(s: &Session, framing: Framing, before: Option<&[(u64
/// Write `bytes` to `out` through the engine's atomic writer (an exclusively created sibling file
/// renamed over `out`), so a failed or interrupted write leaves the previous file untouched. An
/// existing `out` is resolved first: the rename would otherwise replace a symlink with a file and
/// leave the file it points to (`--in-place` through a link) unchanged.
pub(crate) fn write_atomic(out: &Path, bytes: &[u8]) -> Result<(), OutputError> {
/// leave the file it points to (`--in-place` through a link) unchanged. Folders missing above `out`
/// are created, and the topmost one returned for the caller to report.
pub(crate) fn write_atomic(out: &Path, bytes: &[u8]) -> Result<Option<std::path::PathBuf>, OutputError> {
let target = std::fs::canonicalize(out).unwrap_or_else(|_| out.to_path_buf());
cadcraft_engine::atomic_write::write_atomic(&target, bytes).map_err(|source| OutputError::Write { path: out.to_path_buf(), source })
let folder = cadcraft_engine::atomic_write::missing_folder(&target);
cadcraft_engine::atomic_write::write_atomic_making_folders(&target, bytes)
.map_err(|source| OutputError::Write { path: out.to_path_buf(), source })?;
Ok(folder)
}
/// Say that `out` went into a folder that didn't exist, so a mistyped path is noticed.
pub(crate) fn report_new_folder(folder: Option<std::path::PathBuf>, io: &mut Streams) {
if let Some(folder) = folder {
io.errln(format_args!("created the folder {}, which did not exist", folder.display()));
}
}
pub(crate) fn export(s: &Session, out: &Path, io: &mut Streams) -> Result {
@@ -135,7 +146,8 @@ pub(crate) fn export(s: &Session, out: &Path, io: &mut Streams) -> Result {
pub(crate) fn export_framed(s: &Session, out: &Path, window: Option<Bounds2>, io: &mut Streams) -> Result {
let d = s.doc()?;
let bytes = cadcraft_io::write_framed(d, out, window)?;
write_atomic(out, &bytes)?;
let folder = write_atomic(out, &bytes)?;
report_new_folder(folder, io);
io.errln(format_args!("wrote {} ({} bytes)", out.display(), bytes.len()));
if cadcraft_engine::cmd::file::drops_unknown(out) {
let lost = cadcraft_engine::cmd::file::not_saved(d);
+3 -2
View File
@@ -1,6 +1,6 @@
use super::args::{SampleArgs, SampleName};
use super::error::Result;
use super::output::{Streams, write_atomic};
use super::output::{Streams, report_new_folder, write_atomic};
pub(crate) fn sample(a: &SampleArgs, io: &mut Streams) -> Result {
let d = match a.name {
@@ -8,7 +8,8 @@ pub(crate) fn sample(a: &SampleArgs, io: &mut Streams) -> Result {
SampleName::Floorplan => cadcraft_engine::sample::floor_plan(),
};
let bytes = cadcraft_io::write(&d, &a.output)?;
write_atomic(&a.output, &bytes)?;
let folder = write_atomic(&a.output, &bytes)?;
report_new_folder(folder, io);
io.errln(format_args!("wrote {} ({} bytes)", a.output.display(), bytes.len()));
Ok(())
}