euphony_buffer/
hash.rs

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}