use std::path::Path;
use futures_util::StreamExt;
use serde::Serialize;
use sha2::{Digest, Sha256};
use tokio::fs;
use super::LocalStore;
use crate::error::Error;
use crate::namespace::Namespace;
#[derive(Debug, Default, Serialize, PartialEq, Eq)]
pub struct VerifyReport {
pub checked: u64,
pub bytes: u64,
pub corrupt: Vec<String>,
pub unreadable: Vec<String>,
pub incomplete: bool,
}
impl LocalStore {
pub async fn verify(&self, ns: &Namespace) -> Result<VerifyReport, Error> {
let mut report = VerifyReport::default();
let Ok(mut prefixes) = fs::read_dir(self.root.join(ns.org()).join(ns.repo())).await else {
return Ok(report);
};
while let Some(prefix) = prefixes.next_entry().await? {
let mut fanouts = match fs::read_dir(prefix.path()).await {
Ok(fanouts) => fanouts,
Err(error) => {
tracing::warn!(path = ?prefix.path(), %error, "could not be listed");
report.incomplete = true;
continue;
}
};
loop {
match fanouts.next_entry().await {
Ok(Some(fanout)) => {
self.verify_directory(&fanout.path(), ns, &mut report).await;
}
Ok(None) => break,
Err(error) => {
tracing::warn!(path = ?prefix.path(), %error, "listing stopped early");
report.incomplete = true;
break;
}
}
}
}
Ok(report)
}
async fn verify_directory(&self, directory: &Path, ns: &Namespace, report: &mut VerifyReport) {
let mut entries = match fs::read_dir(directory).await {
Ok(entries) => entries,
Err(error) => {
tracing::warn!(?directory, %error, "could not be listed");
report.incomplete = true;
return;
}
};
loop {
let entry = match entries.next_entry().await {
Ok(Some(entry)) => entry,
Ok(None) => return,
Err(error) => {
tracing::warn!(?directory, %error, "listing stopped early");
report.incomplete = true;
return;
}
};
let oid = entry.file_name().to_string_lossy().into_owned();
if Self::validate_oid(&oid).is_err() {
continue;
}
report.checked += 1;
match self.digest_of(ns, &oid).await {
Ok((digest, read)) => {
report.bytes += read;
if digest != oid {
report.corrupt.push(oid);
}
}
Err(error) => {
tracing::warn!(oid, %error, "object could not be read");
report.unreadable.push(oid);
}
}
}
}
async fn digest_of(&self, ns: &Namespace, oid: &str) -> Result<(String, u64), Error> {
let object = self.open(ns, oid).await?;
let size = object.size();
let mut hasher = Sha256::new();
let mut read = 0u64;
let mut chunks = object.stream(0, size).await?;
while let Some(chunk) = chunks.next().await {
let chunk = chunk?;
read += chunk.len() as u64;
hasher.update(&chunk);
}
Ok((hex::encode(hasher.finalize()), read))
}
}
#[cfg(test)]
mod tests;