use crate::{Error, Result};
use camino::Utf8Path;
use simple_fs::SPath;
use std::fs::{self, File};
use std::io::{self, Read as _};
use walkdir::WalkDir;
use zip::ZipWriter;
use zip::write::SimpleFileOptions;
use zip::{CompressionMethod, ZipArchive};
pub fn zip_dir(src_dir: impl AsRef<Utf8Path>, dest_file: impl AsRef<Utf8Path>) -> Result<()> {
let src_dir = src_dir.as_ref();
let dest_file = dest_file.as_ref();
let file = File::create(dest_file.as_std_path())?;
let mut zip = ZipWriter::new(file);
let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated);
for entry in WalkDir::new(src_dir.as_std_path()) {
let entry = entry.map_err(|err| Error::ZipFail {
zip_dir: src_dir.to_string(),
cause: format!("Fail to zip directory. Error on entry. Cause {err}"),
})?;
let Some(path) = Utf8Path::from_path(entry.path()) else {
continue;
};
let relative_path = path.strip_prefix(src_dir).map_err(|err| Error::ZipFail {
zip_dir: src_dir.to_string(),
cause: format!("Fail strip_prefix '{src_dir}' on '{path}'. Cause {err}"),
})?;
let name = relative_path.as_str().replace("\\", "/");
if path.is_dir() {
if !name.is_empty() {
let dir_name = if name.ends_with('/') {
name.to_string()
} else {
format!("{}/", name)
};
zip.add_directory(&dir_name, options).map_err(|err| Error::ZipFail {
zip_dir: src_dir.to_string(),
cause: format!("Fail add directory '{dir_name}'. Cause {err}"),
})?;
}
} else {
zip.start_file(&name, options).map_err(|err| Error::ZipFail {
zip_dir: src_dir.to_string(),
cause: format!("Fail zip.start_file '{name}'. Cause {err}"),
})?;
let mut f = File::open(path)?;
io::copy(&mut f, &mut zip)?;
}
}
zip.finish().map_err(|err| Error::ZipFail {
zip_dir: src_dir.to_string(),
cause: format!("Fail zip.finish '{src_dir}'. Cause {err}"),
})?;
Ok(())
}
pub fn unzip_file(src_zip: impl AsRef<Utf8Path>, dest_dir: impl AsRef<Utf8Path>) -> Result<()> {
let src_zip = src_zip.as_ref();
let dest_dir = dest_dir.as_ref();
let file = File::open(src_zip.as_std_path())?;
let mut archive = ZipArchive::new(file).map_err(|err| Error::UnzipZipFail {
zip_file: src_zip.to_string(),
cause: format!("Fail to create new archive. Cause: {err}"),
})?;
for i in 0..archive.len() {
let mut file = archive.by_index(i).map_err(|err| Error::UnzipZipFail {
zip_file: src_zip.to_string(),
cause: format!("Fail to get item by_index {i}. Cause: {err}"),
})?;
let outpath = dest_dir.join(file.name());
if file.name().ends_with('/') {
fs::create_dir_all(outpath.as_std_path())?;
} else {
if let Some(parent) = outpath.parent() {
fs::create_dir_all(parent.as_std_path())?;
}
let mut outfile = File::create(outpath.as_std_path())?;
io::copy(&mut file, &mut outfile)?;
}
}
Ok(())
}
pub fn extract_text_content(src_zip_path: impl AsRef<SPath>, content_path: &str) -> Result<String> {
let src_zip_path = src_zip_path.as_ref();
let file = File::open(src_zip_path)?;
let mut archive = ZipArchive::new(file).map_err(|err| Error::Zip {
zip_file: src_zip_path.name().to_string(),
cause: err.to_string(),
})?;
let mut file = archive.by_name(content_path).map_err(|_| Error::ZipFileNotFound {
zip_file: src_zip_path.name().to_string(),
content_path: content_path.to_string(),
})?;
let mut data: Vec<u8> = Vec::new();
file.read_to_end(&mut data)?;
let content = String::from_utf8(data).map_err(|err| Error::ZipContent {
zip_file: src_zip_path.name().to_string(),
content_path: content_path.to_string(),
cause: format!("Not utf8. Info: {err}"),
})?;
Ok(content)
}