use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use tar::Archive;
use crate::unpacker::unpacker_utils::apply_strip;
use crate::unpacker::Unpacker;
pub struct TarUnpacker {
pub archive_path: PathBuf,
}
impl TarUnpacker {
pub fn new(archive_path: &Path) -> TarUnpacker {
TarUnpacker {
archive_path: archive_path.to_path_buf(),
}
}
pub fn supports(name: &str) -> bool {
name.ends_with(".tar.gz")
|| name.ends_with(".tar.bz2")
|| name.ends_with(".tar.xz")
|| name.ends_with(".tgz")
|| name.ends_with(".tbz2")
}
}
impl Unpacker for TarUnpacker {
fn unpack(&self, dst_dir: &Path, strip: u32) -> Result<Option<String>> {
let extension = self
.archive_path
.extension()
.ok_or_else(|| anyhow!("Can't get extension for {:?}", self.archive_path))?
.to_str()
.unwrap();
let compressed_reader: Box<dyn io::Read> = match extension {
"gz" | "tgz" => Box::new(archiver_rs::Gzip::open(&self.archive_path)?),
"bz2" | "tbz2" => Box::new(archiver_rs::Bzip2::open(&self.archive_path)?),
"xz" => Box::new(archiver_rs::Xz::open(&self.archive_path)?),
_ => {
return Err(anyhow!("Don't know how to unpack {:?}", self.archive_path));
}
};
let mut tar_reader = Archive::new(compressed_reader);
let dst_dir = &dst_dir.canonicalize().unwrap_or(dst_dir.to_path_buf());
fs::create_dir_all(dst_dir)?;
for entry in tar_reader.entries()? {
let mut entry = entry?;
let path = match apply_strip(&entry.path()?, strip) {
Some(x) => x,
None => continue,
};
if entry.header().entry_type() != tar::EntryType::Directory {
let entry_path = dst_dir.join(path);
let entry_dir = entry_path.parent().unwrap();
fs::create_dir_all(entry_dir)?;
entry.unpack(entry_path)?;
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use yare::parameterized;
use super::*;
use crate::test_file_utils::{get_fixture_path, list_tree, pathbufset_from_strings};
#[parameterized(
tar_gz = { "test_archive.tar.gz" },
tar_bz2 = { "test_archive.tar.bz2" },
tar_xz = { "test_archive.tar.xz" },
tgz = { "test_archive.tgz" },
tbz2 = { "test_archive.tbz2" },
)]
fn test_unpack(filename: &str) {
let path = get_fixture_path(filename);
let dir = assert_fs::TempDir::new().unwrap();
let unpacker = TarUnpacker::new(&path);
let ret = unpacker.unpack(&dir, 0).unwrap();
assert!(ret.is_none());
let actual_files = list_tree(&dir).unwrap();
let expected_files = pathbufset_from_strings(&[
"hello/bin/hello",
"hello/bin/hello-symlink",
"hello/README.md",
]);
assert_eq!(actual_files, expected_files);
#[cfg(unix)]
{
use crate::test_file_utils::is_file_executable;
let exe_path = dir.join("hello/bin/hello").canonicalize().unwrap();
assert!(is_file_executable(&exe_path));
let symlink_path = dir.join("hello/bin/hello-symlink");
assert!(symlink_path.exists());
assert!(symlink_path.is_symlink());
let target_path = symlink_path.canonicalize().unwrap();
assert_eq!(target_path, exe_path);
}
}
#[test]
fn test_strip_components() {
let path = get_fixture_path("test_archive.tar.gz");
let dir = assert_fs::TempDir::new().unwrap();
let unpacker = TarUnpacker::new(&path);
let ret = unpacker.unpack(&dir, 1).unwrap();
assert!(ret.is_none());
let actual_files = list_tree(&dir).unwrap();
let expected_files =
pathbufset_from_strings(&["bin/hello", "bin/hello-symlink", "README.md"]);
assert_eq!(actual_files, expected_files);
}
}