use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::time::Instant;
use walkdir::WalkDir;
use zip::write::SimpleFileOptions;
use zip::{CompressionMethod, ZipWriter};
use crate::archive::compression_label;
use crate::codec::EncodingKind;
use crate::error::XzipError;
use crate::filter::PathFilter;
use crate::verbosity::Verbosity;
pub fn pack_path(
input: &Path,
output_zip: &Path,
encoding: EncodingKind,
recursive: bool,
include: &[String],
exclude: &[String],
verbosity: Verbosity,
) -> Result<(), XzipError> {
let started = Instant::now();
if !input.exists() {
return Err(XzipError::InvalidInput(input.to_path_buf()));
}
let filter = PathFilter::new(include, exclude)?;
let out_file = File::create(output_zip)?;
let mut writer = ZipWriter::new(out_file);
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
let mut files_added = 0usize;
let mut dirs_added = 0usize;
if input.is_file() {
let base = input
.file_name()
.and_then(|x| x.to_str())
.ok_or_else(|| XzipError::InvalidInput(input.to_path_buf()))?;
let zip_path = Path::new(base);
let size = add_file(&mut writer, input, zip_path, options, encoding, verbosity)?;
files_added += 1;
if verbosity.is_extra() {
eprintln!(
"adding: {} ({} bytes, {})",
normalize_zip_path(zip_path),
size,
compression_label(CompressionMethod::Deflated)
);
}
} else {
for entry in WalkDir::new(input) {
let entry = entry.map_err(|e| std::io::Error::other(e.to_string()))?;
let path = entry.path();
if path == input {
continue;
}
if !recursive && path.parent() != Some(input) {
continue;
}
let relative = path
.strip_prefix(input)
.map_err(|_| XzipError::InvalidInput(input.to_path_buf()))?;
let zip_path = normalize_zip_path(relative);
if !filter.allows(&zip_path) {
continue;
}
if entry.file_type().is_dir() {
writer.add_directory(&zip_path, options)?;
dirs_added += 1;
if verbosity.is_verbose() {
eprintln!("adding: {zip_path}");
}
} else if entry.file_type().is_file() {
let size = add_file(
&mut writer,
path,
Path::new(&zip_path),
options,
encoding,
verbosity,
)?;
files_added += 1;
if verbosity.is_extra() {
eprintln!(
"adding: {zip_path} ({size} bytes, {})",
compression_label(CompressionMethod::Deflated)
);
}
}
}
}
writer.finish()?;
if verbosity.is_verbose() {
let elapsed = started.elapsed();
let file_label = if files_added == 1 { "file" } else { "files" };
eprintln!(
"{files_added} {file_label} added ({dirs_added} directories) in {:.2}s",
elapsed.as_secs_f64()
);
}
Ok(())
}
fn add_file<W: Write + std::io::Seek>(
writer: &mut ZipWriter<W>,
src_path: &Path,
zip_relative_path: &Path,
options: SimpleFileOptions,
encoding: EncodingKind,
verbosity: Verbosity,
) -> Result<u64, XzipError> {
let display_name = normalize_zip_path(zip_relative_path);
let encoded = encoding.encode(&display_name)?;
let normalized_name = encoding.decode(&encoded)?;
if verbosity.is_verbose() && !verbosity.is_extra() {
eprintln!("adding: {display_name}");
}
writer.start_file(normalized_name, options)?;
let mut input_file = File::open(src_path)?;
let mut buf = Vec::new();
input_file.read_to_end(&mut buf)?;
let size = buf.len() as u64;
writer.write_all(&buf)?;
Ok(size)
}
fn normalize_zip_path(path: &Path) -> String {
let mut normalized = PathBuf::new();
for component in path.components() {
normalized.push(component);
}
normalized.to_string_lossy().replace('\\', "/")
}