diff --git a/core/dr-sync-folder/examples/scan.rs b/core/dr-sync-folder/examples/scan.rs new file mode 100644 index 0000000..a8c96f8 --- /dev/null +++ b/core/dr-sync-folder/examples/scan.rs @@ -0,0 +1,71 @@ +//! Scan a real folder through the engine, and read a preview out of it. +//! +//! ```text +//! cargo run -p dr-sync-folder --example scan -- /path/to/photos +//! ``` +//! +//! Exercises the same code the application runs: `dr_sync::scan` driving the +//! folder connector, then a ranged `get` of the kind the thumbnail worker +//! makes. Reads only — it never writes into the folder it is pointed at. +use std::collections::HashMap; + +use dr_sync::{RemoteBackend, RemoteId, RemotePath}; +use dr_sync_folder::FolderBackend; +use dr_types::FormatFilter; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let Some(root) = std::env::args().nth(1) else { + eprintln!("usage: scan "); + std::process::exit(2); + }; + + let backend = match FolderBackend::new(&root) { + Ok(b) => b, + Err(e) => { + eprintln!("{e}"); + std::process::exit(1); + } + }; + + let caps = backend.capabilities(); + println!("{} at {root}", backend.name()); + println!( + " strategy: {}", + dr_sync::SyncStrategy::for_capabilities(caps).describe() + ); + + let started = std::time::Instant::now(); + let result = dr_sync::scan( + &backend, + &RemotePath::root(), + &FormatFilter::all(), + &HashMap::new(), + |_| {}, + ) + .await + .expect("scan"); + + println!( + " {} image(s) in {} director(ies), {:?}", + result.images.len(), + result.progress.directories_listed, + started.elapsed() + ); + + let Some(first) = result.images.first() else { + return; + }; + println!( + " first: {} ({} bytes) id {:?}", + first.path, first.size, first.id + ); + + // The shape of request the thumbnail worker makes: a header window, not + // the whole file. + let head = backend + .get(&RemoteId::Path(first.path.clone()), Some(0..65536)) + .await + .expect("ranged read"); + println!(" read {} header bytes", head.len()); +} diff --git a/docs/architecture.md b/docs/architecture.md index bbca51c..0a861ee 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -794,9 +794,11 @@ Three things worth carrying forward: - **`LocalEtags` is the honest answer, and it costs nothing.** A POSIX directory's mtime describes its own entry list and nothing below it, so there is no propagation to exploit and the engine - walks the tree every scan. The walk that was expensive was expensive because it was 50k - `PROPFIND`s; 50k `stat` calls take well under a second. This is the capability model paying for - itself — one engine, two backends, each running at the speed it actually runs at. + walks the tree every scan. **Measured 2026-08-28**: a full uncached walk of 2,299 images + across 233 directories took **137 ms**, with no pruning at all — against 34.1 s for 17,185 RAWs + over WebDAV *with* pruning (§8.4). The walk that was expensive was expensive because it was + thousands of `PROPFIND`s. This is the capability model paying for itself — one engine, two + backends, each running at the speed it actually runs at. - **Identity is a path hash, not an inode.** An inode is stable across a rename but differs between devices and is reused after a delete, so two machines would disagree about which photograph a thumbnail belonged to and a recycled inode would attach an old thumbnail to a new image. diff --git a/docs/storage.md b/docs/storage.md index 6cfff9d..41c63a5 100644 --- a/docs/storage.md +++ b/docs/storage.md @@ -309,10 +309,16 @@ changes when its own entry list changes and at no other time — not when a child's contents are edited, and not for a grandchild. There is nothing to propagate, so `dir_validator` returns `Unsupported` and the engine walks the tree every scan. Which costs almost nothing, because the walk that was expensive -was expensive for a reason this backend does not have: fifty thousand `stat` -calls against a filesystem take well under a second, and fifty thousand -`PROPFIND`s do not. The capability model is what lets both be driven by the same -engine at the speed each actually runs at. +was expensive for a reason this backend does not have. + +**Measured 2026-08-28**, `cargo run -p dr-sync-folder --example scan`: a full +uncached walk of 2,299 images across 233 directories completed in **137 ms**, +and 380 images across 13 directories in **29 ms** — the same engine, the same +`Depth: 1`-per-directory walk, with no pruning at all. The Nextcloud connector's +comparable figure is 34.1 s for 17,185 RAWs across 334 directories *with* +pruning available (ARCH §8.4). The capability model is what lets one engine +drive both at the speed each actually runs at, instead of forcing the fast one +down to the slow one's interface. **Identity is a hash of the path relative to the library root**, FNV-1a 64 (written out, because `DefaultHasher` is explicitly unstable between Rust