use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
use std::io::Read;
use zip::{write::SimpleFileOptions, CompressionMethod, ZipWriter};
use anyhow::{self, Result};
pub fn zip_folder(
folder_dir: &Path,
file_name: Option<&str>,
compression_type: Option<&str>,
compression_level: Option<i64>,
output_dir: Option<&str>,
) -> anyhow::Result<PathBuf> {
if !folder_dir.is_dir() {
anyhow::bail!(
"Provided path is not a directory, or does not exist: {:?}",
folder_dir
);
}
let zip_file_name = match file_name {
Some(name) => Path::new(name).with_extension("zip"),
None => folder_dir
.file_name()
.ok_or_else(|| anyhow::anyhow!("Folder has no valid name"))?
.to_os_string()
.into_string()
.map(|name| Path::new(&name).with_extension("zip"))
.map_err(|os_str| anyhow::anyhow!("Invalid OsString conversion: {}", os_str.to_string_lossy()))?
};
let zip_path = match output_dir {
Some(dir) => Path::new(dir).join(&zip_file_name),
None => folder_dir.parent().unwrap_or_else(|| Path::new(".")).join(&zip_file_name),
};
let compression_method = match compression_type {
Some("bzip2") | Some("bzip") => CompressionMethod::Bzip2,
Some("deflate") | Some("default") => CompressionMethod::Deflated,
Some("zstd") | Some("z") => CompressionMethod::Zstd,
Some(invalid) => {
anyhow::bail!("Invalid compression method: '{}'", invalid);
}
None => CompressionMethod::Deflated,
};
let compression_amount = match compression_level {
Some(level) => level,
None => {
if compression_method == CompressionMethod::Zstd {
3
} else {
6
}
}
};
let file = File::create(&zip_path)?;
let mut zip = ZipWriter::new(file);
let options = SimpleFileOptions::default()
.compression_method(compression_method)
.compression_level(Some(compression_amount))
.unix_permissions(0o755);
let mut buffer = Vec::new();
for entry in WalkDir::new(folder_dir).into_iter().filter_map(Result::ok) {
let path = entry.path();
let name = path.strip_prefix(folder_dir)?;
if path.is_file() {
zip.start_file(name.to_string_lossy(), options)?;
let mut f = File::open(path)?;
f.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
buffer.clear();
} else if path.is_dir() {
zip.add_directory(name.to_string_lossy(), options)?;
}
}
zip.finish()?;
Ok(zip_path)
}
pub fn unzip_file(zip_file_dir: &Path, file_name: Option<&str>, output_dir: Option<&str>) -> anyhow::Result<PathBuf> {
if !zip_file_dir.is_file() {
anyhow::bail!("Provided path is not a file, or does not exist: {:?}", zip_file_dir);
}
let base_name = match file_name {
Some(name) => name.to_string(),
None => zip_file_dir.file_stem().unwrap_or_default().to_string_lossy().into_owned(),
};
let output_dir = match output_dir {
Some(dir) => Path::new(dir).join(&base_name),
None => zip_file_dir.parent().unwrap_or_else(|| Path::new(".")).join(&base_name),
};
let mut output_dir = output_dir;
let mut counter = 1;
while output_dir.exists() {
output_dir = zip_file_dir
.parent()
.unwrap_or_else(|| Path::new("."))
.join(format!("{}-{}", base_name, counter));
counter += 1;
}
std::fs::create_dir_all(&output_dir)?;
let file = File::open(zip_file_dir)?;
let mut archive = zip::ZipArchive::new(file)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let out_path = output_dir.join(file.mangled_name());
if file.is_dir() {
std::fs::create_dir_all(&out_path)?;
} else {
if let Some(parent) = out_path.parent() {
std::fs::create_dir_all(parent)?;
}
let mut outfile = File::create(&out_path)?;
std::io::copy(&mut file, &mut outfile)?;
}
#[cfg(unix)]
if let Some(mode) = file.unix_mode() {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&out_path, std::fs::Permissions::from_mode(mode))?;
}
}
Ok(output_dir)
}