ElyMark: dropped and pasted pictures copy into assets and link at the caret (C14.10)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
72e9441399
commit
b74f4e4e72
12 files changed
+357
-63
No files matched your search
@@ -19,6 +19,7 @@ pub use task::RecoveryTask;
|
||||
|
||||
pub use save::{
|
||||
SaveError, Saved, atomic_capable, claim_all, export, import_attachment, same_file, save,
|
||||
write_attachment,
|
||||
save_as, save_atomic, save_overwrite, save_with_backup, save_with_probe,
|
||||
};
|
||||
pub use session::{Fingerprint, Open, SaveState, SaveTicket, Session, Sessions, StaleTicket};
|
||||
@@ -212,24 +212,38 @@ pub fn same_file(a: &Path, b: &Path) -> io::Result<bool> {
|
||||
|
||||
/// Copies an attachment in; the source is never touched (FILE-07).
|
||||
pub fn import_attachment(source: &Path, dir: &Path) -> io::Result<PathBuf> {
|
||||
std::fs::create_dir_all(dir).map_err(|e| with_path(dir, e))?;
|
||||
let name = source.file_name().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("{} has no file name", source.display()),
|
||||
)
|
||||
})?;
|
||||
let mut target = dir.join(name);
|
||||
let stem = Path::new(name)
|
||||
.file_stem()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let ext = Path::new(name)
|
||||
let mut from = std::fs::File::open(source).map_err(|e| with_path(source, e))?;
|
||||
let target = attach(dir, Path::new(name), |to| io::copy(&mut from, to).map(drop))?;
|
||||
log::info!("files: imported {} to {}", source.display(), target.display());
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
/// Writes `bytes` into `dir` as `name`, or a free variant.
|
||||
pub fn write_attachment(name: &str, bytes: &[u8], dir: &Path) -> io::Result<PathBuf> {
|
||||
let target = attach(dir, Path::new(name), |to| to.write_all(bytes))?;
|
||||
log::info!("files: wrote {} bytes to {}", bytes.len(), target.display());
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
/// Claims a free name like `name`, then fills it.
|
||||
fn attach(
|
||||
dir: &Path,
|
||||
name: &Path,
|
||||
fill: impl FnOnce(&mut std::fs::File) -> io::Result<()>,
|
||||
) -> io::Result<PathBuf> {
|
||||
std::fs::create_dir_all(dir).map_err(|e| with_path(dir, e))?;
|
||||
let stem = name.file_stem().unwrap_or_default().to_string_lossy().into_owned();
|
||||
let ext = name
|
||||
.extension()
|
||||
.map(|ext| ext.to_string_lossy().into_owned())
|
||||
.unwrap_or_default();
|
||||
let mut from = std::fs::File::open(source).map_err(|e| with_path(source, e))?;
|
||||
let mut target = dir.join(name);
|
||||
let mut counter = 1;
|
||||
// Claims the name; another writer's file is never replaced.
|
||||
let mut to = loop {
|
||||
@@ -242,17 +256,12 @@ pub fn import_attachment(source: &Path, dir: &Path) -> io::Result<PathBuf> {
|
||||
Err(e) => return Err(with_path(&target, e)),
|
||||
}
|
||||
};
|
||||
let copied = io::copy(&mut from, &mut to).and_then(|_| to.flush());
|
||||
if let Err(e) = copied {
|
||||
let filled = fill(&mut to).and_then(|()| to.flush());
|
||||
if let Err(e) = filled {
|
||||
// A half copy is not left behind.
|
||||
std::fs::remove_file(&target).ok();
|
||||
return Err(with_path(&target, e));
|
||||
}
|
||||
log::info!(
|
||||
"files: imported {} to {}",
|
||||
source.display(),
|
||||
target.display()
|
||||
);
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,15 @@ fn an_import_copies_and_never_deletes() {
|
||||
assert_eq!(read(&second), "png-bytes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn written_bytes_take_a_free_name() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let first = write_attachment("pasted.png", b"one", dir.path()).expect("the first write");
|
||||
let second = write_attachment("pasted.png", b"two", dir.path()).expect("the second write");
|
||||
assert_ne!(first, second);
|
||||
assert_eq!((read(&first), read(&second)), ("one".into(), "two".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_numbered_import_name_fits_long_file_names() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
|
||||
Reference in new issue
Block a user