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 more methods used in cargo-dist #38

Merged
merged 7 commits into from
Apr 27, 2023
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ toml = { version = "0.5.9", optional = true }
serde_json = { version = "1.0.95", optional = true }
serde = { version = "1.0.159", optional = true, features = ["derive"] }
camino = "1.1.4"
tar = "0.4.38"
zip = "0.6.4"
flate2 = "1.0.25"
xz2 = "0.1.7"

[dev-dependencies]
assert_fs = "1"
Expand Down
226 changes: 226 additions & 0 deletions src/compression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
//! Compression-related methods, all used in `axoasset::Local`

use crate::error::*;
use camino::Utf8Path;
use flate2::{write::ZlibEncoder, Compression, GzBuilder};
use std::{
fs::{self, DirEntry},
io::BufReader,
};
use xz2::write::XzEncoder;
use zip::ZipWriter;

/// Internal tar-file compression algorithms
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum CompressionImpl {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it'd be best to expose all possible methods, while at the same time keeping a crate-internal duplicate of cargo-dists CompressionImpl so we can carry over the function code mostly unmodified (with the exception of the error handling)

/// .gz
Gzip,
/// .xz
Xzip,
/// .zstd
Zstd,
}

pub(crate) fn tar_dir(
src_path: &Utf8Path,
dest_path: &Utf8Path,
compression: &CompressionImpl,
) -> Result<()> {
// Set up the archive/compression
// The contents of the zip (e.g. a tar)
let dir_name = src_path.file_name().unwrap();
let zip_contents_name = format!("{dir_name}.tar");
let final_zip_file = match fs::File::create(dest_path) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a spot where we could use LocalAsset::write_new?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure, since we're just creating the file and not initially writing anything to it, before passing it off to the various buffer writers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we could- and it would help with debugging as the error that throws is the write new error and so using the associated function i think makes sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, if anything, i'd rather add a new write_and_open method or something, since write_new doesn't actually return a File (which is what we need here). either that or we use write_new and then do File::open afterwards

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh got it, sorry, haven't had any coffee yet today lol. i like the write and open idea but we could file that as an issue and not block on it

Ok(file) => file,
Err(details) => {
return Err(AxoassetError::LocalAssetWriteNewFailed {
dest_path: dest_path.to_string(),
details,
})
}
};

match compression {
CompressionImpl::Gzip => {
// Wrap our file in compression
let zip_output = GzBuilder::new()
.filename(zip_contents_name)
.write(final_zip_file, Compression::default());

// Write the tar to the compression stream
let mut tar = tar::Builder::new(zip_output);

// Add the whole dir to the tar
if let Err(details) = tar.append_dir_all(dir_name, src_path) {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to copy directory into tar: {src_path} => {dir_name}",),
details,
});
}
// Finish up the tarring
let zip_output = match tar.into_inner() {
Ok(out) => out,
Err(details) => {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to write tar: {dest_path}"),
details,
})
}
};
// Finish up the compression
let _zip_file = match zip_output.finish() {
Ok(file) => file,
Err(details) => {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to write archive: {dest_path}"),
details,
})
}
};
// Drop the file to close it
}
CompressionImpl::Xzip => {
let zip_output = XzEncoder::new(final_zip_file, 9);
// Write the tar to the compression stream
let mut tar = tar::Builder::new(zip_output);

// Add the whole dir to the tar
if let Err(details) = tar.append_dir_all(dir_name, src_path) {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to copy directory into tar: {src_path} => {dir_name}",),
details,
});
}
// Finish up the tarring
let zip_output = match tar.into_inner() {
Ok(out) => out,
Err(details) => {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to write tar: {dest_path}"),
details,
})
}
};
// Finish up the compression
let _zip_file = match zip_output.finish() {
Ok(file) => file,
Err(details) => {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to write archive: {dest_path}"),
details,
})
}
};
// Drop the file to close it
}
CompressionImpl::Zstd => {
// Wrap our file in compression
let zip_output = ZlibEncoder::new(final_zip_file, Compression::default());

// Write the tar to the compression stream
let mut tar = tar::Builder::new(zip_output);

// Add the whole dir to the tar
if let Err(details) = tar.append_dir_all(dir_name, src_path) {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to copy directory into tar: {src_path} => {dir_name}",),
details,
});
}
// Finish up the tarring
let zip_output = match tar.into_inner() {
Ok(out) => out,
Err(details) => {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to write tar: {dest_path}"),
details,
})
}
};
// Finish up the compression
let _zip_file = match zip_output.finish() {
Ok(file) => file,
Err(details) => {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to write archive: {dest_path}"),
details,
})
}
};
// Drop the file to close it
}
}

Ok(())
}

pub(crate) fn zip_dir(src_path: &Utf8Path, dest_path: &Utf8Path) -> Result<()> {
// Set up the archive/compression
let final_zip_file = match fs::File::create(dest_path) {
Ok(file) => file,
Err(details) => {
return Err(AxoassetError::LocalAssetWriteNewFailed {
dest_path: dest_path.to_string(),
details,
})
}
};

// Wrap our file in compression
let mut zip = ZipWriter::new(final_zip_file);

let dir = match std::fs::read_dir(src_path) {
Ok(dir) => dir,
Err(details) => {
return Err(AxoassetError::LocalAssetReadFailed {
origin_path: src_path.to_string(),
details,
})
}
};

for entry in dir {
if let Err(details) = copy_into_zip(entry, &mut zip) {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to create file in zip: {dest_path}"),
details,
});
}
}

// Finish up the compression
let _zip_file = match zip.finish() {
Ok(file) => file,
Err(details) => {
return Err(AxoassetError::LocalAssetArchive {
reason: format!("failed to write archive: {dest_path}"),
details: details.into(),
})
}
};
// Drop the file to close it
Ok(())
}

/// Copies a file into a provided `ZipWriter`. Mostly factored out so that we can bunch up
/// a bunch of `std::io::Error`s without having to individually handle them.
fn copy_into_zip(
entry: std::result::Result<DirEntry, std::io::Error>,
zip: &mut ZipWriter<fs::File>,
) -> std::result::Result<(), std::io::Error> {
shadows-withal marked this conversation as resolved.
Show resolved Hide resolved
let entry = entry?;
if entry.file_type()?.is_file() {
let options =
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
let file = fs::File::open(entry.path())?;
let mut buf = BufReader::new(file);
let file_name = entry.file_name();
// FIXME: ...don't do this lossy conversion?
let utf8_file_name = file_name.to_string_lossy();
zip.start_file(utf8_file_name.clone(), options)?;
std::io::copy(&mut buf, zip)?;
} else {
todo!("implement zip subdirs! (or was this a symlink?)");
shadows-withal marked this conversation as resolved.
Show resolved Hide resolved
}
Ok(())
}
44 changes: 40 additions & 4 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub enum AxoassetError {
},

/// This error indicates that the mime type of the requested remote asset
/// was not an image.
/// was not an image.
#[error("when fetching asset at {origin_path}, the server's response mime type did not indicate an image.")]
#[help(
"Please make sure the asset url is correct and that the server is properly configured."
Expand Down Expand Up @@ -222,9 +222,7 @@ pub enum AxoassetError {

/// This error indicates that axoasset failed to write a new asset
#[error("failed to write a new asset to {dest_path}.")]
#[diagnostic(help(
"Make sure your destination path is relative to your oranda config or project manifest file."
))]
#[diagnostic(help("Make sure you have the correct permissons to create a new file."))]
LocalAssetWriteNewFailed {
/// The path where the asset was being written to
dest_path: String,
Expand All @@ -233,6 +231,30 @@ pub enum AxoassetError {
details: std::io::Error,
},

/// This error indicates that axoasset failed to create a new directory
#[error("failed to write a new directory to {dest_path}.")]
#[diagnostic(help("Make sure you have the correct permissions to create a new directory."))]
LocalAssetDirCreationFailed {
/// The path where the directory was meant to be created
dest_path: String,
/// Details of the error
#[source]
details: std::io::Error,
},

/// This error indicates that axoasset failed to delete an asset
#[error("failed to delete asset at {dest_path}.")]
#[diagnostic(help(
"Make sure your path is relative to your oranda config or project manifest file."
ashleygwilliams marked this conversation as resolved.
Show resolved Hide resolved
))]
LocalAssetRemoveFailed {
/// The path that was going to be deleted
dest_path: String,
/// Details of the error
#[source]
details: std::io::Error,
},

/// This error indicates that axoasset could not determine the filename for
/// a local asset.
#[error("could not determine file name for asset at {origin_path}")]
Expand All @@ -243,6 +265,20 @@ pub enum AxoassetError {
/// The origin path of the asset, used as an identifier
origin_path: String,
},

/// This error indicates we ran into an issue when creating an archive.
#[error("failed to create archive: {reason}")]
#[diagnostic(help(
ashleygwilliams marked this conversation as resolved.
Show resolved Hide resolved
"Make sure your path is relative to your oranda config or project manifest file."
))]
LocalAssetArchive {
/// A specific step that failed
reason: String,
/// Details of the error
#[source]
details: std::io::Error,
},

/// This error indicates we ran `std::env::current_dir` and somehow got an error.
#[error("Failed to get the current working directory")]
CurrentDir {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use std::path::PathBuf;

pub(crate) mod compression;
pub(crate) mod error;
pub(crate) mod local;
#[cfg(feature = "remote")]
Expand Down
Loading