1use super::*;
2use crate::directory::is_internal_directory_link;
3
4impl<S: Store> HashTree<S> {
5 fn decode_node_or_blob(data: &[u8]) -> Result<Option<TreeNode>, HashTreeError> {
6 match decode_tree_node(data) {
7 Ok(node) => Ok(Some(node)),
8 Err(err) if is_tree_node(data) => Err(HashTreeError::Codec(err)),
9 Err(_) => Ok(None),
10 }
11 }
12
13 pub async fn walk(&self, cid: &Cid, path: &str) -> Result<Vec<WalkEntry>, HashTreeError> {
15 let mut entries = Vec::new();
16 self.walk_recursive(cid, path, &mut entries).await?;
17 Ok(entries)
18 }
19
20 async fn walk_recursive(
21 &self,
22 cid: &Cid,
23 path: &str,
24 entries: &mut Vec<WalkEntry>,
25 ) -> Result<(), HashTreeError> {
26 let data = match self
27 .store
28 .get(&cid.hash)
29 .await
30 .map_err(|e| HashTreeError::Store(e.to_string()))?
31 {
32 Some(d) => d,
33 None => return Ok(()),
34 };
35
36 let data = if let Some(key) = &cid.key {
38 decrypt_chk(&data, key).map_err(|e| HashTreeError::Decryption(e.to_string()))?
39 } else {
40 data
41 };
42
43 let node = match Self::decode_node_or_blob(&data)? {
44 Some(node) => node,
45 None => {
46 entries.push(WalkEntry {
47 path: path.to_string(),
48 hash: cid.hash,
49 link_type: LinkType::Blob,
50 size: data.len() as u64,
51 key: cid.key,
52 });
53 return Ok(());
54 }
55 };
56
57 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
58 entries.push(WalkEntry {
59 path: path.to_string(),
60 hash: cid.hash,
61 link_type: node.node_type,
62 size: node_size,
63 key: cid.key,
64 });
65
66 for link in &node.links {
67 let child_path = match &link.name {
68 Some(name) => {
69 if is_internal_directory_link(&node, link) {
70 let sub_cid = Cid {
71 hash: link.hash,
72 key: link.key,
73 };
74 Box::pin(self.walk_recursive(&sub_cid, path, entries)).await?;
75 continue;
76 }
77 if path.is_empty() {
78 name.clone()
79 } else {
80 format!("{}/{}", path, name)
81 }
82 }
83 None => path.to_string(),
84 };
85
86 let child_cid = Cid {
88 hash: link.hash,
89 key: link.key,
90 };
91 Box::pin(self.walk_recursive(&child_cid, &child_path, entries)).await?;
92 }
93
94 Ok(())
95 }
96
97 pub async fn walk_parallel(
100 &self,
101 cid: &Cid,
102 path: &str,
103 concurrency: usize,
104 ) -> Result<Vec<WalkEntry>, HashTreeError> {
105 self.walk_parallel_with_progress(cid, path, concurrency, None)
106 .await
107 }
108
109 pub async fn walk_parallel_with_progress(
116 &self,
117 cid: &Cid,
118 path: &str,
119 concurrency: usize,
120 progress: Option<&std::sync::atomic::AtomicUsize>,
121 ) -> Result<Vec<WalkEntry>, HashTreeError> {
122 use futures::stream::{FuturesUnordered, StreamExt};
123 use std::collections::VecDeque;
124 use std::sync::atomic::Ordering;
125
126 let mut entries = Vec::new();
127 let mut pending: VecDeque<(Cid, String)> = VecDeque::new();
128 let mut active = FuturesUnordered::new();
129
130 pending.push_back((cid.clone(), path.to_string()));
132
133 loop {
134 while active.len() < concurrency {
136 if let Some((node_cid, node_path)) = pending.pop_front() {
137 let store = &self.store;
138 let fut = async move {
139 let data = store
140 .get(&node_cid.hash)
141 .await
142 .map_err(|e| HashTreeError::Store(e.to_string()))?;
143 Ok::<_, HashTreeError>((node_cid, node_path, data))
144 };
145 active.push(fut);
146 } else {
147 break;
148 }
149 }
150
151 if active.is_empty() {
153 break;
154 }
155
156 if let Some(result) = active.next().await {
158 let (node_cid, node_path, data) = result?;
159
160 if let Some(counter) = progress {
162 counter.fetch_add(1, Ordering::Relaxed);
163 }
164
165 let data = match data {
166 Some(d) => d,
167 None => continue,
168 };
169
170 let data = if let Some(key) = &node_cid.key {
172 decrypt_chk(&data, key).map_err(|e| {
173 HashTreeError::Decryption(format!(
174 "{} at path '{}' hash {} key {}",
175 e,
176 node_path,
177 hex::encode(node_cid.hash),
178 hex::encode(key)
179 ))
180 })?
181 } else {
182 data
183 };
184
185 let node = match Self::decode_node_or_blob(&data)? {
186 Some(node) => node,
187 None => {
188 entries.push(WalkEntry {
190 path: node_path,
191 hash: node_cid.hash,
192 link_type: LinkType::Blob,
193 size: data.len() as u64,
194 key: node_cid.key,
195 });
196 continue;
197 }
198 };
199
200 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
202 entries.push(WalkEntry {
203 path: node_path.clone(),
204 hash: node_cid.hash,
205 link_type: node.node_type,
206 size: node_size,
207 key: node_cid.key,
208 });
209
210 for link in &node.links {
212 let child_path = match &link.name {
213 Some(name) => {
214 if is_internal_directory_link(&node, link) {
215 let sub_cid = Cid {
216 hash: link.hash,
217 key: link.key,
218 };
219 pending.push_back((sub_cid, node_path.clone()));
220 continue;
221 }
222 if node_path.is_empty() {
223 name.clone()
224 } else {
225 format!("{}/{}", node_path, name)
226 }
227 }
228 None => node_path.clone(),
229 };
230
231 if link.link_type == LinkType::Blob {
234 entries.push(WalkEntry {
235 path: child_path,
236 hash: link.hash,
237 link_type: LinkType::Blob,
238 size: link.size,
239 key: link.key,
240 });
241 if let Some(counter) = progress {
242 counter.fetch_add(1, Ordering::Relaxed);
243 }
244 continue;
245 }
246
247 let child_cid = Cid {
249 hash: link.hash,
250 key: link.key,
251 };
252 pending.push_back((child_cid, child_path));
253 }
254 }
255 }
256
257 Ok(entries)
258 }
259
260 pub fn walk_stream(
262 &self,
263 cid: Cid,
264 initial_path: String,
265 ) -> Pin<Box<dyn Stream<Item = Result<WalkEntry, HashTreeError>> + Send + '_>> {
266 Box::pin(stream::unfold(
267 WalkStreamState::Init {
268 cid,
269 path: initial_path,
270 tree: self,
271 },
272 |state| async move {
273 match state {
274 WalkStreamState::Init { cid, path, tree } => {
275 let data = match tree.store.get(&cid.hash).await {
276 Ok(Some(d)) => d,
277 Ok(None) => return None,
278 Err(e) => {
279 return Some((
280 Err(HashTreeError::Store(e.to_string())),
281 WalkStreamState::Done,
282 ))
283 }
284 };
285
286 let data = if let Some(key) = &cid.key {
288 match decrypt_chk(&data, key) {
289 Ok(d) => d,
290 Err(e) => {
291 return Some((
292 Err(HashTreeError::Decryption(format!(
293 "{} at path '{}' hash {} key {}",
294 e,
295 path,
296 hex::encode(cid.hash),
297 hex::encode(key)
298 ))),
299 WalkStreamState::Done,
300 ))
301 }
302 }
303 } else {
304 data
305 };
306
307 let node = match Self::decode_node_or_blob(&data) {
308 Ok(Some(node)) => node,
309 Ok(None) => {
310 let entry = WalkEntry {
312 path,
313 hash: cid.hash,
314 link_type: LinkType::Blob,
315 size: data.len() as u64,
316 key: cid.key,
317 };
318 return Some((Ok(entry), WalkStreamState::Done));
319 }
320 Err(err) => return Some((Err(err), WalkStreamState::Done)),
321 };
322
323 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
324 let entry = WalkEntry {
325 path: path.clone(),
326 hash: cid.hash,
327 link_type: node.node_type,
328 size: node_size,
329 key: cid.key,
330 };
331
332 let mut stack: Vec<WalkStackItem> = Vec::new();
334 for link in node.links.iter().rev() {
335 let is_internal = is_internal_directory_link(&node, link);
336 let child_path = match &link.name {
337 Some(name) if !is_internal => {
338 if path.is_empty() {
339 name.clone()
340 } else {
341 format!("{}/{}", path, name)
342 }
343 }
344 _ => path.clone(),
345 };
346 stack.push(WalkStackItem {
348 hash: link.hash,
349 path: child_path,
350 key: link.key,
351 });
352 }
353
354 Some((Ok(entry), WalkStreamState::Processing { stack, tree }))
355 }
356 WalkStreamState::Processing { mut stack, tree } => {
357 tree.process_walk_stack(&mut stack).await
358 }
359 WalkStreamState::Done => None,
360 }
361 },
362 ))
363 }
364
365 async fn process_walk_stack<'a>(
366 &'a self,
367 stack: &mut Vec<WalkStackItem>,
368 ) -> Option<(Result<WalkEntry, HashTreeError>, WalkStreamState<'a, S>)> {
369 while let Some(item) = stack.pop() {
370 let data = match self.store.get(&item.hash).await {
371 Ok(Some(d)) => d,
372 Ok(None) => continue,
373 Err(e) => {
374 return Some((
375 Err(HashTreeError::Store(e.to_string())),
376 WalkStreamState::Done,
377 ))
378 }
379 };
380
381 let data = if let Some(key) = &item.key {
382 match decrypt_chk(&data, key) {
383 Ok(d) => d,
384 Err(e) => {
385 return Some((
386 Err(HashTreeError::Decryption(format!(
387 "{} at path '{}' hash {} key {}",
388 e,
389 item.path,
390 hex::encode(item.hash),
391 hex::encode(key)
392 ))),
393 WalkStreamState::Done,
394 ))
395 }
396 }
397 } else {
398 data
399 };
400
401 let node = match Self::decode_node_or_blob(&data) {
402 Ok(Some(node)) => node,
403 Ok(None) => {
404 let entry = WalkEntry {
406 path: item.path,
407 hash: item.hash,
408 link_type: LinkType::Blob,
409 size: data.len() as u64,
410 key: item.key,
411 };
412 return Some((
413 Ok(entry),
414 WalkStreamState::Processing {
415 stack: std::mem::take(stack),
416 tree: self,
417 },
418 ));
419 }
420 Err(err) => return Some((Err(err), WalkStreamState::Done)),
421 };
422
423 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
424 let entry = WalkEntry {
425 path: item.path.clone(),
426 hash: item.hash,
427 link_type: node.node_type,
428 size: node_size,
429 key: item.key,
430 };
431
432 for link in node.links.iter().rev() {
434 let is_internal = is_internal_directory_link(&node, link);
435 let child_path = match &link.name {
436 Some(name) if !is_internal => {
437 if item.path.is_empty() {
438 name.clone()
439 } else {
440 format!("{}/{}", item.path, name)
441 }
442 }
443 _ => item.path.clone(),
444 };
445 stack.push(WalkStackItem {
446 hash: link.hash,
447 path: child_path,
448 key: link.key,
449 });
450 }
451
452 return Some((
453 Ok(entry),
454 WalkStreamState::Processing {
455 stack: std::mem::take(stack),
456 tree: self,
457 },
458 ));
459 }
460 None
461 }
462}
463
464struct WalkStackItem {
465 hash: Hash,
466 path: String,
467 key: Option<[u8; 32]>,
468}
469
470enum WalkStreamState<'a, S: Store> {
471 Init {
472 cid: Cid,
473 path: String,
474 tree: &'a HashTree<S>,
475 },
476 Processing {
477 stack: Vec<WalkStackItem>,
478 tree: &'a HashTree<S>,
479 },
480 Done,
481}