Skip to main content

void_core/unixfs/
add.rs

1//! UnixFS add operations — add directories, files, and raw bytes.
2
3use 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
12/// Add a directory recursively as a UnixFS DAG.
13///
14/// Returns structured metadata about every file and directory added,
15/// plus the root CID that IPFS gateways can serve.
16pub 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    // Build the directory DAG (directory nodes on top of file blocks).
32    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
63/// Add a single file from disk.
64pub 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
69/// Add raw bytes as a UnixFS file.
70pub 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    // Sort for deterministic CIDs.
116    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}