use std::sync::Arc;
use async_trait::async_trait;
use bytes::Bytes;
use crate::BlakeHash;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FetchTier {
Cache = 0,
Lan = 1,
Swarm = 2,
Seed = 3,
Cdn = 4,
}
impl FetchTier {
pub fn label(self) -> &'static str {
match self {
FetchTier::Cache => "cache",
FetchTier::Lan => "lan",
FetchTier::Swarm => "swarm",
FetchTier::Seed => "seed",
FetchTier::Cdn => "cdn",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FetchProgress {
pub bytes_so_far: u64,
pub total: Option<u64>,
pub tier: FetchTier,
}
pub type ProgressSink = Arc<dyn Fn(FetchProgress) + Send + Sync>;
#[async_trait]
pub(crate) trait BlobSource: Send + Sync {
async fn fetch_raw(&self, hash: &BlakeHash) -> Option<Bytes>;
async fn fetch_raw_with_progress(
&self,
hash: &BlakeHash,
_sink: Option<&ProgressSink>,
) -> Option<Bytes> {
self.fetch_raw(hash).await
}
fn tier(&self) -> FetchTier;
}
pub(crate) struct FetchChain {
sources: Vec<Arc<dyn BlobSource>>,
}
impl FetchChain {
pub fn new(mut sources: Vec<Arc<dyn BlobSource>>) -> Self {
sources.sort_by_key(|s| s.tier());
FetchChain { sources }
}
pub async fn fetch_with_progress(
&self,
hash: &BlakeHash,
sink: Option<&ProgressSink>,
) -> Option<(FetchTier, Bytes)> {
for source in &self.sources {
let Some(bytes) = source.fetch_raw_with_progress(hash, sink).await else {
continue;
};
if hash.verify(&bytes) {
tracing::debug!(tier = source.tier().label(), hash = %hash, "fetch hit");
return Some((source.tier(), bytes));
}
tracing::warn!(
tier = source.tier().label(),
hash = %hash,
"BLAKE3 mismatch — dropping source"
);
}
None
}
}