Add folder scan with format selection; validate A3 on a real library
Library setup as the user described it: pick a folder, choose which RAW
types to look for, scan recursively.
dr-types::FormatFilter the tick-box selection, seeing through VFS
placeholder suffixes so a dehydrated CR2 still
matches as a CR2
dr-sync::scan recursive walk, Depth:1 per directory, pruning
unchanged subtrees where the backend propagates
directory ETags
Verified against nextcloud.tourolle.paris (34.0.2) on a real library:
browse root 32 entries, 98ms
scan PhotosRaw 17,185 RAW files in 334 directories, 34.1s
(7,836 CR2 + 9,349 DNG)
range read 262KB of a 21.5MB DNG in 119ms — 1.22% of the file,
and enough to read "Canon EOS 6D | ISO 100"
That last line is assumption A3 validated on real data. Cataloguing this
library by whole-file fetch would move roughly 370GB; the range path
moves a few MB.
Pruning is capability-gated rather than assumed: with per-entry ETags a
probe costs a request and proves nothing about children, so it is skipped
entirely. A test asserts zero probes in that case.
Still unresolved: /core/preview returns 400 for every parameter
combination tried, including on a JPEG the server reports as having a
preview. Not a request-shape bug — it fails identically bare. Recorded
rather than worked around; ARCH §6.7 already treats server previews as
opportunistic, so nothing depends on it.
This commit is contained in:
+39
-83
@@ -494,102 +494,58 @@ mod tests {
|
||||
let mut pass = AdjustPass::new(&ctx);
|
||||
let img = grey_image(&ctx, 4000);
|
||||
|
||||
// A name paired with the edit that activates one operation. Boxed
|
||||
// closures rather than a type alias: the list is read top to bottom
|
||||
// as a table of what is covered.
|
||||
#[allow(clippy::type_complexity)]
|
||||
let cases: Vec<(&str, Box<dyn Fn(&mut EditGraph)>)> = vec![
|
||||
(
|
||||
"white_balance.temperature",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(white_balance::ID, white_balance::TEMPERATURE, 60.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"white_balance.tint",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(white_balance::ID, white_balance::TINT, -40.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"exposure",
|
||||
Box::new(|g: &mut EditGraph| g.set_param(exposure::ID, exposure::EXPOSURE, 1.5)),
|
||||
),
|
||||
(
|
||||
"highlights",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(tone::HIGHLIGHTS_SHADOWS_ID, tone::HIGHLIGHTS, -70.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"shadows",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(tone::HIGHLIGHTS_SHADOWS_ID, tone::SHADOWS, 70.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"blacks",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(tone::BLACKS_WHITES_ID, tone::BLACKS, -50.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"whites",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(tone::BLACKS_WHITES_ID, tone::WHITES, 50.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"brilliance",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(colour::BRILLIANCE_ID, colour::BRILLIANCE, 60.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"vibrance",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(colour::VIBRANCE_ID, colour::VIBRANCE, 60.0)
|
||||
}),
|
||||
),
|
||||
(
|
||||
"saturation",
|
||||
Box::new(|g: &mut EditGraph| {
|
||||
g.set_param(colour::SATURATION_ID, colour::SATURATION, 60.0)
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
for (name, apply) in cases {
|
||||
let mut g = EditGraph::default_chain();
|
||||
apply(&mut g);
|
||||
let shader = g.compose();
|
||||
pass.render(&img, &shader, 16, 16)
|
||||
.unwrap_or_else(|e| panic!("{name} generated invalid WGSL:\n{e}"));
|
||||
// Cases derived from the chain itself rather than a hand-written
|
||||
// list: every parameter of every operation is exercised, and adding
|
||||
// an operation extends the coverage automatically instead of
|
||||
// silently going untested.
|
||||
let probe = EditGraph::default_chain();
|
||||
for cap in probe.capabilities() {
|
||||
for p in &cap.params {
|
||||
let dr_pipeline::ParamKind::Scalar { min, max, .. } = p.kind else {
|
||||
continue;
|
||||
};
|
||||
// Both extremes: a fragment can be valid at one end of its
|
||||
// range and not the other.
|
||||
for value in [min, max] {
|
||||
let mut g = EditGraph::default_chain();
|
||||
g.set_param(cap.id, p.id, value);
|
||||
let shader = g.compose();
|
||||
pass.render(&img, &shader, 16, 16).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"{}.{} at {value} generated invalid WGSL:\n{e}",
|
||||
cap.id, p.id
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_whole_chain_at_once_compiles() {
|
||||
// Individually-valid fragments can still collide when combined —
|
||||
// duplicate helpers, clashing locals, a malformed uniform block.
|
||||
// duplicate helpers, clashing locals, a malformed uniform block. With
|
||||
// every operation active this is the largest shader the pipeline can
|
||||
// generate.
|
||||
let Some(ctx) = ctx() else { return };
|
||||
let mut pass = AdjustPass::new(&ctx);
|
||||
let img = grey_image(&ctx, 4000);
|
||||
|
||||
let mut g = EditGraph::default_chain();
|
||||
g.set_param(white_balance::ID, white_balance::TEMPERATURE, 30.0);
|
||||
g.set_param(white_balance::ID, white_balance::TINT, -20.0);
|
||||
g.set_param(exposure::ID, exposure::EXPOSURE, 0.8);
|
||||
g.set_param(tone::HIGHLIGHTS_SHADOWS_ID, tone::HIGHLIGHTS, -60.0);
|
||||
g.set_param(tone::HIGHLIGHTS_SHADOWS_ID, tone::SHADOWS, 40.0);
|
||||
g.set_param(tone::BLACKS_WHITES_ID, tone::BLACKS, -25.0);
|
||||
g.set_param(tone::BLACKS_WHITES_ID, tone::WHITES, 35.0);
|
||||
g.set_param(colour::BRILLIANCE_ID, colour::BRILLIANCE, 45.0);
|
||||
g.set_param(colour::VIBRANCE_ID, colour::VIBRANCE, 55.0);
|
||||
g.set_param(colour::SATURATION_ID, colour::SATURATION, 15.0);
|
||||
for cap in EditGraph::default_chain().capabilities() {
|
||||
for p in &cap.params {
|
||||
if let dr_pipeline::ParamKind::Scalar { max, .. } = p.kind {
|
||||
g.set_param(cap.id, p.id, max * 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let shader = g.compose();
|
||||
assert_eq!(shader.source.matches("---- ").count(), 7);
|
||||
assert_eq!(
|
||||
shader.source.matches("---- ").count(),
|
||||
g.descriptors().len(),
|
||||
"every operation should be active"
|
||||
);
|
||||
pass.render(&img, &shader, 32, 32)
|
||||
.expect("the full chain must compile");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user