Skip to main content

cherenkov/model/index/
local.rs

1use anyhow::{Context, Result, ensure};
2use sha2::{Digest, Sha256};
3use std::{io::Read, path::Path};
4
5/// Metadata identity keeps registration cheap. Packing checks it again before
6/// publication. Files must not be modified while their tensor mappings are live.
7pub(super) fn fingerprint(path: &Path) -> Result<String> {
8    let files = if path.is_file() {
9        vec![path.to_owned()]
10    } else {
11        let mut files = std::fs::read_dir(path)?
12            .map(|entry| entry.map(|e| e.path()))
13            .collect::<std::io::Result<Vec<_>>>()?;
14
15        files.retain(|p| {
16            p.is_file()
17                && p.extension().is_some_and(|e| {
18                    matches!(
19                        e.to_str(),
20                        Some("json" | "jinja" | "safetensors" | "gguf" | "bin")
21                    )
22                })
23        });
24        files.sort();
25
26        files
27    };
28    let mut hash = Sha256::new();
29
30    for file in files {
31        let meta = std::fs::metadata(&file)?;
32
33        hash.update(
34            file.file_name()
35                .context("missing filename")?
36                .as_encoded_bytes(),
37        );
38        hash.update(meta.len().to_le_bytes());
39        hash.update(
40            meta.modified()?
41                .duration_since(std::time::UNIX_EPOCH)?
42                .as_nanos()
43                .to_le_bytes(),
44        );
45
46        if file
47            .extension()
48            .is_some_and(|e| e == "json" || e == "jinja")
49        {
50            ensure!(
51                meta.len() <= 64 * 1024 * 1024,
52                "metadata file exceeds 64 MiB"
53            );
54
55            let mut reader = std::fs::File::open(file)?;
56            let mut buf = [0_u8; 65536];
57
58            loop {
59                let len = reader.read(&mut buf)?;
60
61                if len == 0 {
62                    break;
63                }
64
65                hash.update(&buf[..len]);
66            }
67        }
68    }
69
70    Ok(hash
71        .finalize()
72        .iter()
73        .map(|byte| format!("{byte:02x}"))
74        .collect())
75}
76
77pub(super) fn copy_store(source: &Path, target: &Path, link: bool) -> Result<()> {
78    // Published managed inputs are pinned by a lease. Hard links reuse their
79    // immutable base files; newly selected variants are written under target.
80    for entry in std::fs::read_dir(source)? {
81        let entry = entry?;
82
83        if !entry.path().is_file() {
84            continue;
85        }
86
87        let dest = target.join(entry.file_name());
88
89        if link && entry.path().extension().is_some_and(|e| e == "bin") {
90            std::fs::hard_link(entry.path(), &dest)
91                .or_else(|_| std::fs::copy(entry.path(), &dest).map(|_| ()))?;
92        } else {
93            std::fs::copy(entry.path(), dest)?;
94        }
95    }
96
97    Ok(())
98}