use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use zip::ZipArchive;
use crate::unpacker::unpacker_utils::apply_strip;
use crate::unpacker::Unpacker;
pub struct ZipUnpacker {
archive_path: PathBuf,
}
impl ZipUnpacker {
pub fn new(archive: &Path) -> ZipUnpacker {
ZipUnpacker {
archive_path: archive.to_path_buf(),
}
}
pub fn supports(name: &str) -> bool {
name.ends_with(".zip")
}
}
impl Unpacker for ZipUnpacker {
fn unpack(&self, dst_dir: &Path, strip: u32) -> Result<Option<String>> {
let archive_file = fs::File::open(&self.archive_path)
.with_context(|| format!("Failed to open {:?}", self.archive_path))?;
let mut archive = ZipArchive::new(archive_file)
.with_context(|| format!("Failed to read {:?}", self.archive_path))?;
for idx in 0..archive.len() {
let mut file = archive.by_index(idx)?;
let dst_sub_path = match file.enclosed_name() {
Some(path) => path.to_owned(),
None => continue,
};
let dst_sub_path = match apply_strip(&dst_sub_path, strip) {
Some(x) => x,
None => continue,
};
let dst_path = dst_dir.join(dst_sub_path);
if (*file.name()).ends_with('/') {
fs::create_dir_all(&dst_path)
.with_context(|| format!("Failed to create directory {dst_path:?}"))?;
} else {
if let Some(parent) = dst_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory {parent:?}"))?;
}
}
let mut dst_file = fs::File::create(&dst_path)
.with_context(|| format!("Failed to create file {dst_path:?}"))?;
io::copy(&mut file, &mut dst_file).with_context(|| {
format!("Failed to write {:?} to {dst_path:?}", file.name())
})?;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
fs::set_permissions(&dst_path, fs::Permissions::from_mode(mode))?;
}
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_file_utils::{get_fixture_path, list_tree, pathbufset_from_strings};
#[test]
fn unpack_should_unpack_in_the_right_dir() {
let dir = assert_fs::TempDir::new().unwrap();
let zip_path = get_fixture_path("test_archive.zip");
let unpacker = ZipUnpacker::new(&zip_path);
let dst_dir = dir.join("sub");
unpacker.unpack(&dst_dir, 0).unwrap();
assert_eq!(
list_tree(&dst_dir).unwrap(),
pathbufset_from_strings(&["hello/bin/hello", "hello/README.md"])
);
}
#[test]
fn unpack_should_honor_strip() {
let dir = assert_fs::TempDir::new().unwrap();
let zip_path = get_fixture_path("test_archive.zip");
let unpacker = ZipUnpacker::new(&zip_path);
let dst_dir = dir.join("sub");
unpacker.unpack(&dst_dir, 1).unwrap();
assert_eq!(
list_tree(&dst_dir).unwrap(),
pathbufset_from_strings(&["bin/hello", "README.md"])
);
}
#[test]
fn unpack_should_ignore_files_outside_the_strip() {
let dir = assert_fs::TempDir::new().unwrap();
let zip_path = get_fixture_path("test_archive.zip");
let unpacker = ZipUnpacker::new(&zip_path);
let dst_dir = dir.join("sub");
unpacker.unpack(&dst_dir, 2).unwrap();
assert_eq!(
list_tree(&dst_dir).unwrap(),
pathbufset_from_strings(&["hello"])
);
}
}