use std::env;
use std::fmt::Write as _;
use std::fs;
use std::io::Read as _;
use std::path::{Path, PathBuf};
use sha2::{Digest as _, Sha256};
use crate::error::{Error, Result};
const MAX_BODY: u64 = 256 * 1024 * 1024;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Pin {
pub(crate) repo: &'static str,
pub(crate) revision: &'static str,
pub(crate) file: &'static str,
pub(crate) sha256: &'static str,
pub(crate) env: &'static str,
pub(crate) cache_dir: &'static str,
}
impl Pin {
fn url(&self) -> String {
format!(
"https://huggingface.co/{}/resolve/{}/{}",
self.repo, self.revision, self.file
)
}
fn cache_path(&self) -> PathBuf {
cache_base()
.join("cactus-rs")
.join(self.cache_dir)
.join(self.revision.get(..12).unwrap_or(self.revision))
.join(self.file)
}
}
pub(crate) fn fetch(pin: &Pin) -> Result<Vec<u8>> {
if let Some(path) = env::var_os(pin.env) {
return Ok(fs::read(path)?);
}
let cached = pin.cache_path();
if let Ok(bytes) = fs::read(&cached) {
if digest(&bytes) == pin.sha256 {
return Ok(bytes);
}
}
let bytes = download(&pin.url())?;
let actual = digest(&bytes);
if actual != pin.sha256 {
return Err(Error::ChecksumMismatch {
expected: pin.sha256.to_owned(),
actual,
});
}
let _ = install(&cached, &bytes);
Ok(bytes)
}
fn cache_base() -> PathBuf {
if cfg!(windows) {
env::var_os("LOCALAPPDATA").map(PathBuf::from)
} else {
env::var_os("XDG_CACHE_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".cache")))
}
.unwrap_or_else(env::temp_dir)
}
fn install(path: &Path, bytes: &[u8]) -> Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |elapsed| elapsed.as_nanos());
let name = path
.file_name()
.map_or_else(|| "archive".into(), |name| name.to_string_lossy());
let partial = path.with_file_name(format!("{name}.{}.{nanos}.partial", std::process::id()));
let written = fs::write(&partial, bytes).and_then(|()| fs::rename(&partial, path));
if written.is_err() {
let _ = fs::remove_file(&partial);
}
Ok(written?)
}
fn download(url: &str) -> Result<Vec<u8>> {
let config = ureq::Agent::config_builder()
.user_agent(concat!("cactus-rs/", env!("CARGO_PKG_VERSION")))
.build();
let agent = ureq::Agent::new_with_config(config);
let mut response = agent.get(url).call().map_err(|error| Error::Download {
url: url.to_owned(),
detail: error.to_string(),
})?;
let mut bytes = Vec::new();
response
.body_mut()
.with_config()
.limit(MAX_BODY)
.reader()
.read_to_end(&mut bytes)
.map_err(|error| Error::Download {
url: url.to_owned(),
detail: error.to_string(),
})?;
Ok(bytes)
}
fn digest(bytes: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(bytes);
let mut hex = String::with_capacity(64);
for byte in hasher.finalize() {
let _ = write!(hex, "{byte:02x}");
}
hex
}
#[cfg(test)]
mod tests {
use super::*;
const NEEDLE: Pin = crate::needle::weights::PIN;
const WHISTLE: Pin = crate::whistle::weights::PIN;
#[test]
fn digest_matches_a_known_vector() {
assert_eq!(
digest(b"abc"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn cache_path_carries_the_pin() {
let needle = NEEDLE.cache_path();
let text = needle.display().to_string();
assert!(text.contains("cactus-rs"));
assert!(text.contains("needle3"));
assert!(text.contains(&cactus_sys::NEEDLE_ENGINE_COMMIT[..12]));
assert!(text.ends_with("needle3.cact"));
let whistle = WHISTLE.cache_path();
let expected = Path::new("cactus-rs")
.join("whistle")
.join("b358ddadd89b")
.join("whistle.cact");
assert!(whistle.ends_with(&expected), "{}", whistle.display());
assert_eq!(
needle.ancestors().nth(3),
whistle.ancestors().nth(3),
"different cache roots"
);
}
#[test]
fn urls_point_at_the_pinned_revision() {
assert_eq!(
NEEDLE.url(),
format!(
"https://huggingface.co/Cactus-Compute/needle3/resolve/{}/needle3.cact",
cactus_sys::NEEDLE_ENGINE_COMMIT
)
);
assert_eq!(
WHISTLE.url(),
"https://huggingface.co/Cactus-Compute/whistle/resolve/\
b358ddadd89b7a713b5aa131f23032d3cca1b251/whistle.cact"
);
}
#[test]
fn a_partial_install_keeps_the_archive_name() {
let dir = env::temp_dir().join(format!("cactus-rs-install-{}", std::process::id()));
let path = dir.join("whistle.cact");
install(&path, b"bytes").expect("the temporary directory is writable");
assert_eq!(fs::read(&path).expect("installed"), b"bytes");
let left: Vec<_> = fs::read_dir(&dir)
.expect("the directory exists")
.map(|entry| entry.expect("an entry").file_name())
.collect();
assert_eq!(left, ["whistle.cact"]);
let _ = fs::remove_dir_all(&dir);
}
}