1use crate::util::errors::{wrap, WrappedError};
6
7use flate2::read::GzDecoder;
8use std::fs;
9use std::io::Seek;
10use std::path::{Path, PathBuf};
11use tar::Archive;
12
13use super::errors::wrapdbg;
14use super::io::ReportCopyProgress;
15
16fn should_skip_first_segment(file: &fs::File) -> Result<bool, WrappedError> {
17 let tar = GzDecoder::new(file);
23 let mut archive = Archive::new(tar);
24 let mut entries = archive
25 .entries()
26 .map_err(|e| wrap(e, "error opening archive"))?;
27
28 let first_name = {
29 let file = entries
30 .next()
31 .expect("expected not to have an empty archive")
32 .map_err(|e| wrap(e, "error reading entry file"))?;
33
34 let path = file.path().expect("expected to have path");
35
36 path.iter()
37 .next()
38 .expect("expected to have non-empty name")
39 .to_owned()
40 };
41
42 let mut had_multiple = false;
43 for file in entries.flatten() {
44 had_multiple = true;
45 if let Ok(name) = file.path() {
46 if name.iter().next() != Some(&first_name) {
47 return Ok(false);
48 }
49 }
50 }
51
52 Ok(had_multiple) }
54
55pub fn decompress_tarball<T>(
56 path: &Path,
57 parent_path: &Path,
58 mut reporter: T,
59) -> Result<(), WrappedError>
60where
61 T: ReportCopyProgress,
62{
63 let mut tar_gz = fs::File::open(path)
64 .map_err(|e| wrap(e, format!("error opening file {}", path.display())))?;
65 let skip_first = should_skip_first_segment(&tar_gz)?;
66
67 tar_gz
69 .rewind()
70 .map_err(|e| wrap(e, "error resetting seek position"))?;
71
72 let tar = GzDecoder::new(tar_gz);
73 let mut archive = Archive::new(tar);
74
75 let results = archive
76 .entries()
77 .map_err(|e| wrap(e, format!("error opening archive {}", path.display())))?
78 .filter_map(|e| e.ok())
79 .map(|mut entry| {
80 let entry_path = entry
81 .path()
82 .map_err(|e| wrap(e, "error reading entry path"))?;
83
84 let path = parent_path.join(if skip_first {
85 entry_path.iter().skip(1).collect::<PathBuf>()
86 } else {
87 entry_path.into_owned()
88 });
89
90 if let Some(p) = path.parent() {
91 fs::create_dir_all(p)
92 .map_err(|e| wrap(e, format!("could not create dir for {}", p.display())))?;
93 }
94
95 entry
96 .unpack(&path)
97 .map_err(|e| wrapdbg(e, format!("error unpacking {}", path.display())))?;
98 Ok(path)
99 })
100 .collect::<Result<Vec<PathBuf>, WrappedError>>()?;
101
102 reporter.report_progress(results.len() as u64, results.len() as u64);
104
105 Ok(())
106}