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:
+102
-6
@@ -32,6 +32,7 @@
|
||||
//! data neither would be physically meaningful (ARCH §5.2).
|
||||
|
||||
pub mod descriptor;
|
||||
pub mod framing;
|
||||
pub mod graph;
|
||||
pub mod operation;
|
||||
pub mod ops;
|
||||
@@ -39,8 +40,11 @@ pub mod ops;
|
||||
pub use descriptor::{
|
||||
LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, ParamKind, Scale, Unit,
|
||||
};
|
||||
pub use framing::{CropRect, Framing};
|
||||
pub use graph::{EditGraph, OpCapability, ParamCapability};
|
||||
pub use operation::{compose, Affects, ComposedShader, Helper, Operation, Uniform};
|
||||
pub use operation::{
|
||||
compose, compose_with_framing, Affects, ComposedShader, Helper, Operation, Uniform,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -67,10 +71,12 @@ mod tests {
|
||||
let g = fully_active();
|
||||
assert!(!g.is_neutral());
|
||||
let shader = g.compose();
|
||||
// Counted against the chain rather than a literal, so adding an
|
||||
// operation does not require editing this test.
|
||||
assert_eq!(
|
||||
shader.source.matches("---- ").count(),
|
||||
7,
|
||||
"all seven operations should appear"
|
||||
g.descriptors().len(),
|
||||
"every operation in the chain should appear"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,16 +154,106 @@ mod tests {
|
||||
#[test]
|
||||
fn generated_uniform_names_are_valid_wgsl_identifiers() {
|
||||
let shader = fully_active().compose();
|
||||
for line in shader.source.lines() {
|
||||
|
||||
// Only the `struct Params` block. Scanning the whole source picks up
|
||||
// helper *signatures* such as `fn contrast_curve(x: f32, ...)`, whose
|
||||
// parameters are not uniform declarations at all.
|
||||
let body = shader
|
||||
.source
|
||||
.split_once("struct Params {")
|
||||
.expect("a uniform struct is always generated")
|
||||
.1
|
||||
.split_once('}')
|
||||
.expect("the struct is closed")
|
||||
.0;
|
||||
|
||||
let mut checked = 0;
|
||||
for line in body.lines() {
|
||||
let trimmed = line.trim();
|
||||
let Some((name, _)) = trimmed.split_once(": f32,") else {
|
||||
if trimmed.starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
let Some((name, _)) = trimmed.split_once(':') else {
|
||||
continue;
|
||||
};
|
||||
let name = name.trim();
|
||||
assert!(
|
||||
name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
!name.is_empty()
|
||||
&& name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
&& !name.starts_with(|c: char| c.is_ascii_digit()),
|
||||
"{name} is not a valid WGSL identifier"
|
||||
);
|
||||
checked += 1;
|
||||
}
|
||||
assert!(checked > 0, "the struct should declare fields");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_fragment_declares_a_wgsl_reserved_keyword() {
|
||||
// Caught the hard way: `let target = ...` in the contrast fragment
|
||||
// failed to compile with "name `target` is a reserved keyword", and
|
||||
// the error pointed at generated source rather than at the operation
|
||||
// that wrote it. Checking here names the culprit directly.
|
||||
//
|
||||
// Not the full reserved list — the ones a colour operation would
|
||||
// plausibly reach for.
|
||||
const RESERVED: &[&str] = &[
|
||||
"target",
|
||||
"sample",
|
||||
"filter",
|
||||
"texture",
|
||||
"buffer",
|
||||
"binding",
|
||||
"const",
|
||||
"enum",
|
||||
"mat",
|
||||
"vec",
|
||||
"ptr",
|
||||
"ref",
|
||||
"shared",
|
||||
"static",
|
||||
"typedef",
|
||||
"union",
|
||||
"unless",
|
||||
"handle",
|
||||
"layout",
|
||||
"packed",
|
||||
"premerge",
|
||||
"regardless",
|
||||
"typedef",
|
||||
"active",
|
||||
"do",
|
||||
"enum",
|
||||
"input",
|
||||
"output",
|
||||
"private",
|
||||
"resource",
|
||||
"restrict",
|
||||
"self",
|
||||
"std",
|
||||
"where",
|
||||
];
|
||||
|
||||
let g = EditGraph::default_chain();
|
||||
for cap in g.capabilities() {
|
||||
// Activate the whole operation so its fragment is emitted.
|
||||
let mut probe = EditGraph::default_chain();
|
||||
for p in &cap.params {
|
||||
if let ParamKind::Scalar { max, .. } = p.kind {
|
||||
probe.set_param(cap.id, p.id, max * 0.5);
|
||||
}
|
||||
}
|
||||
let source = probe.compose().source;
|
||||
|
||||
for keyword in RESERVED {
|
||||
let declaration = format!("let {keyword} ");
|
||||
let var_declaration = format!("var {keyword} ");
|
||||
assert!(
|
||||
!source.contains(&declaration) && !source.contains(&var_declaration),
|
||||
"{} declares `{keyword}`, which is a WGSL reserved keyword",
|
||||
cap.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user