rust_car/utils/
extract.rs1use std::collections::{HashMap, VecDeque};
2use std::fs;
3use std::io::Write;
4use std::path::Path;
5use std::path::PathBuf;
6
7use cid::Cid;
8
9use crate::error::CarError;
10use crate::unixfs::{FileType, UnixFs};
11use crate::{reader::CarReader, Ipld};
12
13pub fn extract_ipld_to_current_path(reader: &mut impl CarReader, cid: Cid) -> Result<(), CarError> {
16 extract_ipld(reader, cid, None::<PathBuf>)
17}
18
19pub fn extract_ipld(
23 reader: &mut impl CarReader,
24 cid: Cid,
25 parent: Option<impl AsRef<Path>>,
26) -> Result<(), CarError> {
27 let parent = parent.map(|p| p.as_ref().into());
28 extract_ipld_inner(reader, cid, parent)
29}
30
31struct UnixfsCache {
32 inner: UnixFs,
33 path: PathBuf,
34}
35
36struct IndexRelation {
37 parent_cid: Cid,
38 index: usize,
39}
40
41impl IndexRelation {
42 fn full_path(rel: Option<&IndexRelation>, cache: &HashMap<Cid, UnixfsCache>) -> Option<PathBuf> {
43 let filename = rel.and_then(|r| cache.get(&r.parent_cid)
44 .map(|f| f.inner.links[r.index].name_ref())
45 );
46 let parent_path = rel
47 .and_then(|r| cache
48 .get(&r.parent_cid)
49 .map(|p| &p.path)
50 );
51 parent_path.zip(filename).map(|(p, n)| {
52 let path: PathBuf = p.into();
53 path.join(n)
54 })
55 }
56}
57
58enum Type {
59 Directory,
60 File,
61 FileLinks(Box<UnixFs>),
62}
63
64
65fn extract_ipld_inner(
69 reader: &mut impl CarReader,
70 cid: Cid,
71 parent: Option<PathBuf>,
72) -> Result<(), CarError> {
73 let mut queue = VecDeque::<Cid>::new();
74 let mut unixfs_cache: HashMap<Cid, UnixfsCache> = Default::default();
75 let mut relations: HashMap<Cid, IndexRelation> = Default::default();
76 queue.push_back(cid);
77 let root_path = match parent {
78 Some(p) => {
79 p
80 },
81 None => cid.to_string().into(),
82 };
83 while let Some(cid) = queue.pop_front() {
84 let rel = relations.get(&cid);
85 let full_path = match IndexRelation::full_path(rel, &unixfs_cache) {
86 Some(f) => f,
87 None => root_path.clone(),
88 };
89 let file_ipld: Ipld = reader.ipld(&cid).unwrap();
90 let file_links = match file_ipld {
91 Ipld::Bytes(b) => {
92 let mut file = fs::OpenOptions::new()
93 .create(true)
94 .write(true)
95 .open(&full_path)
96 .unwrap();
97 file.write_all(&b).unwrap();
98 Type::File
99 }
100 m @ Ipld::Map(_) => {
101 let unixfs: UnixFs = (cid, m).try_into()?;
102 match unixfs.file_type {
103 FileType::File => Type::FileLinks(Box::new(unixfs)),
104 _=> {
105 for (idx, link) in unixfs.links().iter().enumerate() {
106 let rel = IndexRelation {
107 parent_cid: cid,
108 index: idx,
109 };
110 queue.push_back(link.hash);
111 relations.insert(link.hash, rel);
112 }
113 let rel = relations.get(&cid);
114 let path = IndexRelation::full_path(rel, &unixfs_cache)
115 .unwrap_or_else(|| root_path.clone());
116 unixfs_cache.insert(cid, UnixfsCache {
117 inner: unixfs,
118 path,
119 });
120 Type::Directory
121 }
122 }
123 }
124 _ => unimplemented!("not implement"),
125 };
126
127 match file_links {
128 Type::FileLinks(f) => {
129 let mut file = fs::OpenOptions::new()
130 .create(true)
131 .write(true)
132 .open(&full_path)
133 .unwrap();
134 for ufs in f.links() {
135 let file_ipld: Ipld = reader.ipld(&ufs.hash).unwrap();
136 match file_ipld {
137 Ipld::Bytes(b) => {
138 file.write_all(&b).unwrap();
139 }
140 _ => unreachable!("should not happend."),
141 }
142 }
143 }
144 Type::Directory => if !full_path.exists() {
145 fs::create_dir(&full_path)?
146 },
147 _ => {},
148 }
149 }
150 Ok(())
151}