rust_car/utils/
extract.rs

1use 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
13/// extract files to current path from CAR file.
14/// `cid` is the root cid
15pub fn extract_ipld_to_current_path(reader: &mut impl CarReader, cid: Cid) -> Result<(), CarError> {
16    extract_ipld(reader, cid, None::<PathBuf>)
17}
18
19/// extract files from CAR file.
20/// if the `parent` path is none, will use current path as root path.
21/// `cid` is the root cid
22pub 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
65/// inner function, extract files from CAR file.
66/// if the `parent` path is none, will use current path as root path.
67/// `cid` is the file cid
68fn 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}