use std::{
io::{self, Read},
path::{Path, PathBuf},
};
use crate::{
error::ArchiveError,
format::{self, ArchiveFormat},
};
pub struct Archive {
pub path: PathBuf,
pub format: ArchiveFormat,
}
impl Archive {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, ArchiveError> {
let path = path.as_ref().to_path_buf();
let format = format::detect_from_file(&path)?;
Ok(Archive {
path,
format,
})
}
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, ArchiveError> {
let path = path.as_ref().to_path_buf();
let format = format::detect_from_extension(&path)?;
Ok(Archive {
path,
format,
})
}
pub fn extract_to<P: AsRef<Path>>(&self, output_dir: P) -> Result<(), ArchiveError> {
let output_dir = output_dir.as_ref();
extract_archive_with_format(self.path.as_ref(), output_dir, self.format)
}
}
pub fn extract_archive<P: AsRef<Path>>(archive_path: P, output_dir: P) -> Result<(), ArchiveError> {
let archive = Archive::open(archive_path)?;
archive.extract_to(output_dir)
}
fn extract_archive_with_format<P: AsRef<Path>>(
path: P,
output_dir: P,
format: ArchiveFormat,
) -> Result<(), ArchiveError> {
let path = path.as_ref();
let output_dir = output_dir.as_ref();
if !output_dir.exists() {
std::fs::create_dir_all(output_dir)?;
}
match format {
ArchiveFormat::Zip => extract_zip(path, output_dir),
ArchiveFormat::TarGz => extract_tar(path, output_dir, flate2::read::GzDecoder::new),
ArchiveFormat::TarXz => extract_tar(path, output_dir, liblzma::read::XzDecoder::new),
ArchiveFormat::TarBz2 => extract_tar(path, output_dir, bzip2::read::BzDecoder::new),
ArchiveFormat::TarZst => {
extract_tar(path, output_dir, |f| {
zstd::stream::read::Decoder::new(f).unwrap()
})
}
ArchiveFormat::Tar => extract_tar(path, output_dir, |f| f),
ArchiveFormat::SevenZ => extract_7z(path, output_dir),
ArchiveFormat::Gz => extract_single(path, output_dir, flate2::read::GzDecoder::new),
ArchiveFormat::Xz => extract_single(path, output_dir, liblzma::read::XzDecoder::new),
ArchiveFormat::Bz2 => extract_single(path, output_dir, bzip2::read::BzDecoder::new),
ArchiveFormat::Zst => {
extract_single(path, output_dir, |f| {
zstd::stream::read::Decoder::new(f).unwrap()
})
}
}
}
fn extract_single<F, R>(path: &Path, output_dir: &Path, decode: F) -> Result<(), ArchiveError>
where
F: FnOnce(std::fs::File) -> R,
R: Read,
{
let stem = path
.file_stem()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("output"));
let dest = output_dir.join(stem);
let file = std::fs::File::open(path)?;
let mut reader = decode(file);
let mut out = std::fs::File::create(&dest)?;
io::copy(&mut reader, &mut out)?;
Ok(())
}
fn extract_tar<F, R>(path: &Path, output_dir: &Path, decode: F) -> Result<(), ArchiveError>
where
F: FnOnce(std::fs::File) -> R + Send + 'static,
R: Read + Send + 'static,
{
let path = path.to_path_buf();
let output_dir = output_dir.to_path_buf();
let file = std::fs::File::open(&path)?;
let reader = decode(file);
let mut archive = tar::Archive::new(reader);
archive.unpack(&output_dir)?;
Ok(())
}
fn extract_zip(path: &Path, output_dir: &Path) -> Result<(), ArchiveError> {
let path = path.to_path_buf();
let output_dir = output_dir.to_path_buf();
let file = std::fs::File::open(&path)?;
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.name());
if file.name().ends_with('/') {
std::fs::create_dir_all(&out_path)?;
} else {
if let Some(p) = out_path.parent()
&& !p.exists()
{
std::fs::create_dir_all(p)?;
}
let mut out_file = std::fs::File::create(&out_path)?;
io::copy(&mut file, &mut out_file)?;
}
}
Ok(())
}
fn extract_7z(path: &Path, output_dir: &Path) -> Result<(), ArchiveError> {
let path = path.to_path_buf();
let output_dir = output_dir.to_path_buf();
Ok(sevenz_rust2::decompress_file(path, output_dir)?)
}