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