From 93d6157474f8b08cf25a2ec207377be8d27cddd6 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 4 Nov 2026 04:10:06 +0000 Subject: [PATCH] address review: private-folder check skips the temp folder itself; override value in expression fallback; assert alias cache opens --- crates/render/src/disk_cache.rs | 6 ++++-- crates/render/src/eval.rs | 2 +- crates/render/src/text.rs | 11 ++++++++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/render/src/disk_cache.rs b/crates/render/src/disk_cache.rs index f754c8a59..c27009ea9 100644 --- a/crates/render/src/disk_cache.rs +++ b/crates/render/src/disk_cache.rs @@ -316,7 +316,9 @@ fn make_private(folder: &Path) -> std::io::Result<()> { let tmp_real = tmp.canonicalize().unwrap_or_else(|_| tmp.clone()); let given = std::path::absolute(folder).unwrap_or_else(|_| folder.to_path_buf()); let real = folder.canonicalize().unwrap_or_else(|_| given.clone()); - if ![&given, &real].iter().any(|p| p.starts_with(&tmp) || p.starts_with(&tmp_real)) { + // The shared folder itself is not ours to change: only folders inside it. + let inside = |p: &PathBuf, t: &PathBuf| p.starts_with(t) && p != t; + if ![&given, &real].iter().any(|p| inside(p, &tmp) || inside(p, &tmp_real)) { return Ok(()); } if std::fs::symlink_metadata(folder)?.file_type().is_symlink() { @@ -1059,7 +1061,7 @@ mod tests { let link = std::env::current_dir().unwrap().join(format!("effectcraft-alias-link-{}", std::process::id())); let _ = std::fs::remove_file(&link); std::os::unix::fs::symlink(&real, &link).unwrap(); - let dc = DiskCache::open(link.join("cache"), 1 << 20); + let dc = DiskCache::open(link.join("cache"), 1 << 20).unwrap(); let mode = std::fs::metadata(real.join("cache")).map(|m| m.permissions().mode() & 0o777); drop(dc); let _ = std::fs::remove_file(&link); diff --git a/crates/render/src/eval.rs b/crates/render/src/eval.rs index 81fe45d6c..8def71986 100644 --- a/crates/render/src/eval.rs +++ b/crates/render/src/eval.rs @@ -285,7 +285,7 @@ impl<'a> EvalCtx<'a> { } impl crate::PropertyOverride { - fn value(&self, layer: &Layer, prop: &Property, time: Tick) -> Value { + pub(crate) fn value(&self, layer: &Layer, prop: &Property, time: Tick) -> Value { if self.font { // A Font control merges only the font into the target text, as // `essential::with_overrides` does. diff --git a/crates/render/src/text.rs b/crates/render/src/text.rs index 77cf63f15..3264dcded 100644 --- a/crates/render/src/text.rs +++ b/crates/render/src/text.rs @@ -245,7 +245,16 @@ pub fn animator_selection(ctx: &EvalCtx, layer: &Layer, anim: &PropGroup, lay: & let Some(prop) = sel.get("amount") else { continue }; // The keyframed amount, read without running its expression (the fallback when the // expression cannot run); an Essential Property override still applies. - let stat = if prop.has_expression() { prop.value_at(layer.layer_time(ctx.time)) } else { ctx.value(layer, prop) }.as_vec3(); + let stat = if prop.has_expression() { + // The expression is not run here, but an Essential Property override still wins over the base value. + match ctx.property_overrides.iter().rev().find(|o| o.layer == layer.id && o.prop == prop.uid) { + Some(o) => o.value(layer, prop, ctx.time), + None => prop.value_at(layer.layer_time(ctx.time)), + } + } else { + ctx.value(layer, prop) + } + .as_vec3(); // One allowance for the whole layer: the expression runs per character, each run // with its own time limit, so a slow expression on a long text is stopped once it // has failed or the layer's evaluations pass MAX_SELECTOR_TIME.