1use std::path::Path;
4
5use rust_unixfs::dir::builder::{BufferingTreeBuilder, TreeOptions};
6use rust_unixfs::file::adder::FileAdder;
7
8use super::store::UnixFsStore;
9use super::types::{AddEntry, AddResult, EntryKind, FileAddResult};
10use crate::{Result, VoidError};
11
12pub fn add_directory(store: &dyn UnixFsStore, dir: &Path) -> Result<AddResult> {
17 let mut opts = TreeOptions::default();
18 opts.wrap_with_directory();
19 let mut tree_builder = BufferingTreeBuilder::new(opts);
20
21 let mut entries = Vec::new();
22 let mut blocks_total = 0usize;
23 let mut bytes_in = 0u64;
24 let mut bytes_out = 0u64;
25
26 add_dir_recursive(
27 store, dir, dir, &mut tree_builder,
28 &mut entries, &mut blocks_total, &mut bytes_in, &mut bytes_out,
29 )?;
30
31 let mut root_cid = None;
33
34 for node in tree_builder.build() {
35 let node = node.map_err(|e| VoidError::Network(format!("unixfs tree build: {e}")))?;
36 let block_size = store.put(node.cid, node.block.to_vec());
37 blocks_total += 1;
38 bytes_out += block_size as u64;
39
40 if !node.path.is_empty() {
41 entries.push(AddEntry {
42 path: node.path.clone(),
43 cid: node.cid,
44 kind: EntryKind::Directory,
45 size: 0,
46 });
47 }
48
49 root_cid = Some(node.cid);
50 }
51
52 let root = root_cid.ok_or_else(|| VoidError::Network("empty directory".into()))?;
53
54 Ok(AddResult {
55 root_cid: root,
56 entries,
57 blocks_total,
58 bytes_in,
59 bytes_out,
60 })
61}
62
63pub fn add_file(store: &dyn UnixFsStore, path: &Path) -> Result<FileAddResult> {
65 let data = std::fs::read(path).map_err(VoidError::Io)?;
66 add_bytes(store, &data)
67}
68
69pub fn add_bytes(store: &dyn UnixFsStore, data: &[u8]) -> Result<FileAddResult> {
71 let size = data.len() as u64;
72 let mut adder = FileAdder::default();
73 let mut file_cid = None;
74 let mut blocks = 0usize;
75 let mut block_bytes = 0u64;
76 let mut offset = 0;
77
78 while offset < data.len() {
79 let (new_blocks, consumed) = adder.push(&data[offset..]);
80 for (cid, block) in new_blocks {
81 let bsize = store.put(cid, block);
82 blocks += 1;
83 block_bytes += bsize as u64;
84 file_cid = Some(cid);
85 }
86 offset += consumed;
87 }
88
89 for (cid, block) in adder.finish() {
90 let bsize = store.put(cid, block);
91 blocks += 1;
92 block_bytes += bsize as u64;
93 file_cid = Some(cid);
94 }
95
96 let cid = file_cid.ok_or_else(|| VoidError::Network("empty file produced no blocks".into()))?;
97 Ok(FileAddResult { cid, size, blocks, block_bytes })
98}
99
100fn add_dir_recursive(
101 store: &dyn UnixFsStore,
102 base: &Path,
103 current: &Path,
104 tree_builder: &mut BufferingTreeBuilder,
105 entries: &mut Vec<AddEntry>,
106 blocks_total: &mut usize,
107 bytes_in: &mut u64,
108 bytes_out: &mut u64,
109) -> Result<()> {
110 let mut dir_entries: Vec<_> = std::fs::read_dir(current)
111 .map_err(VoidError::Io)?
112 .collect::<std::result::Result<Vec<_>, _>>()
113 .map_err(VoidError::Io)?;
114
115 dir_entries.sort_by_key(|e| e.file_name());
117
118 for entry in dir_entries {
119 let path = entry.path();
120 let rel_path = path
121 .strip_prefix(base)
122 .unwrap_or(&path)
123 .to_string_lossy()
124 .replace('\\', "/");
125
126 if path.is_dir() {
127 add_dir_recursive(
128 store, base, &path, tree_builder,
129 entries, blocks_total, bytes_in, bytes_out,
130 )?;
131 } else if path.is_file() {
132 let file_result = add_file(store, &path)?;
133 tree_builder
134 .put_link(&rel_path, file_result.cid, file_result.size)
135 .map_err(|e| VoidError::Network(format!("unixfs tree link: {e}")))?;
136
137 entries.push(AddEntry {
138 path: rel_path,
139 cid: file_result.cid,
140 kind: EntryKind::File,
141 size: file_result.size,
142 });
143
144 *blocks_total += file_result.blocks;
145 *bytes_in += file_result.size;
146 *bytes_out += file_result.block_bytes;
147 }
148 }
149
150 Ok(())
151}