use crate::{
dag::{ResolveError, UnexpectedResolved},
Block, Error, Ipfs, IpfsTypes,
};
use async_stream::stream;
use futures::stream::Stream;
use rust_unixfs::file::{visit::IdleFileVisit, FileReadFailed};
use libipld::Cid;
use std::borrow::Borrow;
use std::ops::Range;
pub async fn cat<'a, Types, MaybeOwned>(
ipfs: MaybeOwned,
starting_point: impl Into<StartingPoint>,
range: Option<Range<u64>>,
) -> Result<impl Stream<Item = Result<Vec<u8>, TraversalFailed>> + Send + 'a, TraversalFailed>
where
Types: IpfsTypes,
MaybeOwned: Borrow<Ipfs<Types>> + Send + 'a,
{
let mut visit = IdleFileVisit::default();
if let Some(range) = range {
visit = visit.with_target_range(range);
}
let block = match starting_point.into() {
StartingPoint::Left(path) => {
let borrow = ipfs.borrow();
let dag = borrow.dag();
let (resolved, _) = dag
.resolve(path, true)
.await
.map_err(TraversalFailed::Resolving)?;
resolved
.into_unixfs_block()
.map_err(TraversalFailed::Path)?
}
StartingPoint::Right(block) => block,
};
let mut cache = None;
let (visit, bytes) = match visit.start(block.data()) {
Ok((bytes, _, _, visit)) => {
let bytes = if !bytes.is_empty() {
Some(bytes.to_vec())
} else {
None
};
(visit, bytes)
}
Err(e) => {
return Err(TraversalFailed::Walking(*block.cid(), e));
}
};
Ok(stream! {
if let Some(bytes) = bytes {
yield Ok(bytes);
}
let mut visit = match visit {
Some(visit) => visit,
None => return,
};
loop {
let (next, _) = visit.pending_links();
let borrow = ipfs.borrow();
let block = match borrow.get_block(next).await {
Ok(block) => block,
Err(e) => {
yield Err(TraversalFailed::Loading(next.to_owned(), e));
return;
},
};
match visit.continue_walk(block.data(), &mut cache) {
Ok((bytes, next_visit)) => {
if !bytes.is_empty() {
yield Ok(bytes.to_vec());
}
match next_visit {
Some(v) => visit = v,
None => return,
}
}
Err(e) => {
yield Err(TraversalFailed::Walking(*block.cid(), e));
return;
}
}
}
})
}
pub enum StartingPoint {
Left(crate::IpfsPath),
Right(Block),
}
impl<T: Into<crate::IpfsPath>> From<T> for StartingPoint {
fn from(a: T) -> Self {
Self::Left(a.into())
}
}
impl From<Block> for StartingPoint {
fn from(b: Block) -> Self {
Self::Right(b)
}
}
#[derive(Debug, thiserror::Error)]
pub enum TraversalFailed {
#[error("path resolving failed")]
Resolving(#[source] ResolveError),
#[error("path resolved to unexpected")]
Path(#[source] UnexpectedResolved),
#[error("loading of {} failed", .0)]
Loading(Cid, #[source] Error),
#[error("walk failed on {}", .0)]
Walking(Cid, #[source] FileReadFailed),
}