use std::{
fs::File,
io::{BufWriter, Seek, Write},
path::{Path, PathBuf},
};
use walkdir::WalkDir;
use zip::{
write::{FileOptions, ZipWriter},
CompressionMethod,
result::ZipResult,
};
#[derive(Debug)]
pub enum PathType {
File,
Dir,
}
#[derive(Debug)]
pub enum Compression {
Stored,
Deflated,
Bzip2,
Zstd,
}
impl Compression {
pub fn stored() -> Self {
Compression::Stored
}
pub fn deflated() -> Self {
Compression::Deflated
}
pub fn bzip2() -> Self {
Compression::Bzip2
}
pub fn zstd() -> Self {
Compression::Zstd
}
}
#[derive(Debug)]
pub struct ZipItem {
source_path: PathBuf,
path_type: PathType,
}
pub struct ZipManager {
items: Vec<ZipItem>,
output_path: PathBuf,
}
impl ZipManager {
pub fn new(output_path: &str) -> Self {
Self {
items: Vec::new(),
output_path: PathBuf::from(output_path),
}
}
pub fn add_file(&mut self, source: &str) {
self.items.push(ZipItem {
source_path: PathBuf::from(source),
path_type: PathType::File,
});
}
pub fn add_directory(&mut self, source: &str) {
self.items.push(ZipItem {
source_path: PathBuf::from(source),
path_type: PathType::Dir,
});
}
pub fn create_zip(&self, compression: Compression) -> ZipResult<()> {
let file = File::create(&self.output_path)?;
let buf_writer = BufWriter::new(file);
let mut zip_writer = ZipWriter::new(buf_writer);
let options: FileOptions<()> = match compression {
Compression::Deflated => {
FileOptions::default()
.compression_method(CompressionMethod::Deflated)
.unix_permissions(0o755)
}
Compression::Stored => {
FileOptions::default()
.compression_method(CompressionMethod::Stored)
.unix_permissions(0o755)
}
Compression::Bzip2 => {
FileOptions::default()
.compression_method(CompressionMethod::Bzip2)
.unix_permissions(0o755)
}
Compression::Zstd => {
FileOptions::default()
.compression_method(CompressionMethod::Zstd)
.unix_permissions(0o755)
}
};
for item in &self.items {
match item.path_type {
PathType::File => {
let base_path = item.source_path.parent().unwrap_or(Path::new(""));
self.compress_file(
&mut zip_writer,
&item.source_path,
base_path,
&options,
)?;
}
PathType::Dir => {
let base_path = &item.source_path;
self.compress_directory(&mut zip_writer, base_path, &options)?;
}
}
}
zip_writer.finish()?;
println!("ZIP archive created successfully at {:?}", self.output_path);
Ok(())
}
pub fn compress_file<W>(
&self,
zip_writer: &mut ZipWriter<W>,
file_path: &Path,
base_path: &Path,
options: &FileOptions<()>,
) -> ZipResult<()>
where
W: Write + Seek,
{
let relative_path = file_path.strip_prefix(base_path).unwrap_or(file_path);
let zip_path = relative_path.to_string_lossy().replace("\\", "/");
zip_writer.start_file(&zip_path, options.to_owned())?;
let mut f = File::open(file_path)?;
std::io::copy(&mut f, zip_writer)?;
Ok(())
}
pub fn compress_directory<W>(
&self,
zip_writer: &mut ZipWriter<W>,
dir_path: &Path,
options: &FileOptions<()>,
) -> ZipResult<()>
where
W: Write + Seek,
{
for entry in WalkDir::new(dir_path).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
let relative_path = path.strip_prefix(dir_path).unwrap_or(path);
let zip_path = relative_path.to_string_lossy().replace("\\", "/");
if path.is_file() {
self.compress_file(zip_writer, path, dir_path, options)?;
} else if path.is_dir() {
let dir_name = if zip_path.ends_with('/') || zip_path.ends_with('\\') {
zip_path.to_owned()
} else {
format!("{}/", zip_path)
};
zip_writer.add_directory(dir_name, options.to_owned())?;
}
}
Ok(())
}
}