use super::{
CustomFlatUnixFs, DirBuilder, Entry, Leaf, NamedLeaf, TreeConstructionFailed, TreeOptions,
};
use crate::Metadata;
use core::fmt;
use ipld_core::cid::{Cid, Version};
use std::collections::{HashMap, VecDeque};
use super::sharded::{self, ShardBlock};
pub struct PostOrderIterator {
full_path: String,
old_depth: usize,
block_buffer: Vec<u8>,
pending: Vec<Visited>,
persisted_cids: HashMap<u64, Vec<Option<NamedLeaf>>>,
reused_children: Vec<Visited>,
cid: Option<Cid>,
total_size: u64,
pending_blocks: VecDeque<ShardBlock>,
deferred: Option<ShardBlock>,
opts: TreeOptions,
}
type Leaves = Vec<Option<NamedLeaf>>;
#[derive(Debug)]
enum Visited {
DescentRoot(DirBuilder),
Descent {
node: DirBuilder,
name: String,
depth: usize,
index: usize,
},
Post {
parent_id: u64,
depth: usize,
name: String,
index: usize,
leaves: LeafStorage,
metadata: Metadata,
},
PostRoot {
leaves: LeafStorage,
metadata: Metadata,
},
}
impl PostOrderIterator {
pub(super) fn new(root: DirBuilder, opts: TreeOptions, longest_path: usize) -> Self {
let root = Visited::DescentRoot(root);
PostOrderIterator {
full_path: String::with_capacity(longest_path),
old_depth: 0,
block_buffer: Default::default(),
pending: vec![root],
persisted_cids: Default::default(),
reused_children: Vec::new(),
cid: None,
total_size: 0,
pending_blocks: VecDeque::new(),
deferred: None,
opts,
}
}
fn render_directory(
links: &[Option<NamedLeaf>],
buffer: &mut Vec<u8>,
block_size_limit: &Option<u64>,
cid_version: Version,
hasher: multihash_codetable::Code,
shard_threshold: &Option<u64>,
metadata: &Metadata,
) -> Result<(Leaf, Vec<ShardBlock>), TreeConstructionFailed> {
use crate::pb::{UnixFs, UnixFsType};
use quick_protobuf::{MessageWrite, Writer};
if let Some(threshold) = shard_threshold {
let estimate = links
.iter()
.filter_map(|opt| opt.as_ref())
.map(|NamedLeaf(name, cid, _)| (name.len() + cid.to_bytes().len()) as u64)
.sum::<u64>();
if estimate > *threshold {
return sharded::build_sharded(links, buffer, cid_version, hasher, metadata);
}
}
let (mode, mtime) = metadata.to_pb();
let node = CustomFlatUnixFs {
links,
data: UnixFs {
Type: UnixFsType::Directory,
mode,
mtime,
..Default::default()
},
};
let size = node.get_size();
if let Some(limit) = block_size_limit {
let size = size as u64;
if *limit < size {
return Err(TreeConstructionFailed::TooLargeBlock(size));
}
}
buffer.clear();
buffer.reserve(size);
let mut writer = Writer::new(&mut *buffer);
node.write_message(&mut writer)
.map_err(TreeConstructionFailed::Protobuf)?;
let cid = crate::pb::make_cid(cid_version, hasher, crate::file::DAG_PB_CODEC, buffer);
let combined_from_links = links
.iter()
.map(|opt| {
opt.as_ref()
.map(|NamedLeaf(_, _, total_size)| total_size)
.unwrap()
})
.sum::<u64>();
Ok((
Leaf {
link: cid,
total_size: buffer.len() as u64 + combined_from_links,
},
Vec::new(),
))
}
pub fn next_borrowed(&mut self) -> Option<Result<TreeNode<'_>, TreeConstructionFailed>> {
if let Some(shard) = self.pending_blocks.pop_front() {
self.block_buffer.clear();
self.block_buffer.extend_from_slice(&shard.block);
self.cid = Some(shard.cid);
self.total_size = shard.total_size;
return Some(Ok(TreeNode {
path: "",
cid: self.cid.as_ref().expect("just set"),
total_size: self.total_size,
block: &self.block_buffer,
}));
}
if let Some(node) = self.deferred.take() {
self.block_buffer.clear();
self.block_buffer.extend_from_slice(&node.block);
self.cid = Some(node.cid);
self.total_size = node.total_size;
return Some(Ok(TreeNode {
path: self.full_path.as_str(),
cid: self.cid.as_ref().expect("just set"),
total_size: self.total_size,
block: &self.block_buffer,
}));
}
while let Some(visited) = self.pending.pop() {
let (name, depth) = match &visited {
Visited::DescentRoot(_) => (None, 0),
Visited::Descent { name, depth, .. } => (Some(name.as_ref()), *depth),
Visited::Post { name, depth, .. } => (Some(name.as_ref()), *depth),
Visited::PostRoot { .. } => (None, 0),
};
update_full_path((&mut self.full_path, &mut self.old_depth), name, depth);
match visited {
Visited::DescentRoot(node) => {
let children = &mut self.reused_children;
let metadata = node.metadata;
let leaves = partition_children_leaves(depth, node.nodes.into_iter(), children);
let any_children = !children.is_empty();
let leaves = if any_children {
self.persisted_cids.insert(node.id, leaves);
LeafStorage::from(node.id)
} else {
leaves.into()
};
self.pending.push(Visited::PostRoot { leaves, metadata });
self.pending.append(children);
}
Visited::Descent {
node,
name,
depth,
index,
} => {
let children = &mut self.reused_children;
let metadata = node.metadata;
let parent_id = node.parent_id.expect("only roots parent_id is None");
let leaves = partition_children_leaves(depth, node.nodes.into_iter(), children);
let any_children = !children.is_empty();
let leaves = if any_children {
self.persisted_cids.insert(node.id, leaves);
node.id.into()
} else {
leaves.into()
};
self.pending.push(Visited::Post {
parent_id,
name,
depth,
leaves,
index,
metadata,
});
self.pending.append(children);
}
Visited::Post {
parent_id,
name,
leaves,
index,
metadata,
..
} => {
let leaves = leaves.into_inner(&mut self.persisted_cids);
let buffer = &mut self.block_buffer;
let (leaf, interior) = match Self::render_directory(
&leaves,
buffer,
&self.opts.block_size_limit,
self.opts.cid_version,
self.opts.hasher,
&self.opts.shard_threshold,
&metadata,
) {
Ok(rendered) => rendered,
Err(e) => return Some(Err(e)),
};
self.cid = Some(leaf.link);
self.total_size = leaf.total_size;
let has_interior = !interior.is_empty();
self.pending_blocks.extend(interior);
{
let parent_leaves = self.persisted_cids.get_mut(&parent_id);
match (parent_id, parent_leaves, index) {
(pid, None, index) => {
panic!("leaves not found for parent_id = {pid} and index = {index}")
}
(_, Some(vec), index) => {
let cell = &mut vec[index];
assert!(cell.is_none());
*cell = Some(NamedLeaf(name, leaf.link, leaf.total_size));
}
}
}
if has_interior {
self.deferred = Some(ShardBlock {
cid: leaf.link,
block: core::mem::take(&mut self.block_buffer),
total_size: leaf.total_size,
});
return self.next_borrowed();
}
return Some(Ok(TreeNode {
path: self.full_path.as_str(),
cid: self.cid.as_ref().unwrap(),
total_size: self.total_size,
block: &self.block_buffer,
}));
}
Visited::PostRoot { leaves, metadata } => {
let leaves = leaves.into_inner(&mut self.persisted_cids);
if !self.opts.wrap_with_directory {
break;
}
let buffer = &mut self.block_buffer;
let (leaf, interior) = match Self::render_directory(
&leaves,
buffer,
&self.opts.block_size_limit,
self.opts.cid_version,
self.opts.hasher,
&self.opts.shard_threshold,
&metadata,
) {
Ok(rendered) => rendered,
Err(e) => return Some(Err(e)),
};
self.cid = Some(leaf.link);
self.total_size = leaf.total_size;
let has_interior = !interior.is_empty();
self.pending_blocks.extend(interior);
if has_interior {
self.deferred = Some(ShardBlock {
cid: leaf.link,
block: core::mem::take(&mut self.block_buffer),
total_size: leaf.total_size,
});
return self.next_borrowed();
}
return Some(Ok(TreeNode {
path: self.full_path.as_str(),
cid: self.cid.as_ref().unwrap(),
total_size: self.total_size,
block: &self.block_buffer,
}));
}
}
}
None
}
}
impl Iterator for PostOrderIterator {
type Item = Result<OwnedTreeNode, TreeConstructionFailed>;
fn next(&mut self) -> Option<Self::Item> {
self.next_borrowed()
.map(|res| res.map(TreeNode::into_owned))
}
}
pub struct TreeNode<'a> {
pub path: &'a str,
pub cid: &'a Cid,
pub total_size: u64,
pub block: &'a [u8],
}
impl fmt::Debug for TreeNode<'_> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("TreeNode")
.field("path", &format_args!("{:?}", self.path))
.field("cid", &format_args!("{}", self.cid))
.field("total_size", &self.total_size)
.field("size", &self.block.len())
.finish()
}
}
impl TreeNode<'_> {
pub fn into_owned(self) -> OwnedTreeNode {
OwnedTreeNode {
path: self.path.to_owned(),
cid: self.cid.to_owned(),
total_size: self.total_size,
block: self.block.into(),
}
}
}
pub struct OwnedTreeNode {
pub path: String,
pub cid: Cid,
pub total_size: u64,
pub block: Box<[u8]>,
}
fn update_full_path(
(full_path, old_depth): (&mut String, &mut usize),
name: Option<&str>,
depth: usize,
) {
if depth < 2 {
full_path.clear();
*old_depth = 0;
} else {
while *old_depth >= depth && *old_depth > 0 {
let slash_at = full_path.bytes().rposition(|ch| ch == b'/');
if let Some(slash_at) = slash_at {
if *old_depth == depth && Some(&full_path[(slash_at + 1)..]) == name {
return;
}
full_path.truncate(slash_at);
*old_depth -= 1;
} else {
todo!(
"no last slash_at in {:?} yet {} >= {}",
full_path,
old_depth,
depth
);
}
}
}
debug_assert!(*old_depth <= depth);
if let Some(name) = name {
if !full_path.is_empty() {
full_path.push('/');
}
full_path.push_str(name);
*old_depth += 1;
}
assert_eq!(*old_depth, depth);
}
fn partition_children_leaves(
depth: usize,
it: impl Iterator<Item = (String, Entry)>,
children: &mut Vec<Visited>,
) -> Leaves {
let mut leaves = Vec::new();
for (i, (k, v)) in it.enumerate() {
match v {
Entry::Directory(node) => {
children.push(Visited::Descent {
node,
name: k,
depth: depth + 1,
index: i,
});
leaves.push(None);
}
Entry::Leaf(leaf) => leaves.push(Some(NamedLeaf(k, leaf.link, leaf.total_size))),
}
}
leaves
}
#[derive(Debug)]
enum LeafStorage {
Direct(Leaves),
Stashed(u64),
}
impl LeafStorage {
fn into_inner(self, stash: &mut HashMap<u64, Leaves>) -> Leaves {
use LeafStorage::*;
match self {
Direct(leaves) => leaves,
Stashed(id) => stash
.remove(&id)
.ok_or(id)
.expect("leaves are either stashed or direct, must able to find with id"),
}
}
}
impl From<u64> for LeafStorage {
fn from(key: u64) -> LeafStorage {
LeafStorage::Stashed(key)
}
}
impl From<Leaves> for LeafStorage {
fn from(leaves: Leaves) -> LeafStorage {
LeafStorage::Direct(leaves)
}
}