use flate2::read::GzDecoder;
use tar::Archive as TarArchive;
use zip::result::ZipError;
use zip::write::FileOptions;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use walkdir::{DirEntry, WalkDir};
use std::io::{Seek, Write};
use crate::prelude::*;
use console::style;
pub async fn extract(
file: &async_std::path::PathBuf,
dir: &async_std::path::PathBuf,
) -> Result<()> {
let file_str = file.clone().into_os_string().into_string()?;
if file_str.ends_with(".tar.gz") || file_str.ends_with(".tgz") {
extract_tar_gz(&file.into(), &dir.into())?;
} else if file_str.ends_with(".zip") {
extract_zip(&file.into(), &dir.into()).await?;
} else {
return Err(format!("extract(): unsupported file type: {file_str}").into());
}
Ok(())
}
fn extract_tar_gz(file: &PathBuf, dir: &PathBuf) -> Result<()> {
let tar_gz = std::fs::File::open(file)?;
let tar = GzDecoder::new(tar_gz);
let mut archive = TarArchive::new(tar);
archive.unpack(dir)?;
Ok(())
}
async fn extract_zip(file: &PathBuf, dir: &PathBuf) -> Result<()> {
let file_reader = std::fs::File::open(file).unwrap();
let mut archive = zip::ZipArchive::new(file_reader).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let outpath = match file.enclosed_name() {
Some(path) => std::path::Path::new(dir).join(path), None => continue,
};
if (*file.name()).ends_with('/') {
std::fs::create_dir_all(&outpath).unwrap();
} else {
if let Some(p) = outpath.parent() {
if !p.exists() {
std::fs::create_dir_all(p).unwrap();
}
}
let mut outfile = std::fs::File::create(&outpath).unwrap();
std::io::copy(&mut file, &mut outfile).unwrap();
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)).unwrap();
}
}
}
Ok(())
}
fn zip_folder<T>(
nb_files: usize,
filename: &str,
_path: &Path,
it: &mut dyn Iterator<Item = DirEntry>,
prefix: &Path,
writer: T,
method: zip::CompressionMethod,
) -> Result<()>
where
T: Write + Seek,
{
let mut count: usize = 0;
let mut bytes: usize = 0;
let filename = style(filename).cyan();
let mut zip = zip::ZipWriter::new(writer);
let options = FileOptions::default()
.compression_method(method)
.unix_permissions(0o755);
let mut buffer = Vec::new();
for entry in it {
let path = entry.path();
let name = path.strip_prefix(prefix).unwrap();
if path.is_file() {
#[allow(deprecated)]
zip.start_file_from_path(name, options)?;
let mut f = File::open(path)?;
f.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
bytes += buffer.len();
buffer.clear();
} else if !name.as_os_str().is_empty() {
#[allow(deprecated)]
zip.add_directory_from_path(name, options)?;
}
count += 1;
let pos = count as f64 / nb_files as f64 * 100.0;
let percent = style(format!("{pos:1.2}%")).cyan();
let size = style(format!("{:1.2} Mb", bytes as f64 / 1024.0 / 1024.0)).cyan();
let files = style(format!("{count}/{nb_files} files")).cyan();
log_state!(
"Compressing",
"... {filename}: {percent} - {files} - {size} "
);
}
log_state_clear();
zip.finish()?;
Ok(())
}
pub fn compress_folder(
src_dir: &async_std::path::Path,
dst_file: &async_std::path::Path,
archive: Archive,
) -> Result<()> {
if !Path::new(src_dir).is_dir() {
return Err(ZipError::FileNotFound.into());
}
let algorithm = archive.algorithm.unwrap_or_default();
let subfolder = archive.subfolder.unwrap_or(true);
log_info!("Archive", "compressing ({})", algorithm.to_string());
let method: zip::CompressionMethod = algorithm.into();
let path = Path::new(dst_file);
let file = File::create(path).unwrap();
let walkdir = WalkDir::new(src_dir);
let it = walkdir.into_iter();
let mut nb_files = 0;
for _ in it {
nb_files += 1;
}
let walkdir = WalkDir::new(src_dir);
let it = walkdir.into_iter();
let prefix = if subfolder {
src_dir.parent().unwrap()
} else {
src_dir
};
zip_folder(
nb_files,
dst_file.file_name().unwrap().to_str().unwrap(),
path,
&mut it.filter_map(|e| e.ok()),
prefix.into(),
file,
method,
)?;
Ok(())
}