use std::{
io::{self, Read},
path::{Path, PathBuf},
};
use crate::{
error::ArchiveError,
format::{self, ArchiveFormat},
};
pub struct Archive {
path: PathBuf,
format: ArchiveFormat,
}
impl Archive {
pub async 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).await?;
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 async 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).await
}
}
pub async fn extract_archive<P: AsRef<Path>>(
archive_path: P,
output_dir: P,
) -> Result<(), ArchiveError> {
let archive = Archive::open(archive_path).await?;
archive.extract_to(output_dir).await
}
async 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() {
tokio::fs::create_dir_all(output_dir).await?;
}
match format {
ArchiveFormat::Zip => extract_zip(path, output_dir).await,
ArchiveFormat::TarGz => extract_tar(path, output_dir, flate2::read::GzDecoder::new).await,
ArchiveFormat::TarXz => extract_tar(path, output_dir, xz2::read::XzDecoder::new).await,
ArchiveFormat::TarBz2 => extract_tar(path, output_dir, bzip2::read::BzDecoder::new).await,
ArchiveFormat::TarZst => {
extract_tar(path, output_dir, |f| {
zstd::stream::read::Decoder::new(f).unwrap()
})
.await
}
ArchiveFormat::Tar => unimplemented!(),
ArchiveFormat::SevenZ => unimplemented!(),
ArchiveFormat::Rar => unimplemented!(),
}
}
async fn extract_tar<F, R>(
path: &Path,
output_dir: &Path,
decompress: 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 decompressed = decompress(file);
let mut archive = tar::Archive::new(decompressed);
archive.unpack(&output_dir)?;
Ok(())
}
async 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() {
if !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(())
}