mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 07:38:24 +00:00
Automation: a confined renderQueue.add without output needs a saved project (#2479)
On a project that was never saved, renderQueue.add without output queued an item whose default output was the home folder, outside every automation root (rendering it was refused, but the queue held a path the agent may not write). While file access is confined, such an add is now refused with a hint to give output inside a write root; a saved project's default output (beside it) is checked when the queue renders, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6681a3bc2a
commit
ad6bc0c19b
4 files changed
+24
-2
No files matched your search
@@ -64,7 +64,7 @@ impl Backend {
|
||||
let params = if params.is_null() { json!({}) } else { params };
|
||||
if let Some(c) = self.confinement.clone() {
|
||||
let lookup = match c.check_command(id, ¶ms)? {
|
||||
Some(crate::confine::Lookup::ProjectFile) => Some((crate::confine::Lookup::ProjectFile, self.run("project.summary", json!({}))?)),
|
||||
Some(l @ (crate::confine::Lookup::ProjectFile | crate::confine::Lookup::QueueDefault)) => Some((l, self.run("project.summary", json!({}))?)),
|
||||
Some(crate::confine::Lookup::RenderOutputs) => Some((crate::confine::Lookup::RenderOutputs, self.run("renderQueue.list", json!({}))?)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
@@ -128,6 +128,9 @@ pub enum Lookup {
|
||||
ProjectFile,
|
||||
/// Every output of the queued Render Queue items (`renderQueue.list`).
|
||||
RenderOutputs,
|
||||
/// Whether the open project has a file (`project.summary` → `path`): a Render Queue item
|
||||
/// added without `output` writes beside it, or to the home folder before the first save.
|
||||
QueueDefault,
|
||||
}
|
||||
|
||||
impl Confinement {
|
||||
@@ -239,6 +242,10 @@ impl Confinement {
|
||||
if matches!(id, "file.openRecent" | "file.importRecent" | "anim.applyRecentPreset") && !given {
|
||||
return Err(Error::BadArgs(format!("`{id}`: give `path` while file access is confined")));
|
||||
}
|
||||
// Without `output`, an unsaved project's item would write to the home folder (#2479).
|
||||
if id == "renderQueue.add" && !given {
|
||||
return Ok(Some(Lookup::QueueDefault));
|
||||
}
|
||||
if IMPLICIT_WRITES.contains(&id) && !(given && id == "file.save") {
|
||||
return Ok(Some(if id == "file.save" || id == "file.incrementAndSave" { Lookup::ProjectFile } else { Lookup::RenderOutputs }));
|
||||
}
|
||||
@@ -253,6 +260,13 @@ impl Confinement {
|
||||
Some(p) => self.check(p, Access::Write).map(drop),
|
||||
None => Err(Error::BadArgs("the project has no file yet: give `path` (Save As) inside an automation write root".into())),
|
||||
},
|
||||
// A saved project's default output sits beside it, and rendering checks it then.
|
||||
Lookup::QueueDefault => match found.get("path").and_then(Value::as_str) {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(Error::BadArgs(
|
||||
"`renderQueue.add`: the project has no file yet, so the default output would go to the home folder: give `output` inside an automation write root".into(),
|
||||
)),
|
||||
},
|
||||
Lookup::RenderOutputs => {
|
||||
let items = found.get("items").or(Some(found)).and_then(Value::as_array).cloned().unwrap_or_default();
|
||||
for it in items.iter().filter(|it| it.get("render").and_then(Value::as_bool) != Some(false)) {
|
||||
|
||||
@@ -1372,6 +1372,12 @@ fn automation_roots_confine_file_access() {
|
||||
assert!(err, "{name} {args} should be refused: {c:?}");
|
||||
c[0]["text"].as_str().unwrap_or_default().to_string()
|
||||
};
|
||||
// #2479: an unsaved project's queue item without `output` would write to the home folder.
|
||||
call_json(&mut s, "execute_command", json!({"command": "comp.new", "params": {"name": "Unsaved", "width": 64, "height": 64}}));
|
||||
let msg = denied(&mut s, "execute_command", json!({"command": "renderQueue.add", "params": {"comp": "Unsaved"}}));
|
||||
assert!(msg.contains("home folder") && msg.contains("give `output`"), "{msg}");
|
||||
call_json(&mut s, "execute_command", json!({"command": "renderQueue.add", "params": {"comp": "Unsaved", "output": p(&write, "unsaved.mp4")}}));
|
||||
call_json(&mut s, "execute_command", json!({"command": "renderQueue.remove"}));
|
||||
// Reading: inside a read root, not outside.
|
||||
call_json(&mut s, "open_project", json!({"path": p(&read, "source.ecproj")}));
|
||||
let msg = denied(&mut s, "open_project", json!({"path": p(&outside, "free.ecproj")}));
|
||||
|
||||
+3
-1
@@ -11,7 +11,9 @@ written only under a write root; with no write root nothing is written. Every en
|
||||
(`execute_command`, `command_run`, each `batch` step) is checked before it runs: its file
|
||||
parameters must be absolute paths inside the roots (symbolic links resolved, no `..`), and so must
|
||||
any other absolute path in its parameters. Save, Increment and Save and rendering the Render
|
||||
Queue are checked against the files they write (the open project's, the queued outputs). Scripts
|
||||
Queue are checked against the files they write (the open project's, the queued outputs);
|
||||
`renderQueue.add` without `output` needs a saved project, since an unsaved one's default output
|
||||
would go to the home folder (#2479). Scripts
|
||||
(`run_script`, `file.runScript`…), Execute File, Reveal in Finder, `$N` references to file paths
|
||||
in a batch, and the bridge's UI-driving tools (`ui_click`, `ui_key`, `ui_type`, `ui_drag`,
|
||||
`ui_set`, `script_ui`, `control`) are refused while confined. Without the flags nothing changes.
|
||||
|
||||
Reference in new issue
Block a user