Skip to content

Commit

Permalink
Merge pull request #84 from axodotdev/decompress
Browse files Browse the repository at this point in the history
feat: add zip/tarball decompression
  • Loading branch information
mistydemeo authored Mar 5, 2024
2 parents 6c743fd + 6869b94 commit d555b43
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 1 deletion.
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)
}
}

0 comments on commit d555b43

Please sign in to comment.