use crate::util::errors::{wrap, WrappedError};
use flate2::read::GzDecoder;
use std::fs::{self, File};
use std::io::{Read, Seek};
use std::path::{Path, PathBuf};
use tar::Archive;
use super::errors::wrapdbg;
use super::io::ReportCopyProgress;
fn should_skip_first_segment(file: &fs::File) -> Result<(bool, u64), WrappedError> {
let tar = GzDecoder::new(file);
let mut archive = Archive::new(tar);
let mut entries = archive
.entries()
.map_err(|e| wrap(e, "error opening archive"))?;
let first_name = {
let file = entries
.next()
.expect("expected not to have an empty archive")
.map_err(|e| wrap(e, "error reading entry file"))?;
let path = file.path().expect("expected to have path");
path.iter()
.next()
.expect("expected to have non-empty name")
.to_owned()
};
let mut num_entries = 1;
let mut had_different_prefixes = false;
for file in entries.flatten() {
if !had_different_prefixes {
if let Ok(name) = file.path() {
if name.iter().next() != Some(&first_name) {
had_different_prefixes = true;
}
}
}
num_entries += 1;
}
Ok((!had_different_prefixes && num_entries > 1, num_entries))
}
pub fn decompress_tarball<T>(
mut tar_gz: File,
parent_path: &Path,
mut reporter: T,
) -> Result<(), WrappedError>
where
T: ReportCopyProgress,
{
let (skip_first, num_entries) = should_skip_first_segment(&tar_gz)?;
let report_progress_every = num_entries / 20;
let mut entries_so_far = 0;
let mut last_reported_at = 0;
tar_gz
.rewind()
.map_err(|e| wrap(e, "error resetting seek position"))?;
let tar = GzDecoder::new(tar_gz);
let mut archive = Archive::new(tar);
archive
.entries()
.map_err(|e| wrap(e, "error opening archive"))?
.filter_map(|e| e.ok())
.try_for_each::<_, Result<_, WrappedError>>(|mut entry| {
entries_so_far += 1;
if entries_so_far - last_reported_at > report_progress_every {
reporter.report_progress(entries_so_far, num_entries);
entries_so_far += 1;
last_reported_at = entries_so_far;
}
let entry_path = entry
.path()
.map_err(|e| wrap(e, "error reading entry path"))?;
let path = parent_path.join(if skip_first {
entry_path.iter().skip(1).collect::<PathBuf>()
} else {
entry_path.into_owned()
});
if let Some(p) = path.parent() {
fs::create_dir_all(p)
.map_err(|e| wrap(e, format!("could not create dir for {}", p.display())))?;
}
entry
.unpack(&path)
.map_err(|e| wrapdbg(e, format!("error unpacking {}", path.display())))?;
Ok(())
})?;
reporter.report_progress(num_entries, num_entries);
Ok(())
}
pub fn has_gzip_header(path: &Path) -> std::io::Result<(File, bool)> {
let mut file = fs::File::open(path)?;
let mut header = [0; 2];
let _ = file.read_exact(&mut header);
file.rewind()?;
Ok((file, header[0] == 0x1f && header[1] == 0x8b))
}