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(
101 &self,
102 cid: &Cid,
103 path: &str,
104 concurrency: usize,
105 ) -> Result<Vec<WalkEntry>, HashTreeError> {
106 self.walk_parallel_with_progress(cid, path, concurrency, None)
107 .await
108 }
109
110 pub async fn walk_parallel_with_progress(
118 &self,
119 cid: &Cid,
120 path: &str,
121 concurrency: usize,
122 progress: Option<&std::sync::atomic::AtomicUsize>,
123 ) -> Result<Vec<WalkEntry>, HashTreeError> {
124 use futures::stream::{FuturesUnordered, StreamExt};
125 use std::collections::VecDeque;
126 use std::sync::atomic::Ordering;
127
128 let mut entries = Vec::new();
129 let concurrency = concurrency.max(1);
130 let mut pending: VecDeque<(Cid, String)> = VecDeque::new();
131 let mut active = FuturesUnordered::new();
132
133 pending.push_back((cid.clone(), path.to_string()));
135
136 loop {
137 while active.len() < concurrency {
139 if let Some((node_cid, node_path)) = pending.pop_front() {
140 let store = &self.store;
141 let fut = async move {
142 let data = store
143 .get(&node_cid.hash)
144 .await
145 .map_err(|e| HashTreeError::Store(e.to_string()))?;
146 Ok::<_, HashTreeError>((node_cid, node_path, data))
147 };
148 active.push(fut);
149 } else {
150 break;
151 }
152 }
153
154 if active.is_empty() {
156 break;
157 }
158
159 if let Some(result) = active.next().await {
161 let (node_cid, node_path, data) = result?;
162
163 if let Some(counter) = progress {
165 counter.fetch_add(1, Ordering::Relaxed);
166 }
167
168 let data = match data {
169 Some(d) => d,
170 None => continue,
171 };
172
173 let data = if let Some(key) = &node_cid.key {
175 decrypt_chk(&data, key).map_err(|e| {
176 HashTreeError::Decryption(format!(
177 "{} at path '{}' hash {}",
178 e,
179 node_path,
180 hex::encode(node_cid.hash)
181 ))
182 })?
183 } else {
184 data
185 };
186
187 let node = match Self::decode_node_or_blob(&data)? {
188 Some(node) => node,
189 None => {
190 entries.push(WalkEntry {
192 path: node_path,
193 hash: node_cid.hash,
194 link_type: LinkType::Blob,
195 size: data.len() as u64,
196 key: node_cid.key,
197 });
198 continue;
199 }
200 };
201
202 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
204 entries.push(WalkEntry {
205 path: node_path.clone(),
206 hash: node_cid.hash,
207 link_type: node.node_type,
208 size: node_size,
209 key: node_cid.key,
210 });
211
212 for link in &node.links {
214 let child_path = match &link.name {
215 Some(name) => {
216 if is_internal_directory_link(&node, link) {
217 let sub_cid = Cid {
218 hash: link.hash,
219 key: link.key,
220 };
221 pending.push_back((sub_cid, node_path.clone()));
222 continue;
223 }
224 if node_path.is_empty() {
225 name.clone()
226 } else {
227 format!("{}/{}", node_path, name)
228 }
229 }
230 None => node_path.clone(),
231 };
232
233 if link.link_type == LinkType::Blob {
236 entries.push(WalkEntry {
237 path: child_path,
238 hash: link.hash,
239 link_type: LinkType::Blob,
240 size: link.size,
241 key: link.key,
242 });
243 if let Some(counter) = progress {
244 counter.fetch_add(1, Ordering::Relaxed);
245 }
246 continue;
247 }
248
249 let child_cid = Cid {
251 hash: link.hash,
252 key: link.key,
253 };
254 pending.push_back((child_cid, child_path));
255 }
256 }
257 }
258
259 Ok(entries)
260 }
261
262 pub fn walk_stream(
264 &self,
265 cid: Cid,
266 initial_path: String,
267 ) -> Pin<Box<dyn Stream<Item = Result<WalkEntry, HashTreeError>> + Send + '_>> {
268 Box::pin(stream::unfold(
269 WalkStreamState::Init {
270 cid,
271 path: initial_path,
272 tree: self,
273 },
274 |state| async move {
275 match state {
276 WalkStreamState::Init { cid, path, tree } => {
277 let data = match tree.store.get(&cid.hash).await {
278 Ok(Some(d)) => d,
279 Ok(None) => return None,
280 Err(e) => {
281 return Some((
282 Err(HashTreeError::Store(e.to_string())),
283 WalkStreamState::Done,
284 ))
285 }
286 };
287
288 let data = if let Some(key) = &cid.key {
290 match decrypt_chk(&data, key) {
291 Ok(d) => d,
292 Err(e) => {
293 return Some((
294 Err(HashTreeError::Decryption(format!(
295 "{} at path '{}' hash {}",
296 e,
297 path,
298 hex::encode(cid.hash)
299 ))),
300 WalkStreamState::Done,
301 ))
302 }
303 }
304 } else {
305 data
306 };
307
308 let node = match Self::decode_node_or_blob(&data) {
309 Ok(Some(node)) => node,
310 Ok(None) => {
311 let entry = WalkEntry {
313 path,
314 hash: cid.hash,
315 link_type: LinkType::Blob,
316 size: data.len() as u64,
317 key: cid.key,
318 };
319 return Some((Ok(entry), WalkStreamState::Done));
320 }
321 Err(err) => return Some((Err(err), WalkStreamState::Done)),
322 };
323
324 let node_size: u64 = node.links.iter().map(|l| l.size).sum();
325 let entry = WalkEntry {
326 path: path.clone(),
327 hash: cid.hash,
328 link_type: node.node_type,
329 size: node_size,
330 key: cid.key,
331 };
332
333 let mut stack: Vec<WalkStackItem> = Vec::new();
335 for link in node.links.iter().rev() {
336 let is_internal = is_internal_directory_link(&node, link);
337 let child_path = match &link.name {
338 Some(name) if !is_internal => {
339 if path.is_empty() {
340 name.clone()
341 } else {
342 format!("{}/{}", path, name)
343 }
344 }
345 _ => path.clone(),
346 };
347 stack.push(WalkStackItem {
349 hash: link.hash,
350 path: child_path,
351 key: link.key,
352 });
353 }
354
355 Some((Ok(entry), WalkStreamState::Processing { stack, tree }))
356 }
357 WalkStreamState::Processing { mut stack, tree } => {
358 tree.process_walk_stack(&mut stack).await
359 }
360 WalkStreamState::Done => None,
361 }
362 },
363 ))
364 }
365
366 async fn process_walk_stack<'a>(
367 &'a self,
368 stack: &mut Vec<WalkStackItem>,
369 ) -> Option<(Result<WalkEntry, HashTreeError>, WalkStreamState<'a, S>)> {
370 while let Some(item) = stack.pop() {
371 let data = match self.store.get(&item.hash).await {
372 Ok(Some(d)) => d,
373 Ok(None) => continue,
374 Err(e) => {
375 return Some((
376 Err(HashTreeError::Store(e.to_string())),
377 WalkStreamState::Done,
378 ))
379 }
380 };
381
382 let data = if let Some(key) = &item.key {
383 match decrypt_chk(&data, key) {
384 Ok(d) => d,
385 Err(e) => {
386 return Some((
387 Err(HashTreeError::Decryption(format!(
388 "{} at path '{}' hash {}",
389 e,
390 item.path,
391 hex::encode(item.hash)
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}