Skip to main content

max / audiofiles

core: thread SampleHash through the storage/VFS API (was &str) Content hashes crossed the storage and VFS boundary as bare &str, so any string could be passed where a validated SHA-256 content address was meant. Thread the SampleHash newtype through that surface instead: - SampleStore::{import_hashed, import_loose_files_hashed, exists, sample_path, remove, verify_sample}, the free resolvers (resolve_file_path, sample_extension[_any], sample_original_name, query_sample_field), and vfs::create_sample_link now take &SampleHash. - Internal DB reads yield SampleHash directly via the existing FromSql impl; sample_path keeps validate_hash as a defense-in-depth guard. - Callers holding a trusted string (import output, DB rows, UI-supplied hashes at the DirectBackend boundary) construct SampleHash::from_trusted at the crossing, concentrating the trust assertion at the storage layer. The analysis/tags/rules/similarity query layers keep their &str API. No behavior change. Build, clippy (-D warnings), and the full core/browser/sync suites (662 + 91 + e2e) stay green.
Co-Authored-By
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-25 01:17 UTC
Signed with PGP, not checked
Commit: 2b65332e327781fbbed46bebc9853f1b4be95cb6
Parent: 635bf8e
16 files changed, +639 insertions, -144 deletions
@@ -270,6 +270,7 @@
270 270 Some(Err(msg)) => return Err(CoreError::Internal(msg)),
271 271 None => audiofiles_core::store::hash_file(path)?,
272 272 };
273 + let hash = audiofiles_core::SampleHash::from_trusted(hash);
273 274 if loose_files {
274 275 store.import_loose_files_hashed(path, &hash, file_size, db)?;
275 276 } else {
@@ -279,7 +280,7 @@
279 280 let ext = audiofiles_core::util::get_extension(path);
280 281
281 282 match vfs::create_sample_link(db, vfs_id, parent_id, &name, &hash) {
282 - Ok(_) => Ok(ImportFileResult::Imported(hash, ext)),
283 + Ok(_) => Ok(ImportFileResult::Imported(hash.into_inner(), ext)),
283 284 Err(CoreError::NameConflict(_)) => Ok(ImportFileResult::Duplicate),
284 285 Err(e) => {
285 286 if let CoreError::Db(ref sqlite_err) = e
@@ -2426,10 +2426,12 @@
2426 2426
2427 2427 // sample_extension reads via query_sample_field, which now filters
2428 2428 // out tombstoned rows.
2429 - let live_ext = crate::store::sample_extension(&db, "live").unwrap();
2429 + let live_ext =
2430 + crate::store::sample_extension(&db, &crate::SampleHash::from_trusted("live")).unwrap();
2430 2431 assert_eq!(live_ext, "wav");
2431 2432
2432 - let tomb_ext = crate::store::sample_extension(&db, "tomb");
2433 + let tomb_ext =
2434 + crate::store::sample_extension(&db, &crate::SampleHash::from_trusted("tomb"));
2433 2435 assert!(
2434 2436 matches!(tomb_ext, Err(crate::error::CoreError::SampleNotFound(_))),
2435 2437 "tombstoned sample should be hidden from sample_extension; got {tomb_ext:?}"
@@ -409,8 +409,22 @@
409 409 insert_fake_sample(&db, "hash2");
410 410
411 411 let vfs_id = vfs::create_vfs(&db, "Test").unwrap();
412 - vfs::create_sample_link(&db, vfs_id, None, "kick.wav", "hash1").unwrap();
413 - vfs::create_sample_link(&db, vfs_id, None, "snare.wav", "hash2").unwrap();
412 + vfs::create_sample_link(
413 + &db,
414 + vfs_id,
415 + None,
416 + "kick.wav",
417 + &crate::SampleHash::from_trusted("hash1"),
418 + )
419 + .unwrap();
420 + vfs::create_sample_link(
421 + &db,
422 + vfs_id,
423 + None,
424 + "snare.wav",
425 + &crate::SampleHash::from_trusted("hash2"),
426 + )
427 + .unwrap();
414 428
415 429 (db, vfs_id)
416 430 }
@@ -500,7 +514,14 @@
500 514 for i in 0..(SEARCH_RESULT_LIMIT + 50) {
501 515 let hash = format!("h{i}");
502 516 insert_fake_sample(&db, &hash);
503 - vfs::create_sample_link(&db, vfs_id, None, &format!("kick_{i}.wav"), &hash).unwrap();
517 + vfs::create_sample_link(
518 + &db,
519 + vfs_id,
520 + None,
521 + &format!("kick_{i}.wav"),
522 + &crate::SampleHash::from_trusted(hash.clone()),
523 + )
524 + .unwrap();
504 525 }
505 526 let filter = SearchFilter {
506 527 text_query: "kick".to_string(),
@@ -61,5 +61,12 @@
61 61 };
62 62 crate::analysis::save_analysis_batch(db, std::slice::from_ref(&result)).unwrap();
63 63
64 - crate::vfs::create_sample_link(db, vfs_id, None, name, hash).unwrap()
64 + crate::vfs::create_sample_link(
65 + db,
66 + vfs_id,
67 + None,
68 + name,
69 + &crate::SampleHash::from_trusted(hash),
70 + )
71 + .unwrap()
65 72 }
@@ -243,7 +243,7 @@
243 243 vfs_id: VfsId,
244 244 parent_id: Option<NodeId>,
245 245 name: &str,
246 - sample_hash: &str,
246 + sample_hash: &SampleHash,
247 247 ) -> Result<NodeId> {
248 248 validate_node_name(name)?;
249 249 check_root_name_conflict(db, vfs_id, parent_id, name)?;
@@ -706,8 +706,22 @@
706 706 let dir = create_directory(&db, vfs_id, None, "Drums").unwrap();
707 707 insert_fake_sample(&db, "live");
708 708 insert_fake_sample(&db, "dead");
709 - create_sample_link(&db, vfs_id, Some(dir), "live.wav", "live").unwrap();
710 - create_sample_link(&db, vfs_id, Some(dir), "dead.wav", "dead").unwrap();
709 + create_sample_link(
710 + &db,
711 + vfs_id,
712 + Some(dir),
713 + "live.wav",
714 + &crate::SampleHash::from_trusted("live"),
715 + )
716 + .unwrap();
717 + create_sample_link(
718 + &db,
719 + vfs_id,
720 + Some(dir),
721 + "dead.wav",
722 + &crate::SampleHash::from_trusted("dead"),
723 + )
724 + .unwrap();
711 725
712 726 let before = list_full_tree(&db).unwrap();
713 727 assert!(before.iter().any(|n| n.path.ends_with("dead.wav")));
@@ -773,7 +787,14 @@
773 787 insert_fake_sample(&db, "abc123");
774 788 let vfs_id = create_vfs(&db, "Lib").unwrap();
775 789
776 - let node_id = create_sample_link(&db, vfs_id, None, "kick.wav", "abc123").unwrap();
790 + let node_id = create_sample_link(
791 + &db,
792 + vfs_id,
793 + None,
794 + "kick.wav",
795 + &crate::SampleHash::from_trusted("abc123"),
796 + )
797 + .unwrap();
777 798 let node = get_node(&db, node_id).unwrap();
778 799 assert_eq!(node.node_type, NodeType::Sample);
779 800 assert_eq!(node.sample_hash.as_deref(), Some("abc123"));
@@ -870,7 +891,14 @@
870 891 insert_fake_sample(&db, "sample1");
871 892 let vfs_id = create_vfs(&db, "Lib").unwrap();
872 893
873 - create_sample_link(&db, vfs_id, None, "zzz.wav", "sample1").unwrap();
894 + create_sample_link(
895 + &db,
896 + vfs_id,
897 + None,
898 + "zzz.wav",
899 + &crate::SampleHash::from_trusted("sample1"),
900 + )
901 + .unwrap();
874 902 create_directory(&db, vfs_id, None, "AAA").unwrap();
875 903
876 904 let children = list_children(&db, vfs_id, None).unwrap();
@@ -884,7 +912,14 @@
884 912 let vfs_id = create_vfs(&db, "Lib").unwrap();
885 913 insert_fake_sample(&db, "s1");
886 914 let dir = create_directory(&db, vfs_id, None, "Dir").unwrap();
887 - create_sample_link(&db, vfs_id, Some(dir), "s1.wav", "s1").unwrap();
915 + create_sample_link(
916 + &db,
917 + vfs_id,
918 + Some(dir),
919 + "s1.wav",
920 + &crate::SampleHash::from_trusted("s1"),
921 + )
922 + .unwrap();
888 923
889 924 let subtree = collect_subtree(&db, dir).unwrap();
890 925 assert_eq!(subtree.len(), 2); // dir + sample
@@ -949,7 +984,14 @@
949 984 let db = setup();
950 985 let vfs_id = create_vfs(&db, "Lib").unwrap();
951 986 crate::test_helpers::insert_fake_sample(&db, "hash1");
952 - create_sample_link(&db, vfs_id, None, "kick.wav", "hash1").unwrap();
987 + create_sample_link(
988 + &db,
989 + vfs_id,
990 + None,
991 + "kick.wav",
992 + &crate::SampleHash::from_trusted("hash1"),
993 + )
994 + .unwrap();
953 995
954 996 let nodes = list_children_enriched(&db, vfs_id, None).unwrap();
955 997 assert_eq!(nodes.len(), 1);
@@ -961,7 +1003,14 @@
961 1003 let db = setup();
962 1004 let vfs_id = create_vfs(&db, "Lib").unwrap();
963 1005 crate::test_helpers::insert_fake_sample(&db, "hash1");
964 - create_sample_link(&db, vfs_id, None, "kick.wav", "hash1").unwrap();
1006 + create_sample_link(
1007 + &db,
1008 + vfs_id,
1009 + None,
1010 + "kick.wav",
1011 + &crate::SampleHash::from_trusted("hash1"),
1012 + )
1013 + .unwrap();
965 1014
966 1015 // Set cloud_only=1 directly
967 1016 db.conn()
@@ -992,9 +1041,30 @@
992 1041 insert_fake_sample(&db, "hash_a");
993 1042 insert_fake_sample(&db, "hash_b");
994 1043 insert_fake_sample(&db, "hash_c");
995 - create_sample_link(&db, vfs_id, None, "a.wav", "hash_a").unwrap();
996 - create_sample_link(&db, vfs_id, None, "b.wav", "hash_b").unwrap();
997 - create_sample_link(&db, vfs_id, None, "c.wav", "hash_c").unwrap();
1044 + create_sample_link(
1045 + &db,
1046 + vfs_id,
1047 + None,
1048 + "a.wav",
1049 + &crate::SampleHash::from_trusted("hash_a"),
1050 + )
1051 + .unwrap();
1052 + create_sample_link(
1053 + &db,
1054 + vfs_id,
1055 + None,
1056 + "b.wav",
1057 + &crate::SampleHash::from_trusted("hash_b"),
1058 + )
1059 + .unwrap();
1060 + create_sample_link(
1061 + &db,
1062 + vfs_id,
1063 + None,
1064 + "c.wav",
1065 + &crate::SampleHash::from_trusted("hash_c"),
1066 + )
1067 + .unwrap();
998 1068
999 1069 let results = find_nodes_by_hashes(&db, vfs_id, &["hash_b", "hash_a"]).unwrap();
1000 1070
@@ -1027,7 +1097,14 @@
1027 1097 let db = setup();
1028 1098 let vfs_id = create_vfs(&db, "Lib").unwrap();
1029 1099 insert_fake_sample(&db, "hash_cloud");
1030 - create_sample_link(&db, vfs_id, None, "cloud.wav", "hash_cloud").unwrap();
1100 + create_sample_link(
1101 + &db,
1102 + vfs_id,
1103 + None,
1104 + "cloud.wav",
1105 + &crate::SampleHash::from_trusted("hash_cloud"),
1106 + )
1107 + .unwrap();
1031 1108
1032 1109 db.conn()
1033 1110 .execute(
@@ -1162,7 +1239,13 @@
1162 1239 let db = setup();
1163 1240 insert_fake_sample(&db, "hash1");
1164 1241 let vfs_id = create_vfs(&db, "Lib").unwrap();
1165 - let result = create_sample_link(&db, vfs_id, None, "foo/bar.wav", "hash1");
1242 + let result = create_sample_link(
1243 + &db,
1244 + vfs_id,
1245 + None,
1246 + "foo/bar.wav",
1247 + &crate::SampleHash::from_trusted("hash1"),
1248 + );
1166 1249 assert!(matches!(result, Err(CoreError::InvalidNodeName(_))));
1167 1250 }
1168 1251
@@ -1192,7 +1275,14 @@
1192 1275 let vfs_id = create_vfs(&db, "Library").unwrap();
1193 1276 let drums = create_directory(&db, vfs_id, None, "Drums").unwrap();
1194 1277 let kicks = create_directory(&db, vfs_id, Some(drums), "Kicks").unwrap();
1195 - create_sample_link(&db, vfs_id, Some(kicks), "808.wav", "s1").unwrap();
1278 + create_sample_link(
1279 + &db,
1280 + vfs_id,
1281 + Some(kicks),
1282 + "808.wav",
1283 + &crate::SampleHash::from_trusted("s1"),
1284 + )
1285 + .unwrap();
1196 1286
1197 1287 let tree = list_full_tree(&db).unwrap();
1198 1288 let paths: Vec<&str> = tree.iter().map(|n| n.path.as_str()).collect();
@@ -1216,7 +1306,14 @@
1216 1306 let v1 = create_vfs(&db, "Alpha").unwrap();
1217 1307 let v2 = create_vfs(&db, "Beta").unwrap();
1218 1308 create_directory(&db, v1, None, "Dir1").unwrap();
1219 - create_sample_link(&db, v2, None, "sample.wav", "s1").unwrap();
1309 + create_sample_link(
1310 + &db,
1311 + v2,
1312 + None,
1313 + "sample.wav",
1314 + &crate::SampleHash::from_trusted("s1"),
1315 + )
1316 + .unwrap();
1220 1317
1221 1318 let tree = list_full_tree(&db).unwrap();
1222 1319 let paths: Vec<&str> = tree.iter().map(|n| n.path.as_str()).collect();
@@ -11,6 +11,7 @@
11 11
12 12 use tracing::{debug, instrument, warn};
13 13
14 + use crate::SampleHash;
14 15 use crate::db::Database;
15 16 use crate::error::{Result, io_err};
16 17 use crate::store::{sample_extension, sample_location, sample_source_path};
@@ -246,7 +247,7 @@
246 247 let mut seen = HashSet::new();
247 248 for &hash in hashes {
248 249 if seen.insert(hash)
249 - && let Ok(ext) = sample_extension(db, hash)
250 + && let Ok(ext) = sample_extension(db, &SampleHash::from_trusted(hash))
250 251 {
251 252 let source = sample_source_path(db, hash).ok().flatten();
252 253 result.push((hash.to_string(), ext, source));
@@ -378,7 +379,14 @@
378 379 create_store_file(&store_dir, "abc123", "wav");
379 380
380 381 let vfs_id = create_vfs(&db, "Library").unwrap();
381 - create_sample_link(&db, vfs_id, None, "kick.wav", "abc123").unwrap();
382 + create_sample_link(
383 + &db,
384 + vfs_id,
385 + None,
386 + "kick.wav",
387 + &crate::SampleHash::from_trusted("abc123"),
388 + )
389 + .unwrap();
382 390
383 391 let config = make_config(&mirror_dir, &store_dir);
384 392 let stats = sync_mirror(&db, &config).unwrap();
@@ -414,7 +422,14 @@
414 422 // Deliberately do NOT create a store blob, there isn't one for loose files.
415 423
416 424 let vfs_id = create_vfs(&db, "Library").unwrap();
417 - create_sample_link(&db, vfs_id, None, "kick.wav", &hash).unwrap();
425 + create_sample_link(
426 + &db,
427 + vfs_id,
428 + None,
429 + "kick.wav",
430 + &crate::SampleHash::from_trusted(hash.clone()),
431 + )
432 + .unwrap();
418 433
419 434 let config = make_config(&mirror_dir, &store_dir);
420 435 let stats = sync_mirror(&db, &config).unwrap();
@@ -441,7 +456,14 @@
441 456 create_store_file(&store_dir, "abc123", "wav");
442 457
443 458 let vfs_id = create_vfs(&db, "Library").unwrap();
444 - let node_id = create_sample_link(&db, vfs_id, None, "kick.wav", "abc123").unwrap();
459 + let node_id = create_sample_link(
460 + &db,
461 + vfs_id,
462 + None,
463 + "kick.wav",
464 + &crate::SampleHash::from_trusted("abc123"),
465 + )
466 + .unwrap();
445 467
446 468 let config = make_config(&mirror_dir, &store_dir);
447 469 sync_mirror(&db, &config).unwrap();
@@ -477,7 +499,14 @@
477 499 create_store_file(&store_dir, "abc123", "wav");
478 500
479 501 let vfs_id = create_vfs(&db, "Library").unwrap();
480 - create_sample_link(&db, vfs_id, None, "kick.wav", "abc123").unwrap();
502 + create_sample_link(
503 + &db,
504 + vfs_id,
505 + None,
506 + "kick.wav",
507 + &crate::SampleHash::from_trusted("abc123"),
508 + )
509 + .unwrap();
481 510
482 511 let config = make_config(&mirror_dir, &store_dir);
483 512 sync_mirror(&db, &config).unwrap();
@@ -89,7 +89,14 @@
89 89 );
90 90
91 91 // Verify the file exists in the store.
92 - assert!(env.store.exists(&hash, "wav").unwrap());
92 + assert!(
93 + env.store
94 + .exists(
95 + &audiofiles_core::SampleHash::from_trusted(hash.clone()),
96 + "wav"
97 + )
98 + .unwrap()
99 + );
93 100
94 101 // Verify the sample row exists in the database.
95 102 let row_count: i64 = env
@@ -110,9 +117,14 @@
110 117 // ── Step 2: Create VFS and link sample ──────────────────────
111 118 let vfs_id = vfs::create_vfs(&env.db, "TestLibrary").unwrap();
112 119 let drums_dir = vfs::create_directory(&env.db, vfs_id, None, "Drums").unwrap();
113 - let node_id =
114 - vfs::create_sample_link(&env.db, vfs_id, Some(drums_dir), "test_sine_440.wav", &hash)
115 - .unwrap();
120 + let node_id = vfs::create_sample_link(
121 + &env.db,
122 + vfs_id,
123 + Some(drums_dir),
124 + "test_sine_440.wav",
125 + &audiofiles_core::SampleHash::from_trusted(hash.clone()),
126 + )
127 + .unwrap();
116 128
117 129 // Verify the VFS node was created correctly.
118 130 let node = vfs::get_node(&env.db, node_id).unwrap();
@@ -125,7 +137,13 @@
125 137 assert_eq!(children[0].name, "test_sine_440.wav");
126 138
127 139 // ── Step 3: Run audio analysis ──────────────────────────────
128 - let store_path = env.store.sample_path(&hash, "wav").unwrap();
140 + let store_path = env
141 + .store
142 + .sample_path(
143 + &audiofiles_core::SampleHash::from_trusted(hash.clone()),
144 + "wav",
145 + )
146 + .unwrap();
129 147 let config = AnalysisConfig::default();
130 148 let result = analysis::analyze_sample(&hash, &store_path, &config).unwrap();
131 149
@@ -328,7 +346,15 @@
328 346 );
329 347
330 348 // Verify the exported file content matches the store copy.
331 - let store_bytes = fs::read(env.store.sample_path(&hash, "wav").unwrap()).unwrap();
349 + let store_bytes = fs::read(
350 + env.store
351 + .sample_path(
352 + &audiofiles_core::SampleHash::from_trusted(hash.clone()),
353 + "wav",
354 + )
355 + .unwrap(),
356 + )
357 + .unwrap();
332 358 let export_bytes = fs::read(&exported_file).unwrap();
333 359 assert_eq!(
334 360 store_bytes, export_bytes,
@@ -397,7 +423,13 @@
397 423 let env = TestEnv::new();
398 424
399 425 let hash = env.store.import(&env.wav_path, &env.db).unwrap();
400 - let store_path = env.store.sample_path(&hash, "wav").unwrap();
426 + let store_path = env
427 + .store
428 + .sample_path(
429 + &audiofiles_core::SampleHash::from_trusted(hash.clone()),
430 + "wav",
431 + )
432 + .unwrap();
401 433
402 434 let config = AnalysisConfig::default();
403 435 let result = analysis::analyze_sample(&hash, &store_path, &config).unwrap();
@@ -450,18 +482,44 @@
450 482
451 483 // Analyze both.
452 484 let config = AnalysisConfig::default();
453 - let store_path1 = env.store.sample_path(&hash1, "wav").unwrap();
485 + let store_path1 = env
486 + .store
487 + .sample_path(
488 + &audiofiles_core::SampleHash::from_trusted(hash1.clone()),
489 + "wav",
490 + )
491 + .unwrap();
454 492 let result1 = analysis::analyze_sample(&hash1, &store_path1, &config).unwrap();
455 493 analysis::save_analysis_batch(&env.db, std::slice::from_ref(&result1)).unwrap();
456 494
457 - let store_path2 = env.store.sample_path(&hash2, "wav").unwrap();
495 + let store_path2 = env
496 + .store
497 + .sample_path(
498 + &audiofiles_core::SampleHash::from_trusted(hash2.clone()),
499 + "wav",
500 + )
501 + .unwrap();
458 502 let result2 = analysis::analyze_sample(&hash2, &store_path2, &config).unwrap();
459 503 analysis::save_analysis_batch(&env.db, std::slice::from_ref(&result2)).unwrap();
460 504
461 505 // Create VFS with both samples.
462 506 let vfs_id = vfs::create_vfs(&env.db, "MultiTest").unwrap();
463 - vfs::create_sample_link(&env.db, vfs_id, None, "test_sine_440.wav", &hash1).unwrap();
464 - vfs::create_sample_link(&env.db, vfs_id, None, "bass_note_80hz.wav", &hash2).unwrap();
507 + vfs::create_sample_link(
508 + &env.db,
509 + vfs_id,
510 + None,
511 + "test_sine_440.wav",
512 + &audiofiles_core::SampleHash::from_trusted(hash1.clone()),
513 + )
514 + .unwrap();
515 + vfs::create_sample_link(
516 + &env.db,
517 + vfs_id,
518 + None,
519 + "bass_note_80hz.wav",
520 + &audiofiles_core::SampleHash::from_trusted(hash2.clone()),
521 + )
522 + .unwrap();
465 523
466 524 // Tag differently.
467 525 tags::add_tag(&env.db, &hash1, "instrument.synth").unwrap();
@@ -260,7 +260,14 @@
260 260
261 261 // Create directory structure in VFS
262 262 let drums_id = vfs::create_directory(db, vfs_id, None, "Drums").unwrap();
263 - vfs::create_sample_link(db, vfs_id, Some(drums_id), "kick.wav", &hash).unwrap();
263 + vfs::create_sample_link(
264 + db,
265 + vfs_id,
266 + Some(drums_id),
267 + "kick.wav",
268 + &crate::SampleHash::from_trusted(hash.clone()),
269 + )
270 + .unwrap();
264 271
265 272 vfs_id
266 273 }
@@ -716,7 +723,14 @@
716 723 let wav_path = dir.path().join("KICK.wav");
717 724 write_test_wav(&wav_path, 1, 44100, &[0.1, -0.1]);
718 725 let hash2 = store.import(&wav_path, &db).unwrap();
719 - vfs::create_sample_link(&db, vfs_id, None, "KICK.wav", &hash2).unwrap();
726 + vfs::create_sample_link(
727 + &db,
728 + vfs_id,
729 + None,
730 + "KICK.wav",
731 + &crate::SampleHash::from_trusted(hash2.clone()),
732 + )
733 + .unwrap();
720 734
721 735 let items = collect_export_items(&db, vfs_id, None).unwrap();
722 736 assert_eq!(items.len(), 2);
@@ -1024,7 +1038,14 @@
1024 1038 let wav2 = dir.path().join("snare.wav");
1025 1039 write_test_wav(&wav2, 1, 44100, &[0.1, -0.1]);
1026 1040 let hash2 = store.import(&wav2, &db).unwrap();
1027 - vfs::create_sample_link(&db, vfs_id, None, "snare.wav", &hash2).unwrap();
1041 + vfs::create_sample_link(
1042 + &db,
1043 + vfs_id,
1044 + None,
1045 + "snare.wav",
1046 + &crate::SampleHash::from_trusted(hash2.clone()),
1047 + )
1048 + .unwrap();
1028 1049
1029 1050 let items = collect_export_items(&db, vfs_id, None).unwrap();
1030 1051 assert!(items.len() >= 2);
@@ -1080,7 +1101,14 @@
1080 1101 write_test_wav(&src, 1, 44100, &samples);
1081 1102 let hash = store.import(&src, &db).unwrap();
1082 1103 let vfs_id = vfs::create_vfs(&db, "v").unwrap();
1083 - vfs::create_sample_link(&db, vfs_id, None, "long.wav", &hash).unwrap();
1104 + vfs::create_sample_link(
1105 + &db,
1106 + vfs_id,
1107 + None,
1108 + "long.wav",
1109 + &crate::SampleHash::from_trusted(hash.clone()),
1110 + )
1111 + .unwrap();
1084 1112 let items = collect_export_items(&db, vfs_id, None).unwrap();
1085 1113
1086 1114 let dest_dir = dir.path().join("export_stream_cancel");
@@ -1133,7 +1161,14 @@
1133 1161 write_test_wav(&src, 1, 44100, &samples);
1134 1162 let hash = store.import(&src, &db).unwrap();
1135 1163 let vfs_id = vfs::create_vfs(&db, "v").unwrap();
1136 - vfs::create_sample_link(&db, vfs_id, None, "long.wav", &hash).unwrap();
1164 + vfs::create_sample_link(
1165 + &db,
1166 + vfs_id,
1167 + None,
1168 + "long.wav",
1169 + &crate::SampleHash::from_trusted(hash.clone()),
1170 + )
1171 + .unwrap();
1137 1172 let items = collect_export_items(&db, vfs_id, None).unwrap();
1138 1173
1139 1174 let dest_dir = dir.path().join("export_stream_ok");
@@ -203,9 +203,9 @@
203 203 // Stage 1: copy every slice blob into the content store. Content-addressed
204 204 // and idempotent, so a failure here touches no VFS state, there is nothing
205 205 // to roll back, and a retry reuses any blobs already written.
206 - let mut imported: Vec<(&str, String)> = Vec::with_capacity(slices.len());
206 + let mut imported: Vec<(&str, crate::SampleHash)> = Vec::with_capacity(slices.len());
207 207 for (slice_name, temp_path) in &slices {
208 - let hash = store.import(temp_path, db)?;
208 + let hash = crate::SampleHash::from_trusted(store.import(temp_path, db)?);
209 209 imported.push((slice_name.as_str(), hash));
210 210 }
211 211
@@ -329,10 +329,13 @@
329 329 let run_dir = temp_path.parent().map(Path::to_path_buf);
330 330 let import = store.import(&temp_path, db);
331 331 let _ = std::fs::remove_file(&temp_path);
332 - let hash = import?;
332 + let hash = crate::SampleHash::from_trusted(import?);
333 333 vfs::create_sample_link(db, vfs_id, parent_id, &out_name, &hash)?;
334 334 cleanup_run_dir(run_dir.as_deref());
335 - Ok(ConformResult { hash, overshoot })
335 + Ok(ConformResult {
336 + hash: hash.into_inner(),
337 + overshoot,
338 + })
336 339 }
337 340
338 341 /// Decode `source_path`, conform it to `target`, and write the result as a new
@@ -574,8 +577,12 @@
574 577 assert_eq!(children[0].name, "pad_22k_16bit.wav");
575 578
576 579 // The stored file decodes back at the conformed spec.
577 - let ext = crate::store::sample_extension(&db, &hash).unwrap();
578 - let path = store.sample_path(&hash, &ext).unwrap();
580 + let ext =
581 + crate::store::sample_extension(&db, &crate::SampleHash::from_trusted(hash.clone()))
582 + .unwrap();
583 + let path = store
584 + .sample_path(&crate::SampleHash::from_trusted(hash.clone()), &ext)
585 + .unwrap();
579 586 let decoded = decode_multichannel(&path).unwrap();
580 587 assert_eq!(decoded.sample_rate, 22050);
581 588 assert_eq!(decoded.channels, 1);