candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
//! Whether a snapshot file already is the one the listing names, and removing a pointer that is not.

use std::fs;
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;

use sha1::Digest;
use sha1::Sha1;

use super::listing::ListedFile;

/// Whether `pointer` holds `file`: its listed size, and for a file git stores itself, its git blob id too.
pub(super) fn intact(pointer: &Path, file: &ListedFile) -> io::Result<bool> {
  let metadata = match fs::metadata(pointer) {
    Ok(metadata) => metadata,
    Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),
    Err(error) => return Err(error),
  };
  if !metadata.is_file() || metadata.len() != file.size {
    return Ok(false);
  }
  Ok(!file.in_git || git_blob_id(pointer, file.size)?.eq_ignore_ascii_case(&file.oid))
}

/// Removes the pointer and never what it links to: a blob can serve other snapshots, a link can lead to a user's file.
pub(super) fn remove(pointer: &Path) -> io::Result<()> {
  match fs::remove_file(pointer) {
    Err(error) if error.kind() != io::ErrorKind::NotFound => Err(error),
    _ => Ok(()),
  }
}

/// git's id of a blob: SHA-1 over `blob <size>\0` and then the content.
pub(super) fn git_blob_id(path: &Path, size: u64) -> io::Result<String> {
  let mut hasher = Sha1::new();
  hasher.update(format!("blob {size}\0"));
  let mut file = File::open(path)?;
  let mut buffer = vec![0; 1 << 16];
  loop {
    match file.read(&mut buffer)? {
      0 => break,
      read => hasher.update(&buffer[..read]),
    }
  }
  Ok(hex(&hasher.finalize()))
}

pub(super) fn hex(digest: &[u8]) -> String {
  digest.iter().map(|byte| format!("{byte:02x}")).collect()
}