Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add zip/tarball decompression #84

Merged
merged 1 commit into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 111 additions & 1 deletion src/compression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ pub(crate) fn tar_dir(
// Wrap our file in compression
let zip_output = ZstdEncoder::new(final_zip_file, 0).map_err(|details| {
AxoassetError::LocalAssetArchive {
reason: format!("failed to create zstd encoder"),
reason: "failed to create zstd encoder".to_string(),
details,
}
})?;
Expand Down Expand Up @@ -162,6 +162,80 @@ pub(crate) fn tar_dir(
Ok(())
}

#[cfg(feature = "compression-tar")]
fn open_tarball(
tarball: &Utf8Path,
compression: &CompressionImpl,
) -> crate::error::Result<Vec<u8>> {
use std::io::Read;

use flate2::read::GzDecoder;
use xz2::read::XzDecoder;
use zstd::stream::Decoder as ZstdDecoder;

use crate::LocalAsset;

let source = LocalAsset::load_bytes(tarball)?;
let mut tarball_bytes = vec![];

match compression {
CompressionImpl::Gzip => {
let mut decoder = GzDecoder::new(source.as_slice());
decoder.read_to_end(&mut tarball_bytes)?;
}
CompressionImpl::Xzip => {
let mut decoder = XzDecoder::new(source.as_slice());
decoder.read_to_end(&mut tarball_bytes)?;
}
CompressionImpl::Zstd => {
let mut decoder = ZstdDecoder::new(source.as_slice())?;
decoder.read_to_end(&mut tarball_bytes)?;
}
};

Ok(tarball_bytes)
}

#[cfg(feature = "compression-tar")]
pub(crate) fn untar_all(
tarball: &Utf8Path,
dest_path: &Utf8Path,
compression: &CompressionImpl,
) -> crate::error::Result<()> {
let tarball_bytes = open_tarball(tarball, compression)?;
let mut tarball = tar::Archive::new(tarball_bytes.as_slice());
tarball.unpack(dest_path)?;

Ok(())
}

#[cfg(feature = "compression-tar")]
pub(crate) fn untar_file(
tarball: &Utf8Path,
filename: &str,
compression: &CompressionImpl,
) -> crate::error::Result<Vec<u8>> {
use std::io::Read;

let tarball_bytes = open_tarball(tarball, compression)?;
let mut tarball = tar::Archive::new(tarball_bytes.as_slice());
for entry in tarball.entries()? {
let mut entry = entry?;
if let Some(name) = entry.path()?.file_name() {
if name == filename {
let mut buf = vec![];
entry.read_to_end(&mut buf)?;

return Ok(buf);
}
}
}

Err(crate::AxoassetError::ExtractFilenameFailed {
desired_filename: filename.to_owned(),
})
}

#[cfg(feature = "compression-zip")]
pub(crate) fn zip_dir(
src_path: &Utf8Path,
Expand Down Expand Up @@ -223,3 +297,39 @@ pub(crate) fn zip_dir(
zip.finish()?;
Ok(())
}

#[cfg(feature = "compression-zip")]
pub(crate) fn unzip_all(zipfile: &Utf8Path, dest_path: &Utf8Path) -> crate::error::Result<()> {
use std::io::Cursor;

use crate::LocalAsset;

let source = LocalAsset::load_bytes(zipfile)?;
let seekable = Cursor::new(source);
let mut archive = zip::ZipArchive::new(seekable)?;
archive.extract(&dest_path)?;

Ok(())
}

#[cfg(feature = "compression-zip")]
pub(crate) fn unzip_file(zipfile: &Utf8Path, filename: &str) -> crate::error::Result<Vec<u8>> {
use std::io::{Cursor, Read};

use crate::LocalAsset;

let source = LocalAsset::load_bytes(zipfile)?;
let seekable = Cursor::new(source);
let mut archive = zip::ZipArchive::new(seekable)?;
let mut file =
archive
.by_name(&filename)
.map_err(|_| crate::AxoassetError::ExtractFilenameFailed {
desired_filename: filename.to_owned(),
})?;

let mut buf = vec![];
file.read_to_end(&mut buf)?;

Ok(buf)
}
18 changes: 18 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ pub enum AxoassetError {
#[error(transparent)]
MimeParseParse(#[from] mime::FromStrError),

/// This error is a transparent error forwarded from the flate2 library.
/// This error indicates that an error of some kind occurred while performing io.
#[error(transparent)]
Io(#[from] std::io::Error),

/// This error is a transparent error forwarded from the flate2 library.
/// This error indicates that an error of some kind occurred while opening a ZIP file.
#[error(transparent)]
#[cfg(feature = "compression-zip")]
Zip(#[from] zip::result::ZipError),

/// This error indicates that axoasset was asked to create a new remote
/// asset, likely by being given an path that starts with http or https.
/// Axoasset can only create new assets on the file system.
Expand Down Expand Up @@ -291,6 +302,13 @@ pub enum AxoassetError {
desired_filename: String,
},

#[error("Failed to find {desired_filename} within archive being decompressed")]
/// This error indicates we failed to find the desired file within a tarball or zip
ExtractFilenameFailed {
/// The filename we were searching for
desired_filename: String,
},

#[error("Failed to walk to ancestor of {origin_path}")]
/// Walkdir failed to yield an entry
WalkDirFailed {
Expand Down
81 changes: 81 additions & 0 deletions src/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,26 @@ impl LocalAsset {
)
}

/// Extracts the entire tarball at `tarball` to a provided directory
#[cfg(any(feature = "compression", feature = "compression-tar"))]
pub fn untar_gz_all(tarball: &Utf8Path, dest_path: &Utf8Path) -> Result<()> {
crate::compression::untar_all(
tarball,
dest_path,
&crate::compression::CompressionImpl::Gzip,
)
}

/// Extracts the file named `filename` within the tarball at `tarball` and returns its contents as bytes
#[cfg(any(feature = "compression", feature = "compression-tar"))]
pub fn untar_gz_file(tarball: &Utf8Path, filename: &str) -> Result<Vec<u8>> {
crate::compression::untar_file(
tarball,
filename,
&crate::compression::CompressionImpl::Gzip,
)
}

/// Creates a new .tar.xz file from a provided directory
///
/// The with_root argument specifies that all contents of dest_dir should be placed
Expand All @@ -401,6 +421,29 @@ impl LocalAsset {
)
}

/// Extracts the entire tarball at `tarball` to a provided directory
#[cfg(any(feature = "compression", feature = "compression-tar"))]
pub fn untar_xz_all(
tarball: impl AsRef<Utf8Path>,
dest_path: impl AsRef<Utf8Path>,
) -> Result<()> {
crate::compression::untar_all(
Utf8Path::new(tarball.as_ref()),
Utf8Path::new(dest_path.as_ref()),
&crate::compression::CompressionImpl::Xzip,
)
}

/// Extracts the file named `filename` within the tarball at `tarball` and returns its contents as bytes
#[cfg(any(feature = "compression", feature = "compression-tar"))]
pub fn untar_xz_file(tarball: impl AsRef<Utf8Path>, filename: &str) -> Result<Vec<u8>> {
crate::compression::untar_file(
Utf8Path::new(tarball.as_ref()),
filename,
&crate::compression::CompressionImpl::Xzip,
)
}

/// Creates a new .tar.zstd file from a provided directory
///
/// The with_root argument specifies that all contents of dest_dir should be placed
Expand All @@ -421,6 +464,29 @@ impl LocalAsset {
)
}

/// Extracts the entire tarball at `tarball` to a provided directory
#[cfg(any(feature = "compression", feature = "compression-tar"))]
pub fn untar_zstd_all(
tarball: impl AsRef<Utf8Path>,
dest_path: impl AsRef<Utf8Path>,
) -> Result<()> {
crate::compression::untar_all(
Utf8Path::new(tarball.as_ref()),
Utf8Path::new(dest_path.as_ref()),
&crate::compression::CompressionImpl::Zstd,
)
}

/// Extracts the file named `filename` within the tarball at `tarball` and returns its contents as bytes
#[cfg(any(feature = "compression", feature = "compression-tar"))]
pub fn untar_zstd_file(tarball: impl AsRef<Utf8Path>, filename: &str) -> Result<Vec<u8>> {
crate::compression::untar_file(
Utf8Path::new(tarball.as_ref()),
filename,
&crate::compression::CompressionImpl::Zstd,
)
}

/// Creates a new .zip file from a provided directory
///
/// The with_root argument specifies that all contents of dest_dir should be placed
Expand All @@ -443,4 +509,19 @@ impl LocalAsset {
details: e.into(),
})
}

/// Extracts a .zip file to the a provided directory
#[cfg(any(feature = "compression", feature = "compression-zip"))]
pub fn unzip_all(zipfile: impl AsRef<Utf8Path>, dest_dir: impl AsRef<Utf8Path>) -> Result<()> {
crate::compression::unzip_all(
Utf8Path::new(zipfile.as_ref()),
Utf8Path::new(dest_dir.as_ref()),
)
}

/// Extracts the file named `filename` within the ZIP file at `zipfile` and returns its contents as bytes
#[cfg(any(feature = "compression", feature = "compression-zip"))]
pub fn unzip_file(zipfile: impl AsRef<Utf8Path>, filename: &str) -> Result<Vec<u8>> {
crate::compression::unzip_file(Utf8Path::new(zipfile.as_ref()), filename)
}
}
Loading