1use base64::URL_SAFE_NO_PAD;
2use std::{
3 io::{self, Read, Seek, SeekFrom, Write},
4 path::{Path, PathBuf},
5};
6use tempfile::NamedTempFile;
7
8pub type Hash = [u8; 32];
9
10pub fn join_path(root: &Path, hash: &Hash, ext: &str) -> PathBuf {
11 let mut out = [b'A'; 64];
12 let len = base64::encode_config_slice(hash, URL_SAFE_NO_PAD, &mut out);
13 let out = unsafe { core::str::from_utf8_unchecked_mut(&mut out) };
14 let mut path = root.join(&out[..len]);
15 if !ext.is_empty() {
16 path.set_extension(ext);
17 }
18 path
19}
20
21pub fn reader(r: &mut impl Read) -> Hash {
22 let mut hasher = blake3::Hasher::new();
23 let mut buf = [0; 4096];
24 loop {
25 let len = r.read(&mut buf).unwrap();
26
27 if len == 0 {
28 return *hasher.finalize().as_bytes();
29 }
30
31 hasher.update(&buf[..len]);
32 }
33}
34
35pub fn create<W: FnOnce(&mut io::BufWriter<NamedTempFile>) -> io::Result<()>>(
36 root: &Path,
37 ext: &str,
38 write: W,
39) -> io::Result<PathBuf> {
40 let tmp = tempfile::NamedTempFile::new()?;
41
42 let mut buf = io::BufWriter::new(tmp);
43 write(&mut buf)?;
44 buf.flush()?;
45
46 let mut tmp = buf.into_inner()?;
47
48 tmp.seek(SeekFrom::Start(0)).unwrap();
49
50 let hash = reader(&mut tmp);
51 let path = join_path(root, &hash, ext);
52 tmp.persist(&path)?;
53 Ok(path)
54}