mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-14 19:26:51 -04:00
photocraft_io::named_for_file(doc, name) serialises the document under the file it is being written to, so a .pcraft manifest carries the identity the save is about to adopt rather than the previous one — the same rule every other format follows on import, where document.name comes from the file it was read from. A bare extension names no file and changes nothing. Applied once, in the .pcraft branch of export_stages, which export and export_with both funnel through: the desktop Save (services.rs:242), Export (services.rs:384), the web build (web.rs:355) and the engine file command (file_cmds.rs:295) are covered by one site, and the previews rendered before the bytes describe the same document as the manifest. Scope per review: the automation funnels (files::save / save_bytes) already landed in #3319 with their own named_for_output, so this carries no crates/automation change; its scenarios are covered by crates/automation/tests/native_save_name.rs on main.
This commit is contained in:
1 parent
50b97605fb
commit
d32d3f41bb
2 files changed
+58
-3
No files matched your search
+20
-3
@@ -310,6 +310,22 @@ fn has_extension(name: &str, expected: &str) -> bool {
|
||||
name.rsplit(['/', '\\']).next().and_then(|name| name.rsplit_once('.')).is_some_and(|(base, ext)| !base.is_empty() && ext.eq_ignore_ascii_case(expected))
|
||||
}
|
||||
|
||||
/// The document as it is written to `name` (a file name, path or bare extension): a copy whose
|
||||
/// name is that file's own, so a `.pcraft` manifest reports the file it lives in — the name the
|
||||
/// session adopts on the save that wrote it, and the name every other format gives the document
|
||||
/// when it is opened (#3312). A bare extension ("pcraft") names no file and changes nothing.
|
||||
pub fn named_for_file<'a>(doc: &'a Document, name: &str) -> std::borrow::Cow<'a, Document> {
|
||||
let file = name.contains(['/', '\\', '.']).then(|| name.rsplit(['/', '\\']).next().unwrap_or(name));
|
||||
match file {
|
||||
Some(file) if !file.is_empty() && doc.name != file => {
|
||||
let mut owned = (*doc).clone();
|
||||
owned.name = file.to_string();
|
||||
std::borrow::Cow::Owned(owned)
|
||||
}
|
||||
_ => std::borrow::Cow::Borrowed(doc),
|
||||
}
|
||||
}
|
||||
|
||||
/// Exports `doc` to the format named by `name_or_ext` (a file name, path or
|
||||
/// bare extension).
|
||||
pub fn export(doc: &Document, name_or_ext: &str, opts: &ExportOptions) -> Result<ExportResult, IoError> {
|
||||
@@ -355,14 +371,15 @@ fn export_stages(doc: &Document, name_or_ext: &str, opts: &ExportOptions, ctl: &
|
||||
return ora::export(doc);
|
||||
}
|
||||
if ext == photocraft_format::EXTENSION {
|
||||
let thumbnail = Some(photocraft_compose::thumbnail(doc, 256));
|
||||
let doc = named_for_file(doc, name_or_ext);
|
||||
let thumbnail = Some(photocraft_compose::thumbnail(doc.as_ref(), 256));
|
||||
ctl.check().map_err(|_| IoError::Cancelled)?;
|
||||
ctl.progress(PCRAFT_THUMBNAIL_DONE);
|
||||
let composite = Some(photocraft_compose::thumbnail(doc, 1024));
|
||||
let composite = Some(photocraft_compose::thumbnail(doc.as_ref(), 1024));
|
||||
ctl.check().map_err(|_| IoError::Cancelled)?;
|
||||
ctl.progress(PCRAFT_PREVIEWS_DONE);
|
||||
let previews = photocraft_format::SaveOptions { thumbnail, composite };
|
||||
return Ok(ExportResult { bytes: photocraft_format::save_to_bytes(doc, &previews)?, warnings: Vec::new() });
|
||||
return Ok(ExportResult { bytes: photocraft_format::save_to_bytes(doc.as_ref(), &previews)?, warnings: Vec::new() });
|
||||
}
|
||||
if ext == "psd" || ext == "psb" {
|
||||
let o = PsdExportOptions { force_psb: opts.force_psb || ext == "psb", ..Default::default() };
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
//! A `.pcraft` bundle's manifest reports the file it is in (#3312): writing a document to a new
|
||||
//! name must not leave the previous one inside, on every path that produces `.pcraft` bytes.
|
||||
|
||||
mod common;
|
||||
|
||||
use common::*;
|
||||
use photocraft_color::{ColorMode, SampleType};
|
||||
use photocraft_format::zip::ZipReader;
|
||||
use photocraft_io::*;
|
||||
|
||||
fn doc() -> photocraft_doc::Document {
|
||||
let mut d = gen_doc(ColorMode::Rgb, SampleType::U8, Features::PIXELS);
|
||||
d.name = "FirstTest.psd".into();
|
||||
d
|
||||
}
|
||||
|
||||
/// `document.name` as the bundle actually stores it.
|
||||
fn manifest_name(bytes: &[u8]) -> String {
|
||||
let archive = ZipReader::new(bytes).expect("zip");
|
||||
let manifest: serde_json::Value = serde_json::from_slice(&archive.read_by_name("manifest.json", usize::MAX).expect("manifest")).expect("manifest json");
|
||||
manifest["document"]["name"].as_str().expect("document.name").to_string()
|
||||
}
|
||||
|
||||
/// Save As to a new name: the written file names itself, so reopening it agrees with the session
|
||||
/// that wrote it instead of reporting the file it came from.
|
||||
#[test]
|
||||
fn export_names_the_manifest_after_the_file() {
|
||||
let r = export(&doc(), "/tmp/somewhere/_nametest.pcraft", &ExportOptions::default()).expect("export");
|
||||
assert_eq!(manifest_name(&r.bytes), "_nametest.pcraft");
|
||||
assert_eq!(photocraft_format::load_from_bytes(&r.bytes).expect("load").name, "_nametest.pcraft");
|
||||
}
|
||||
|
||||
/// A bare extension names no file and leaves the document's own name alone.
|
||||
#[test]
|
||||
fn a_bare_extension_names_no_file() {
|
||||
let r = export(&doc(), "pcraft", &ExportOptions::default()).expect("export");
|
||||
assert_eq!(manifest_name(&r.bytes), "FirstTest.psd");
|
||||
}
|
||||
Reference in new issue
Block a user