PDF presets: a rename that can't be written keeps the preset under its old name

Renaming a saved PDF preset (pdf.preset.save with newName) removed
the old file from the PDF Presets folder before it wrote the new one.
When that write failed, for example on a full disk, the folder held no
file for the preset, and the preset was gone at the next start.

The store now writes the new file first and removes the old one only
when that write succeeded. A failed write leaves the preset in the
folder under its old name. A rename that changes only the case of the
name writes the old file and then renames it, and the old file gets
its content back when the rename fails. The command still succeeds
when the folder can't be written, and the failure is logged, as
before.
This commit is contained in:
dotCooCoo
2026-10-18 01:24:34 +07:00
committed by bflatastic
parent a209996da2
commit 6a2bb03ebf
2 changed files with 109 additions and 9 deletions
+76
View File
@@ -40,6 +40,82 @@ fn saved_presets_come_back_in_the_next_session_and_renames_and_deletes_follow()
assert!(!dir.join(format!("{}.vcpdfpresets", file_stem("Old"))).exists());
}
#[test]
fn a_rename_that_cannot_be_written_keeps_the_preset_under_its_old_name() {
let dir = fresh_dir("rename-fails").join("PDF Presets");
let mut a = Session::new();
a.attach_pdf_preset_store(dir.clone());
a.execute("pdf.preset.save", &json!({"name": "Web", "description": "for screens"})).unwrap();
// The test puts a folder where the new name's file goes, so the write fails as it would on a full disk.
std::fs::create_dir(dir.join(format!("{}.vcpdfpresets", file_stem("Screen")))).unwrap();
a.execute("pdf.preset.save", &json!({"name": "Web", "newName": "Screen"})).unwrap();
let mut b = Session::new();
b.attach_pdf_preset_store(dir.clone());
assert_eq!(saved_names(&mut b), ["Web"]);
assert_eq!(b.prefs.pdf_presets[0].description, "for screens");
assert!(dir.join(format!("{}.vcpdfpresets", file_stem("Web"))).is_file());
}
#[test]
fn a_rename_that_only_changes_case_keeps_one_file() {
let dir = fresh_dir("rename-case").join("PDF Presets");
let mut a = Session::new();
a.attach_pdf_preset_store(dir.clone());
a.execute("pdf.preset.save", &json!({"name": "Web"})).unwrap();
a.execute("pdf.preset.save", &json!({"name": "Web", "newName": "WEB", "description": "renamed"})).unwrap();
let mut b = Session::new();
b.attach_pdf_preset_store(dir.clone());
assert_eq!(saved_names(&mut b), ["WEB"]);
assert_eq!(b.prefs.pdf_presets[0].description, "renamed");
assert_eq!(file_names(&dir), [format!("{}.vcpdfpresets", file_stem("WEB"))]);
}
fn file_names(dir: &std::path::Path) -> Vec<String> {
std::fs::read_dir(dir).unwrap().map(|e| e.unwrap().file_name().to_string_lossy().into_owned()).collect()
}
/// Windows does not allow replacing a read-only file or writing into it, so the write fails.
#[cfg(windows)]
#[test]
fn a_rename_that_only_changes_case_and_cannot_be_written_keeps_the_old_file() {
let dir = fresh_dir("rename-case-fails").join("PDF Presets");
let mut a = Session::new();
a.attach_pdf_preset_store(dir.clone());
a.execute("pdf.preset.save", &json!({"name": "Web", "description": "for screens"})).unwrap();
let old = dir.join(format!("{}.vcpdfpresets", file_stem("Web")));
let writable = std::fs::metadata(&old).unwrap().permissions();
let mut read_only = writable.clone();
read_only.set_readonly(true);
std::fs::set_permissions(&old, read_only).unwrap();
a.execute("pdf.preset.save", &json!({"name": "Web", "newName": "WEB"})).unwrap();
std::fs::set_permissions(&old, writable).unwrap();
let mut b = Session::new();
b.attach_pdf_preset_store(dir.clone());
assert_eq!(saved_names(&mut b), ["Web"]);
assert_eq!(b.prefs.pdf_presets[0].description, "for screens");
assert_eq!(file_names(&dir), [format!("{}.vcpdfpresets", file_stem("Web"))]);
}
/// Where file names keep case apart, a folder named like the new file makes the rename fail.
#[cfg(not(any(windows, target_vendor = "apple")))]
#[test]
fn a_rename_that_only_changes_case_and_cannot_be_moved_keeps_the_old_preset() {
let dir = fresh_dir("rename-case-blocked").join("PDF Presets");
let mut a = Session::new();
a.attach_pdf_preset_store(dir.clone());
a.execute("pdf.preset.save", &json!({"name": "Web", "description": "for screens"})).unwrap();
std::fs::create_dir(dir.join(format!("{}.vcpdfpresets", file_stem("WEB")))).unwrap();
a.execute("pdf.preset.save", &json!({"name": "Web", "newName": "WEB", "description": "renamed"})).unwrap();
let mut b = Session::new();
b.attach_pdf_preset_store(dir.clone());
assert_eq!(saved_names(&mut b), ["Web"]);
assert_eq!(b.prefs.pdf_presets[0].description, "for screens");
}
#[test]
fn a_folder_that_does_not_exist_yet_takes_the_presets_the_preferences_held() {
let dir = fresh_dir("seed").join("PDF Presets");