Preferences: a recovery folder that can't be listed is refused

Data Recovery lists the folders in its recovery folder before it starts, so a
folder that can be written but not listed (a Unix folder with mode 0333) left
it unable to start. Choose... now lists the picked folder as well as making a
folder in it, and refuses it with a status message when either fails. The
progress line says what is checked.
This commit is contained in:
dotCooCoo
2026-11-05 03:04:39 +07:00
committed by bflatastic
parent 13d0cbc51f
commit 08aa511252
3 changed files with 20 additions and 5 deletions
+5 -4
View File
@@ -427,17 +427,18 @@ fn choose_recovery_folder(app: &mut VectorcraftApp, d: &mut Dialog) {
}
}
/// `Ok` when `path` is a folder that a folder can be created in, as the recovery store makes its
/// own there: an empty folder is made there and removed. A folder that can't be removed again is
/// an error too.
/// `Ok` when `path` is a folder that can be listed and that a folder can be created in, as the
/// recovery store lists its folders there and makes its own: the folder is listed, and an empty
/// folder is made there and removed. A folder that can't be removed again is an error too.
fn writable_folder(path: &str) -> Result<(), String> {
let dir = std::path::Path::new(path);
if !dir.is_dir() {
return Err(format!("{path} is not a folder"));
}
let refused = |e: std::io::Error| format!("Data Recovery can't keep copies in {path}: {e}");
std::fs::read_dir(dir).map_err(refused)?;
let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |t| t.as_nanos());
let check = dir.join(format!(".vectorcraft-write-check-{}-{nanos}", std::process::id()));
let refused = |e: std::io::Error| format!("Data Recovery can't keep copies in {path}: {e}");
std::fs::create_dir(&check).map_err(refused)?;
std::fs::remove_dir(&check).map_err(refused)
}
+14
View File
@@ -506,6 +506,20 @@ fn a_recovery_folder_that_cannot_be_written_is_refused() {
assert!(a.ui.status.starts_with("Data Recovery can't keep copies in "), "{}", a.ui.status);
}
let _ = std::fs::set_permissions(&locked, std::fs::Permissions::from_mode(0o755));
// A folder that can be written but not listed: the recovery store lists its folders.
let unlisted = dir.join("unlisted");
std::fs::create_dir(&unlisted).unwrap();
std::fs::set_permissions(&unlisted, std::fs::Permissions::from_mode(0o333)).unwrap();
// An account that may list anyway (root) has nothing to refuse.
if std::fs::read_dir(&unlisted).is_err() {
let answer = unlisted.to_string_lossy().into_owned();
a.services.pick_folder = Some(Box::new(move || Some(answer.clone())));
a.ui.status.clear();
choose_recovery_folder(&mut a, &mut d);
assert_eq!(d.str("recoveryFolder"), "kept");
assert!(a.ui.status.starts_with("Data Recovery can't keep copies in "), "{}", a.ui.status);
}
let _ = std::fs::set_permissions(&unlisted, std::fs::Permissions::from_mode(0o755));
}
let _ = std::fs::remove_dir_all(&dir);
}
+1 -1
View File
@@ -1134,7 +1134,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
PDF export writes a linear or radial gradient that mixes a spot color with process colors, or two spot colors, as a DeviceN shading of the spot inks and the process inks its stops use, so each spot ink prints on its own plate; an off-center midpoint is written as a stop with the inks halfway between its two stops. A gradient that mixes a spot color with RGB or Lab colors is still written without the spot ink, with a warning. A Perceptual gradient of spot inks is written as a Separation or DeviceN shading of the stops on its curve; a dithered gradient fill, and a Perceptual gradient with RGB or Lab stops other than white or more than 4,096 stops on its curve, are written as images in process colors, with a warning. A Lab spot color in such a shading is named by the same Separation space, with the Lab alternate, as its flat fills (`engine/src/tests/linked_stops.rs`, `engine/src/tests/labspots.rs`).
PDF export writes a spot color named All or None (in a Separation space, the names of every plate and of no plate: [ISO 32000-1:2008](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf), 8.6.6.4) as the ink All Spot or None Spot (All Spot 2 and on when a spot swatch already has that name) in its fills, strokes, gradients, colorized images and Lab alternates; Registration still prints on every plate (`pdf/src/tests/output.rs`).
- `.vectorcraft` (lossless JSON, compressed, atomic saves, save down to v1), Save As with a format chooser, Save a Copy, Revert, templates, Data Recovery after a crash (browser tabs hold their copies with Web Locks too, so a background tab's copies are never taken for a crash's, and a tab repairs copies another removed when it resumes: #367), background save and export. The desktop app writes its warnings and errors (graphics adapter, a lost graphics device, panics the guard recovered from) to standard error and to `logs/vectorcraft.log` next to the preferences, the two previous runs kept and each file capped at 16 MiB, so a launch without a terminal still leaves something to attach to a bug report (#465). Native documents with a lot of path data (traced or generated art, in layers, symbols or patterns) are saved as compact JSON, about a fifth of the size; small documents stay pretty-printed (#1492). Saving to a network drive on macOS falls back to a plain fsync when the share refuses a full flush (#1625, part of #1620; not confirmed on a real share), and a save still succeeds when a folder refuses the temporary file, the rename over the document or copying permissions: the content is written elsewhere first and copied in, and a failed copy keeps the temporary file and names it (#1649, part of #1620). A document opened by a relative path finds its relatively linked images, and `vectorcraft-cli convert` lists what reading the input left out as `importWarnings` (#1606, fixes #1604).
Preferences › File Handling › Data Recovery › Folder has a Choose… button on the desktop that picks the folder Data Recovery keeps its copies in ([Illustrator reference, Set Data Recovery preferences](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)); a picked folder that a file cannot be created in is refused with a status message, and the field keeps its value.
Preferences › File Handling › Data Recovery › Folder has a Choose… button on the desktop that picks the folder Data Recovery keeps its copies in ([Illustrator reference, Set Data Recovery preferences](https://web.archive.org/web/20210601162814/https://helpx.adobe.com/pdf/illustrator_reference.pdf)); a picked folder that Data Recovery cannot create its folders in or list is refused with a status message, and the field keeps its value.
A name typed in the desktop save panel without the file type's extension gets it, and when a file of that name exists, which the panel did not ask about, Save, Save As, Save a Copy, Save as Template, the exports and every other write through a save panel ask before replacing it (dialog `replaceFile`; Cancel writes nothing).
A name returned by the desktop save panel with the extension of a format the save does not write has it replaced with the saved format's: Save As of a new document saves `card.ait` and `card.png` as `card.vectorcraft`, and Save as Template saves `card.pdf` as `card.vctemplate`. An extension that names no format stays part of the name (`notes.v2` is saved as `notes.v2.vectorcraft`).
Export As, Save as PDF, Save for Office Documents, Save for Web, Text Export Options, print to file, the presets' Export… and the library, swatch and Document Info saves replace a typed extension of another format than the one they write in the same way: Export As PNG writes `card.svg` as `card.png`, Save as PDF writes `card.png` as `card.pdf`, and `document.exportSelection` with a `format` suggests and adds that format's extension. An extension of the written format stays as typed in any letter case (`photo.JPEG` for JPEG), Export As SVG writes `art.svgz` compressed, and `document.exportSelection` without a `format` writes the format the typed extension names (`tests/saveext.rs`).