File hashing in naming schemes: {crc32}/{md5}/{sha1} via Hash Files
Closes the last remaining gap in porting-roadmap item 1 (full naming-
scheme bindings) - user picked this from a short list of next-feature
options after the recent naming-scheme/Match usability work.
crates/aurora-curator-core/src/sfv: new compute_all_checksums(path),
alongside the existing compute_checksum(path, kind). Streams the file
ONCE, updating CRC32/MD5/SHA-1 hashers concurrently over each chunk,
rather than calling compute_checksum three times (three full reads of
what's often a multi-gigabyte video file). The SFV panel itself keeps
using compute_checksum directly - it only ever needs one algorithm at a
time, picked by the user, so computing the other two nobody asked for
would be wasteful in the other direction. 3 new tests: matches known
CRC32/MD5/SHA-1 test vectors together, agrees with compute_checksum on a
file bigger than the read buffer (catches a hasher only seeing the first
chunk), errors on a missing file.
src-tauri/src/sfv.rs: new NamingHashOutcome struct (plain
#[serde(rename_all = "camelCase")] struct, not a tagged enum - the class
of bug fixed in the nineteenth delivery doesn't apply here) and
compute_naming_hashes command, batching compute_all_checksums the same
blocking-thread way compute_checksums already does, with the same
per-file error isolation (one bad file doesn't abort the batch).
Registered in lib.rs.
RenamePanel.tsx: new runHashFiles() and a "Hash Files" button (new
HashIcon in Icons.tsx) next to Fetch Data. Deliberately NOT wired into
Match automatically like the rest of its local steps - hashing reads a
file's entire contents rather than a small header, so running it
unasked on every Match click for every loaded file would make Match
unpredictably slow. Its own busy state (hashing) disables Match/Fetch
Data/Hash Files against each other while any one is running. New "File
hashing" field-reference group ({crc32}/{md5}/{sha1} as clickable chips,
reusing the field-chip insert-at-cursor mechanism from the earlier
naming-scheme UX delivery) and updated inline help text.
Verified via cargo test --workspace (191 -> 193 in the core crate: 145 ->
148) + cargo check --workspace (clean) + npm run build (clean) + a
10-check Playwright script against npm run dev (mocking
window.__TAURI_INTERNALS__.invoke): confirmed the three field chips are
present, Hash Files calls compute_naming_hashes with every loaded path,
the status line correctly reports hashed-vs-failed counts, a computed
CRC32 reaches the live rename preview, a failed file's row doesn't
fabricate a hash, and clicking Hash Files with nothing loaded is handled
gracefully. Caught one test-mock bug along the way (not an app bug): an
early mock response for a successful hash omitted the `error` field
entirely rather than setting it to `null`, which the app correctly (per
the real Rust command's always-present Option<String> field) treated as
a truthy non-null value and skipped merging - fixed the mock to match
the real wire contract.
Updated README (status paragraph, project layout for sfv.rs's now-four
commands, a new "Hash Files" section placed after "Real media-file
probing", porting roadmap item 1's "still missing" list).
This commit is contained in:
@@ -127,6 +127,7 @@ pub fn run() {
|
||||
sfv::compute_checksums,
|
||||
sfv::save_checksum_file,
|
||||
sfv::verify_checksum_file,
|
||||
sfv::compute_naming_hashes,
|
||||
list::export_text_list,
|
||||
probe::probe_media_files,
|
||||
history::list_rename_history,
|
||||
|
||||
@@ -53,6 +53,51 @@ pub async fn compute_checksums(paths: Vec<String>, kind: ChecksumKind) -> Vec<Ch
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// All three checksums for one file, for the naming-scheme `{crc32}`/`{md5}`/`{sha1}`
|
||||
/// fields (see `RenamePanel.tsx`'s `runHashFiles`) - unlike `ChecksumOutcome` above,
|
||||
/// which is one algorithm at a time for the SFV panel, this is all three at once so a
|
||||
/// naming scheme referencing more than one of them doesn't hash the same file three
|
||||
/// times over. `error` is set (and all three checksums left `None`) if hashing this
|
||||
/// particular file failed - kept per-file, same as `ChecksumOutcome`, so one bad file
|
||||
/// doesn't abort the whole batch.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NamingHashOutcome {
|
||||
pub path: String,
|
||||
pub crc32: Option<String>,
|
||||
pub md5: Option<String>,
|
||||
pub sha1: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// Computes CRC32 + MD5 + SHA-1 for every path in `paths` in one pass per file (see
|
||||
/// `aurora_curator_core::sfv::compute_all_checksums`). Same blocking-thread treatment
|
||||
/// as `compute_checksums` - real file I/O, potentially large video files - and
|
||||
/// deliberately a separate, explicit action from "Match" rather than something that
|
||||
/// runs automatically: hashing a whole file is much slower than Match's other local
|
||||
/// steps (filename parsing, container-header probing), which only read a small header,
|
||||
/// not the entire file.
|
||||
#[tauri::command]
|
||||
pub async fn compute_naming_hashes(paths: Vec<String>) -> Vec<NamingHashOutcome> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|path| match sfv::compute_all_checksums(Path::new(&path)) {
|
||||
Ok(all) => NamingHashOutcome {
|
||||
path,
|
||||
crc32: Some(all.crc32),
|
||||
md5: Some(all.md5),
|
||||
sha1: Some(all.sha1),
|
||||
error: None,
|
||||
},
|
||||
Err(err) => NamingHashOutcome { path, crc32: None, md5: None, sha1: None, error: Some(err.to_string()) },
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// One row the frontend already has (a computed checksum for a loaded file) - the
|
||||
/// input shape `save_checksum_file` builds a sidecar file's text content from.
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user