max / audiofiles
8 files changed,
+101 insertions,
-24 deletions
| @@ -23,7 +23,7 @@ version = "0.5.2" | |||
| 23 | 23 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 24 | 24 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" | |
| 25 | 25 | dependencies = [ | |
| 26 | - | "crypto-common", | |
| 26 | + | "crypto-common 0.1.7", | |
| 27 | 27 | "generic-array", | |
| 28 | 28 | ] | |
| 29 | 29 | ||
| @@ -421,6 +421,7 @@ version = "0.5.0" | |||
| 421 | 421 | dependencies = [ | |
| 422 | 422 | "bs1770", | |
| 423 | 423 | "dirs", | |
| 424 | + | "hex", | |
| 424 | 425 | "hound", | |
| 425 | 426 | "rayon", | |
| 426 | 427 | "realfft", | |
| @@ -428,7 +429,7 @@ dependencies = [ | |||
| 428 | 429 | "rusqlite", | |
| 429 | 430 | "serde", | |
| 430 | 431 | "serde_json", | |
| 431 | - | "sha2", | |
| 432 | + | "sha2 0.11.0", | |
| 432 | 433 | "stratum-dsp", | |
| 433 | 434 | "symphonia", | |
| 434 | 435 | "tagtree", | |
| @@ -458,13 +459,14 @@ dependencies = [ | |||
| 458 | 459 | "audiofiles-core", | |
| 459 | 460 | "base64", | |
| 460 | 461 | "chrono", | |
| 462 | + | "hex", | |
| 461 | 463 | "open", | |
| 462 | 464 | "parking_lot", | |
| 463 | 465 | "rand", | |
| 464 | 466 | "rusqlite", | |
| 465 | 467 | "serde", | |
| 466 | 468 | "serde_json", | |
| 467 | - | "sha2", | |
| 469 | + | "sha2 0.11.0", | |
| 468 | 470 | "synckit-client", | |
| 469 | 471 | "tempfile", | |
| 470 | 472 | "thiserror 2.0.19", | |
| @@ -527,7 +529,7 @@ version = "0.10.6" | |||
| 527 | 529 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 528 | 530 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" | |
| 529 | 531 | dependencies = [ | |
| 530 | - | "digest", | |
| 532 | + | "digest 0.10.7", | |
| 531 | 533 | ] | |
| 532 | 534 | ||
| 533 | 535 | [[package]] | |
| @@ -540,6 +542,15 @@ dependencies = [ | |||
| 540 | 542 | ] | |
| 541 | 543 | ||
| 542 | 544 | [[package]] | |
| 545 | + | name = "block-buffer" | |
| 546 | + | version = "0.12.1" | |
| 547 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 548 | + | checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" | |
| 549 | + | dependencies = [ | |
| 550 | + | "hybrid-array", | |
| 551 | + | ] | |
| 552 | + | ||
| 553 | + | [[package]] | |
| 543 | 554 | name = "block-padding" | |
| 544 | 555 | version = "0.3.3" | |
| 545 | 556 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -818,7 +829,7 @@ version = "0.4.4" | |||
| 818 | 829 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 819 | 830 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" | |
| 820 | 831 | dependencies = [ | |
| 821 | - | "crypto-common", | |
| 832 | + | "crypto-common 0.1.7", | |
| 822 | 833 | "inout", | |
| 823 | 834 | "zeroize", | |
| 824 | 835 | ] | |
| @@ -870,6 +881,12 @@ dependencies = [ | |||
| 870 | 881 | ] | |
| 871 | 882 | ||
| 872 | 883 | [[package]] | |
| 884 | + | name = "const-oid" | |
| 885 | + | version = "0.10.2" | |
| 886 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 887 | + | checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" | |
| 888 | + | ||
| 889 | + | [[package]] | |
| 873 | 890 | name = "const-random" | |
| 874 | 891 | version = "0.1.18" | |
| 875 | 892 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -1083,6 +1100,15 @@ dependencies = [ | |||
| 1083 | 1100 | ] | |
| 1084 | 1101 | ||
| 1085 | 1102 | [[package]] | |
| 1103 | + | name = "crypto-common" | |
| 1104 | + | version = "0.2.2" | |
| 1105 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1106 | + | checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" | |
| 1107 | + | dependencies = [ | |
| 1108 | + | "hybrid-array", | |
| 1109 | + | ] | |
| 1110 | + | ||
| 1111 | + | [[package]] | |
| 1086 | 1112 | name = "cursor-icon" | |
| 1087 | 1113 | version = "1.2.0" | |
| 1088 | 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -1100,12 +1126,23 @@ version = "0.10.7" | |||
| 1100 | 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1101 | 1127 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" | |
| 1102 | 1128 | dependencies = [ | |
| 1103 | - | "block-buffer", | |
| 1104 | - | "crypto-common", | |
| 1129 | + | "block-buffer 0.10.4", | |
| 1130 | + | "crypto-common 0.1.7", | |
| 1105 | 1131 | "subtle", | |
| 1106 | 1132 | ] | |
| 1107 | 1133 | ||
| 1108 | 1134 | [[package]] | |
| 1135 | + | name = "digest" | |
| 1136 | + | version = "0.11.3" | |
| 1137 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1138 | + | checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" | |
| 1139 | + | dependencies = [ | |
| 1140 | + | "block-buffer 0.12.1", | |
| 1141 | + | "const-oid", | |
| 1142 | + | "crypto-common 0.2.2", | |
| 1143 | + | ] | |
| 1144 | + | ||
| 1145 | + | [[package]] | |
| 1109 | 1146 | name = "dirs" | |
| 1110 | 1147 | version = "6.0.0" | |
| 1111 | 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -2150,7 +2187,7 @@ version = "0.12.1" | |||
| 2150 | 2187 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2151 | 2188 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" | |
| 2152 | 2189 | dependencies = [ | |
| 2153 | - | "digest", | |
| 2190 | + | "digest 0.10.7", | |
| 2154 | 2191 | ] | |
| 2155 | 2192 | ||
| 2156 | 2193 | [[package]] | |
| @@ -2199,6 +2236,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
| 2199 | 2236 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | |
| 2200 | 2237 | ||
| 2201 | 2238 | [[package]] | |
| 2239 | + | name = "hybrid-array" | |
| 2240 | + | version = "0.4.13" | |
| 2241 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2242 | + | checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" | |
| 2243 | + | dependencies = [ | |
| 2244 | + | "typenum", | |
| 2245 | + | ] | |
| 2246 | + | ||
| 2247 | + | [[package]] | |
| 2202 | 2248 | name = "hyper" | |
| 2203 | 2249 | version = "1.11.0" | |
| 2204 | 2250 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| @@ -4421,7 +4467,7 @@ dependencies = [ | |||
| 4421 | 4467 | "num", | |
| 4422 | 4468 | "once_cell", | |
| 4423 | 4469 | "serde", | |
| 4424 | - | "sha2", | |
| 4470 | + | "sha2 0.10.9", | |
| 4425 | 4471 | "zbus", | |
| 4426 | 4472 | ] | |
| 4427 | 4473 | ||
| @@ -4543,7 +4589,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" | |||
| 4543 | 4589 | dependencies = [ | |
| 4544 | 4590 | "cfg-if", | |
| 4545 | 4591 | "cpufeatures 0.2.17", | |
| 4546 | - | "digest", | |
| 4592 | + | "digest 0.10.7", | |
| 4593 | + | ] | |
| 4594 | + | ||
| 4595 | + | [[package]] | |
| 4596 | + | name = "sha2" | |
| 4597 | + | version = "0.11.0" | |
| 4598 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4599 | + | checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" | |
| 4600 | + | dependencies = [ | |
| 4601 | + | "cfg-if", | |
| 4602 | + | "cpufeatures 0.3.0", | |
| 4603 | + | "digest 0.11.3", | |
| 4547 | 4604 | ] | |
| 4548 | 4605 | ||
| 4549 | 4606 | [[package]] | |
| @@ -5025,7 +5082,7 @@ dependencies = [ | |||
| 5025 | 5082 | "rusqlite", | |
| 5026 | 5083 | "serde", | |
| 5027 | 5084 | "serde_json", | |
| 5028 | - | "sha2", | |
| 5085 | + | "sha2 0.10.9", | |
| 5029 | 5086 | "thiserror 2.0.19", | |
| 5030 | 5087 | "tokio", | |
| 5031 | 5088 | "tokio-stream", | |
| @@ -5573,7 +5630,7 @@ version = "0.5.1" | |||
| 5573 | 5630 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5574 | 5631 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" | |
| 5575 | 5632 | dependencies = [ | |
| 5576 | - | "crypto-common", | |
| 5633 | + | "crypto-common 0.1.7", | |
| 5577 | 5634 | "subtle", | |
| 5578 | 5635 | ] | |
| 5579 | 5636 |
| @@ -18,7 +18,8 @@ eframe = { version = "0.34", default-features = false, features = ["default_font | |||
| 18 | 18 | cpal = "0.17" | |
| 19 | 19 | rusqlite = { version = "0.39", features = ["bundled", "functions"] } | |
| 20 | 20 | thiserror = "2.0.18" | |
| 21 | - | sha2 = "0.10.9" | |
| 21 | + | sha2 = "0.11.0" | |
| 22 | + | hex = "0.4.3" | |
| 22 | 23 | symphonia = { version = "0.5.5", default-features = false, features = ["wav", "aiff", "mp3", "flac", "ogg", "vorbis", "pcm", "aac", "alac", "isomp4", "caf"] } | |
| 23 | 24 | parking_lot = "0.12.5" | |
| 24 | 25 | dirs = "6.0.0" |
| @@ -11,6 +11,7 @@ analysis = ["dep:bs1770", "dep:realfft"] | |||
| 11 | 11 | rusqlite = { workspace = true } | |
| 12 | 12 | thiserror = { workspace = true } | |
| 13 | 13 | sha2 = { workspace = true } | |
| 14 | + | hex = { workspace = true } | |
| 14 | 15 | symphonia = { workspace = true } | |
| 15 | 16 | stratum-dsp = { workspace = true } | |
| 16 | 17 | serde = { workspace = true } |
| @@ -90,7 +90,7 @@ impl SampleHash { | |||
| 90 | 90 | /// | |
| 91 | 91 | /// `store::validate_hash` delegates here, so there is one rule set rather than | |
| 92 | 92 | /// two that can drift. Lowercase-only matches how the app produces hashes | |
| 93 | - | /// (`format!("{:x}")`), so every real hash passes and uppercase is rejected. | |
| 93 | + | /// (`hex::encode`), so every real hash passes and uppercase is rejected. | |
| 94 | 94 | pub fn validated(s: &str) -> std::result::Result<Self, &'static str> { | |
| 95 | 95 | if s.len() != 64 | |
| 96 | 96 | || !s |
| @@ -126,7 +126,7 @@ pub fn relocate_missing_loose_files(db: &Database, search_root: &Path) -> Result | |||
| 126 | 126 | if !ok { | |
| 127 | 127 | continue; | |
| 128 | 128 | } | |
| 129 | - | let computed = format!("{:x}", hasher.finalize()); | |
| 129 | + | let computed = hex::encode(hasher.finalize()); | |
| 130 | 130 | if computed == *hash { | |
| 131 | 131 | let abs = cand | |
| 132 | 132 | .canonicalize() |
| @@ -442,7 +442,7 @@ impl SampleStore { | |||
| 442 | 442 | } | |
| 443 | 443 | hasher.update(&buf[..n]); | |
| 444 | 444 | } | |
| 445 | - | let computed = format!("{:x}", hasher.finalize()); | |
| 445 | + | let computed = hex::encode(hasher.finalize()); | |
| 446 | 446 | ||
| 447 | 447 | Ok(computed == hash) | |
| 448 | 448 | } | |
| @@ -769,7 +769,7 @@ pub fn relocate_sample( | |||
| 769 | 769 | } | |
| 770 | 770 | hasher.update(&buf[..n]); | |
| 771 | 771 | } | |
| 772 | - | let computed = format!("{:x}", hasher.finalize()); | |
| 772 | + | let computed = hex::encode(hasher.finalize()); | |
| 773 | 773 | ||
| 774 | 774 | if computed != hash { | |
| 775 | 775 | return Err(CoreError::Internal(format!( | |
| @@ -827,7 +827,7 @@ pub fn hash_file(path: &Path) -> Result<(String, i64)> { | |||
| 827 | 827 | } | |
| 828 | 828 | hasher.update(&buf[..n]); | |
| 829 | 829 | } | |
| 830 | - | Ok((format!("{:x}", hasher.finalize()), file_size)) | |
| 830 | + | Ok((hex::encode(hasher.finalize()), file_size)) | |
| 831 | 831 | } | |
| 832 | 832 | ||
| 833 | 833 | /// Copy `src` to `dst` while computing the SHA-256 of the bytes actually | |
| @@ -850,7 +850,7 @@ fn copy_hashing(src: &Path, dst: &Path) -> Result<String> { | |||
| 850 | 850 | output.write_all(&buf[..n]).map_err(|e| io_err(dst, e))?; | |
| 851 | 851 | } | |
| 852 | 852 | output.sync_all().map_err(|e| io_err(dst, e))?; | |
| 853 | - | Ok(format!("{:x}", hasher.finalize())) | |
| 853 | + | Ok(hex::encode(hasher.finalize())) | |
| 854 | 854 | } | |
| 855 | 855 | ||
| 856 | 856 | /// Hash a batch of files in parallel (rayon). Each result is aligned to the | |
| @@ -1101,6 +1101,23 @@ mod tests { | |||
| 1101 | 1101 | } | |
| 1102 | 1102 | ||
| 1103 | 1103 | #[test] | |
| 1104 | + | fn hash_file_matches_the_reference_sha256_digest() { | |
| 1105 | + | // The content address is the library's primary key, so the exact hex | |
| 1106 | + | // string a given byte sequence produces is a compatibility guarantee: | |
| 1107 | + | // change it and every stored path and database row stops resolving. | |
| 1108 | + | // Pinned against an independent SHA-256 of the same bytes, so a hasher | |
| 1109 | + | // or hex-encoding swap has to survive a known answer, not just agree | |
| 1110 | + | // with itself. | |
| 1111 | + | let (dir, _db, _store) = setup(); | |
| 1112 | + | let src = create_test_file(&dir, "kick.wav", b"fake audio data"); | |
| 1113 | + | let (hash, _) = hash_file(&src).unwrap(); | |
| 1114 | + | assert_eq!( | |
| 1115 | + | hash, "cec560f942befcb4e4a4d1161c5c03b3a787e2d525f650042641e62bf8773c69", | |
| 1116 | + | "SHA-256 of b\"fake audio data\", lowercase hex, no separators" | |
| 1117 | + | ); | |
| 1118 | + | } | |
| 1119 | + | ||
| 1120 | + | #[test] | |
| 1104 | 1121 | fn import_hashed_matches_serial_import() { | |
| 1105 | 1122 | let (dir, db, store) = setup(); | |
| 1106 | 1123 | let src = create_test_file(&dir, "snare.wav", b"some audio bytes"); | |
| @@ -1221,7 +1238,7 @@ mod tests { | |||
| 1221 | 1238 | let src = create_test_file(&dir, "kick.wav", content); | |
| 1222 | 1239 | ||
| 1223 | 1240 | // Pre-place a truncated blob at the canonical path the real import targets. | |
| 1224 | - | let hash = format!("{:x}", Sha256::digest(content)); | |
| 1241 | + | let hash = hex::encode(Sha256::digest(content)); | |
| 1225 | 1242 | let dest = store.sample_path(&hash, "wav").unwrap(); | |
| 1226 | 1243 | fs::create_dir_all(dest.parent().unwrap()).unwrap(); | |
| 1227 | 1244 | fs::write(&dest, b"trunc").unwrap(); | |
| @@ -1233,7 +1250,7 @@ mod tests { | |||
| 1233 | 1250 | let stored = fs::read(&dest).unwrap(); | |
| 1234 | 1251 | assert_eq!(stored, content, "truncated blob must be repaired on import"); | |
| 1235 | 1252 | assert_eq!( | |
| 1236 | - | format!("{:x}", Sha256::digest(&stored)), | |
| 1253 | + | hex::encode(Sha256::digest(&stored)), | |
| 1237 | 1254 | hash, | |
| 1238 | 1255 | "repaired blob hashes back to its content address" | |
| 1239 | 1256 | ); | |
| @@ -1377,7 +1394,7 @@ mod tests { | |||
| 1377 | 1394 | // committed to `{wrong_hash}.ext`. | |
| 1378 | 1395 | let (dir, db, store) = setup(); | |
| 1379 | 1396 | let src = create_test_file(&dir, "kick.wav", b"the actual bytes on disk"); | |
| 1380 | - | let wrong_hash = format!("{:x}", Sha256::digest(b"what we hashed earlier")); | |
| 1397 | + | let wrong_hash = hex::encode(Sha256::digest(b"what we hashed earlier")); | |
| 1381 | 1398 | ||
| 1382 | 1399 | let result = store.import_hashed(&src, &wrong_hash, 24, &db); | |
| 1383 | 1400 | assert!( |
| @@ -14,6 +14,7 @@ serde_json = { workspace = true } | |||
| 14 | 14 | tracing = { workspace = true } | |
| 15 | 15 | uuid = { workspace = true } | |
| 16 | 16 | sha2 = { workspace = true } | |
| 17 | + | hex = { workspace = true } | |
| 17 | 18 | rand = { workspace = true } | |
| 18 | 19 | base64 = { workspace = true } | |
| 19 | 20 | thiserror = { workspace = true } |
| @@ -114,7 +114,7 @@ pub async fn download_missing_blobs( | |||
| 114 | 114 | }; | |
| 115 | 115 | ||
| 116 | 116 | // Verify SHA-256 before writing | |
| 117 | - | let computed = format!("{:x}", Sha256::digest(&data)); | |
| 117 | + | let computed = hex::encode(Sha256::digest(&data)); | |
| 118 | 118 | if computed != *hash { | |
| 119 | 119 | tracing::warn!( | |
| 120 | 120 | expected = hash, | |
| @@ -212,7 +212,7 @@ pub async fn download_one_blob( | |||
| 212 | 212 | .await | |
| 213 | 213 | .map_err(|e| SyncError::Client(e.to_string()))?; | |
| 214 | 214 | ||
| 215 | - | let computed = format!("{:x}", Sha256::digest(&data)); | |
| 215 | + | let computed = hex::encode(Sha256::digest(&data)); | |
| 216 | 216 | if computed != *hash { | |
| 217 | 217 | return Err(SyncError::Other(format!( | |
| 218 | 218 | "Downloaded blob hash mismatch (expected {hash}, got {computed})" |