use crate::Krate;
use anyhow::{Context as _, Error};
use bytes::Bytes;
use digest::Digest as DigestTrait;
use sha2::Sha256;
use std::{fs, io};
use std::{convert::Into, fmt, path::PathBuf, str, time};
const FINGERPRINT_SIZE: usize = 32;
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, Debug)]
struct Fingerprint([u8; FINGERPRINT_SIZE]);
impl Fingerprint {
fn digest(bytes: &[u8]) -> Self {
let mut hasher = Sha256::default();
hasher.update(bytes);
Self::from_sha256_bytes(&hasher.finalize())
}
fn from_sha256_bytes(sha256_bytes: &[u8]) -> Self {
if sha256_bytes.len() != FINGERPRINT_SIZE {
panic!(
"Input value was not a fingerprint; has length: {} (must be {})",
sha256_bytes.len(),
FINGERPRINT_SIZE,
);
}
let mut fingerprint = [0; FINGERPRINT_SIZE];
fingerprint.clone_from_slice(&sha256_bytes[0..FINGERPRINT_SIZE]);
Self(fingerprint)
}
fn from_hex_string(hex_string: &str) -> Result<Self, Error> {
<[u8; FINGERPRINT_SIZE] as hex::FromHex>::from_hex(hex_string)
.map(Self)
.map_err(|e| e.into())
}
fn to_hex(self) -> String {
let mut s = String::new();
for &byte in &self.0 {
fmt::Write::write_fmt(&mut s, format_args!("{:02x}", byte)).unwrap();
}
s
}
}
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
struct FilesystemDB {
root: PathBuf,
}
impl FilesystemDB {
fn new(root: PathBuf) -> Result<Self, Error> {
fs::create_dir_all(&root)?;
Ok(Self { root })
}
fn lookup_fingerprint(&self, key: Fingerprint) -> Result<Option<Bytes>, Error> {
let hex = key.to_hex();
let entry_path = self.root.join(hex);
match fs::read(entry_path) {
Ok(bytes) => Ok(Some(Bytes::from(bytes))),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
Err(e) => Err(e.into()),
}
}
fn lookup<K: Into<Fingerprint>, V: From<Bytes>>(&self, key: K) -> Result<Option<V>, Error> {
let key = key.into();
let result = self.lookup_fingerprint(key)?;
Ok(result.map(|bytes| bytes.into()))
}
fn insert_fingerprint_bytes(
&self,
key: Fingerprint,
value: Bytes,
) -> Result<Fingerprint, Error> {
let hex = key.to_hex();
let entry_path = self.root.join(hex);
fs::write(entry_path, &value)?;
Ok(key)
}
fn insert<K: Into<Fingerprint>, V: Into<Bytes>>(
&self,
key: K,
value: V,
) -> Result<Fingerprint, Error> {
let key = key.into();
self.insert_fingerprint_bytes(key, value.into())
}
fn list_keys(&self) -> Result<Vec<Fingerprint>, Error> {
let mut results = Vec::new();
for res in fs::read_dir(&self.root)? {
let entry = res?;
let file_name = entry.file_name();
results.push(Fingerprint::from_hex_string(&file_name.to_string_lossy())?);
}
Ok(results)
}
fn modified_time_fingerprint(
&self,
key: Fingerprint,
) -> Result<Option<time::SystemTime>, Error> {
let hex = key.to_hex();
let entry_path = self.root.join(hex);
let modified_time = match fs::metadata(entry_path) {
Ok(metadata) => metadata.modified()?,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
return Ok(None);
}
Err(e) => {
return Err(e.into());
}
};
Ok(Some(modified_time))
}
fn modified_time<K: Into<Fingerprint>>(
&self,
key: K,
) -> Result<Option<time::SystemTime>, Error> {
self.modified_time_fingerprint(key.into())
}
}
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
struct CasDB {
db: FilesystemDB,
}
impl CasDB {
fn new(db: FilesystemDB) -> Self {
Self { db }
}
fn lookup_cas_fingerprint(&self, key: Fingerprint) -> Result<Option<Bytes>, Error> {
self.db.lookup_fingerprint(key)
}
fn lookup_cas<K: Into<Fingerprint>, V: From<Bytes>>(&self, key: K) -> Result<Option<V>, Error> {
let key = key.into();
let result = self.lookup_cas_fingerprint(key)?;
Ok(result.map(|bytes| bytes.into()))
}
fn insert_cas_bytes(&self, value: Bytes) -> Result<Fingerprint, Error> {
let key = Fingerprint::digest(&value);
self.db.insert_fingerprint_bytes(key, value)
}
fn insert_cas<V: Into<Bytes>>(&self, value: V) -> Result<Fingerprint, Error> {
let bytes = value.into();
self.insert_cas_bytes(bytes)
}
fn list_cas_keys(&self) -> Result<Vec<Fingerprint>, Error> {
self.db.list_keys()
}
}
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
pub struct FSBackend {
krate_lookup: CasDB,
krate_data: FilesystemDB,
prefix: String,
}
impl FSBackend {
pub fn new(loc: crate::FilesystemLocation<'_>) -> Result<Self, Error> {
let crate::FilesystemLocation { path } = loc;
let krate_lookup = CasDB::new(FilesystemDB::new(path.join("krate_lookup"))?);
let krate_data = FilesystemDB::new(path.join("krate_data"))?;
Ok(Self {
krate_lookup,
krate_data,
prefix: "".to_string(),
})
}
}
impl From<Krate> for Fingerprint {
fn from(krate: Krate) -> Self {
let krate_json = serde_json::to_string(&krate)
.expect("did not expect an error serializing Krate object");
Self::digest(krate_json.as_bytes())
}
}
impl From<Krate> for Bytes {
fn from(krate: Krate) -> Self {
let krate_json =
serde_json::to_vec(&krate).expect("did not expect an error serializing Krate object");
Bytes::from(krate_json)
}
}
impl From<Bytes> for Krate {
fn from(bytes: Bytes) -> Self {
let json_string =
str::from_utf8(&bytes).expect("failed to convert bytes into json string for Krate");
let krate: Krate = serde_json::from_str(json_string)
.expect("failed to deserialize Krate from json string");
krate
}
}
impl crate::Backend for FSBackend {
fn fetch(&self, krate: &Krate) -> Result<Bytes, Error> {
self.krate_data
.lookup(krate.clone())?
.ok_or_else(|| anyhow::Error::msg(format!("krate {:?} not found!", krate)))
}
fn upload(&self, source: Bytes, krate: &Krate) -> Result<usize, Error> {
self.krate_lookup.insert_cas(krate.clone())?;
let len = source.len();
self.krate_data.insert(krate.clone(), source)?;
Ok(len)
}
fn list(&self) -> Result<Vec<String>, Error> {
let all_keys: Vec<Fingerprint> = self.krate_lookup.list_cas_keys()?;
let mut all_names: Vec<String> = vec![];
for key in all_keys {
let cur_krate: Krate = self
.krate_lookup
.lookup_cas(key)?
.expect("this key was provided by list_cas_keys()");
let stripped_name = cur_krate.name[self.prefix.len()..].to_owned();
all_names.push(stripped_name);
}
Ok(all_names)
}
fn updated(&self, krate: &Krate) -> Result<Option<crate::Timestamp>, Error> {
if let Some(timestamp) = self.krate_data.modified_time(krate.clone())? {
let unix_time = timestamp.duration_since(time::UNIX_EPOCH)?.as_secs();
crate::Timestamp::from_unix_timestamp(unix_time as i64)
.context("invalid timestamp range")
.map(Some)
} else {
Ok(None)
}
}
fn set_prefix(&mut self, prefix: &str) {
self.prefix = prefix.to_owned();
}
}