mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 01:15:25 +00:00
Automation: replies hide a file or missing folder on the way to the recovery folder
When a recovery copy fails because a folder on the way to the recovery folder is a file or is missing, the error names that folder, not the recovery folder. Replies to a client confined to read roots hid only the recovery folder itself, so the full path of that ancestor stayed visible. A reply now also hides each ancestor of a recovery folder that it writes as a whole path. The check compares text only and does not look anything up on disk. Follows #7287.
This commit is contained in:
@@ -1274,7 +1274,9 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context, req: &ControlReques
|
||||
|
||||
/// Hides in `reply` the libraries hidden now and in `before`, and the paths outside the read root that
|
||||
/// `sent`, the request's params, holds ([`handle`]), and the recovery folders outside the read root
|
||||
/// that are in use or that a failure message named. The `path` and `files` entries of the reply's
|
||||
/// that are in use or that a failure message named, with each folder on the way to one that the reply
|
||||
/// writes as a whole path ([`writes_whole_path`]): a failure names a file or a missing folder there.
|
||||
/// The `path` and `files` entries of the reply's
|
||||
/// `result`, at any depth, that name a file the request gave inside the write roots, the file it
|
||||
/// wrote, stay as given ([`written_paths`]).
|
||||
fn hide_in_reply(app: &VectorcraftApp, reply: &mut Value, sent: &Value, before: Hiding) {
|
||||
@@ -1288,16 +1290,42 @@ fn hide_in_reply(app: &VectorcraftApp, reply: &mut Value, sent: &Value, before:
|
||||
hiding.merge(before);
|
||||
let mut paths = Vec::new();
|
||||
sent_paths(sent, &mut paths);
|
||||
// The status text of a recovery copy that failed names the recovery folder it went to.
|
||||
// The status text of a recovery copy that failed names the recovery folder it went to, or the
|
||||
// first folder on the way to it that is a file or is missing.
|
||||
let current = vectorcraft_engine::cmd::recovery::folder(&app.session).map(|(f, _)| f);
|
||||
for folder in app.recovery.named.iter().chain(current.as_ref()) {
|
||||
collect_sent_paths(&json!(folder), true, &mut paths);
|
||||
for on_the_way in std::path::Path::new(folder).ancestors().skip(1).map(|p| p.to_string_lossy()) {
|
||||
if !on_the_way.is_empty() && writes_whole_path(reply, &on_the_way) {
|
||||
collect_sent_paths(&json!(on_the_way), true, &mut paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
hiding.set_hidden(paths, cfg!(windows));
|
||||
hide_library_ids(reply, &hiding);
|
||||
restore_written_paths(reply, kept);
|
||||
}
|
||||
|
||||
/// Does `v` write `path` in a string or a key at any depth with no path text before it (a letter, a
|
||||
/// digit, `_`, `-`, `.`, `~` or a separator) or after it ([`continues_path`]), a Windows path
|
||||
/// ([`splits_at_backslash`]) with either separator and in any case of ASCII letters too? Only the text
|
||||
/// is compared: nothing on disk is looked up.
|
||||
fn writes_whole_path(v: &Value, path: &str) -> bool {
|
||||
let windows = splits_at_backslash(path);
|
||||
let path_before =
|
||||
|before: &str| before.chars().next_back().is_some_and(|c| c.is_alphanumeric() || matches!(c, '_' | '-' | '.' | '~' | '/' | '\\'));
|
||||
let whole = |s: &str| {
|
||||
let spellings = if windows { vec![path.replace('\\', "/"), path.replace('/', "\\")] } else { vec![path.to_string()] };
|
||||
spellings.iter().any(|p| occurrences(s, p, windows).into_iter().any(|at| !path_before(&s[..at]) && !continues_path(&s[at + p.len()..])))
|
||||
};
|
||||
match v {
|
||||
Value::String(s) => whole(s),
|
||||
Value::Array(items) => items.iter().any(|item| writes_whole_path(item, path)),
|
||||
Value::Object(fields) => fields.iter().any(|(key, item)| whole(key) || writes_whole_path(item, path)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// The strings that `v`'s path params hold at any depth ([`vectorcraft_engine::file_access::is_path_key`])
|
||||
/// that name a file inside the write roots; `v` is a path param's value when `path_param` is set.
|
||||
fn written_paths(v: &Value, path_param: bool, out: &mut Vec<String>) {
|
||||
|
||||
@@ -474,6 +474,36 @@ fn a_recovery_folder_outside_the_read_root_is_hidden_in_the_status() {
|
||||
assert_eq!(inspect(&mut app, format!("Copies go to {inside}")), json!(format!("Copies go to {inside}")));
|
||||
}
|
||||
|
||||
/// `ui.inspect` shows a file on the way to a recovery folder outside the read root, which a failed
|
||||
/// recovery copy names, by its name, and a missing folder on the way too. A path that only begins or
|
||||
/// only ends with a folder on the way stays.
|
||||
#[test]
|
||||
fn a_file_on_the_way_to_a_recovery_folder_outside_the_read_root_is_hidden_in_the_status() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services::default());
|
||||
let outside = crate::tests_folder::Folder::new("recovery-in-the-way");
|
||||
let file = outside.file("private", b"a file");
|
||||
let folder = std::path::Path::new(&file).join("recovery").to_string_lossy().into_owned();
|
||||
app.session.recovery.set_default_folder(Some(folder));
|
||||
let root = crate::tests_folder::Folder::new("recovery-in-the-way-root");
|
||||
let roots = vectorcraft_engine::file_access::AutomationRoots::new(&[root.as_path()], &[]).unwrap();
|
||||
let inspect = |app: &mut VectorcraftApp, status: String| {
|
||||
app.ui.status = status;
|
||||
let reply = vectorcraft_engine::file_access::confine(roots.as_ref(), || input_request(app, "ui.inspect", json!({})));
|
||||
reply["result"]["ui"]["status"].clone()
|
||||
};
|
||||
let status = format!("Couldn't save recovery data: {file} is not a folder");
|
||||
assert_eq!(inspect(&mut app, status), "Couldn't save recovery data: private is not a folder");
|
||||
let missing = outside.join("gone");
|
||||
let folder = missing.join("recovery").to_string_lossy().into_owned();
|
||||
app.session.recovery.set_default_folder(Some(folder));
|
||||
let status = format!("Couldn't save recovery data: couldn't make {}", missing.display());
|
||||
assert_eq!(inspect(&mut app, status), "Couldn't save recovery data: couldn't make gone");
|
||||
let under = format!("Copies go near {}", outside.join("other.svg").display());
|
||||
assert_eq!(inspect(&mut app, under.clone()), json!(under));
|
||||
let longer = format!("Copies go near /work{}", missing.display());
|
||||
assert_eq!(inspect(&mut app, longer.clone()), json!(longer));
|
||||
}
|
||||
|
||||
/// A path outside the read root that a request sent is shown by its file name in a reply that succeeded
|
||||
/// as in one that failed.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user