Skip to content

Commit

Permalink
feat(copy): factor out a WalkDirs wrapper and clean up copy code
Browse files Browse the repository at this point in the history
  • Loading branch information
Gankra committed Aug 8, 2023
1 parent a7c8cf3 commit cf2a9f9
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 47 deletions.
15 changes: 4 additions & 11 deletions src/compression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,14 @@ pub(crate) fn zip_dir(
fs::File,
io::{Read, Write},
};
use zip::{result::ZipError, write::FileOptions, CompressionMethod};
use zip::{write::FileOptions, CompressionMethod};

let file = File::create(dest_path)?;

// The `zip` crate lacks the conveniences of the `tar` crate so we need to manually
// walk through all the subdirs of `src_path` and copy each entry. walkdir streamlines
// that process for us.
let walkdir = walkdir::WalkDir::new(src_path);
let walkdir = crate::dirs::walk_dir(src_path);
let it = walkdir.into_iter();

let mut zip = zip::ZipWriter::new(file);
Expand All @@ -190,15 +190,8 @@ pub(crate) fn zip_dir(

let mut buffer = Vec::new();
for entry in it.filter_map(|e| e.ok()) {
let path = entry.path();
// Get the relative path of this file/dir that will be used in the zip
let Some(name) = path
.strip_prefix(src_path)
.ok()
.and_then(Utf8Path::from_path)
else {
return Err(ZipError::UnsupportedArchive("unsupported path format"));
};
let name = &entry.rel_path;
let path = &entry.full_path;
// Optionally apply the root prefix
let name = if let Some(root) = with_root {
root.join(name)
Expand Down
20 changes: 20 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,16 @@ pub enum AxoassetError {
/// The problematic path
path: std::path::PathBuf,
},
/// This error indicates we tried to strip_prefix a path that should have been
/// a descendant of another, but it didn't work.
#[error("Child wasn't nested under its parent: {root_dir} => {child_dir}")]
#[diagnostic(help("Are symlinks involved?"))]
PathNesting {
/// The root/ancestor dir
root_dir: camino::Utf8PathBuf,
/// THe child/descendent path
child_dir: camino::Utf8PathBuf,
},

#[error("Failed to find {desired_filename} in an ancestor of {start_dir}")]
/// This error indicates we failed to find the desired file in an ancestor of the search dir.
Expand All @@ -302,6 +312,16 @@ pub enum AxoassetError {
desired_filename: String,
},

#[error("Failed to walk to ancestor of {origin_path}")]
/// Walkdir failed to yield an entry
WalkDirFailed {
/// The root path we were trying to walkdirs
origin_path: camino::Utf8PathBuf,
/// Inner walkdir error
#[source]
details: walkdir::Error,
},

/// This error indicates we tried to deserialize some JSON with serde_json
/// but failed.
#[cfg(feature = "json-serde")]
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ use std::path::PathBuf;

#[cfg(any(feature = "compression-zip", feature = "compression-tar"))]
pub(crate) mod compression;
pub(crate) mod dirs;
pub(crate) mod error;
pub(crate) mod local;
#[cfg(feature = "remote")]
pub(crate) mod remote;
pub(crate) mod source;
pub(crate) mod spanned;

use camino::Utf8PathBuf;
pub use error::AxoassetError;
use error::Result;
pub use local::LocalAsset;
Expand Down Expand Up @@ -83,7 +85,7 @@ impl Asset {

/// Copies an asset, returning the path to the copy destination on the
/// local filesystem.
pub async fn copy(origin_path: &str, dest_dir: &str) -> Result<PathBuf> {
pub async fn copy(origin_path: &str, dest_dir: &str) -> Result<Utf8PathBuf> {
#[cfg(feature = "remote")]
if is_remote(origin_path)? {
return RemoteAsset::copy(origin_path, dest_dir).await;
Expand Down
52 changes: 20 additions & 32 deletions src/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};

use camino::{Utf8Path, Utf8PathBuf};

use crate::error::*;
use crate::{dirs, error::*};

/// A local asset contains a path on the local filesystem and its contents
#[derive(Debug)]
Expand Down Expand Up @@ -216,11 +216,20 @@ impl LocalAsset {
///
/// The destination will use the same file name as the origin has.
/// If you want to specify the destination file's name, use [`LocalAsset::copy_named`][].
///
/// The returned path is the resulting file.
pub fn copy(
origin_path: impl AsRef<Utf8Path>,
dest_dir: impl AsRef<Utf8Path>,
) -> Result<PathBuf> {
LocalAsset::load(origin_path)?.write(dest_dir)
) -> Result<Utf8PathBuf> {
let origin_path = origin_path.as_ref();
let dest_dir = dest_dir.as_ref();

let filename = Self::filename(origin_path)?;
let dest_path = dest_dir.join(filename);
Self::copy_named(origin_path, &dest_path)?;

Ok(dest_path)
}

/// Copies an asset from one location on the local filesystem to another
Expand All @@ -247,6 +256,8 @@ impl LocalAsset {
/// The destination will use the same dir name as the origin has, so
/// dest_dir is the *parent* of the copied directory. If you want to specify the destination's
/// dir name, use [`LocalAsset::copy_dir_named`][].
///
/// The returned path is the resulting dir.
pub fn copy_dir(
origin_path: impl AsRef<Utf8Path>,
dest_dir: impl AsRef<Utf8Path>,
Expand All @@ -257,6 +268,7 @@ impl LocalAsset {
let filename = Self::filename(origin_path)?;
let dest_path = dest_dir.join(filename);
Self::copy_dir_named(origin_path, &dest_path)?;

Ok(dest_path)
}

Expand All @@ -271,38 +283,14 @@ impl LocalAsset {
let origin_path = origin_path.as_ref();
let dest_path = dest_path.as_ref();

for entry in walkdir::WalkDir::new(origin_path) {
let entry = entry.map_err(|e| AxoassetError::LocalAssetCopyFailed {
origin_path: origin_path.to_string(),
dest_path: dest_path.to_string(),
details: e.into_io_error(),
})?;

let from = Utf8PathBuf::from_path_buf(entry.path().to_owned())
.map_err(|details| AxoassetError::Utf8Path { path: details })?;
let suffix = from.strip_prefix(origin_path).map_err(|_| {
AxoassetError::LocalAssetCopyFailed {
origin_path: origin_path.to_string(),
dest_path: dest_path.to_string(),
details: None,
}
})?;
let to = dest_path.join(suffix);
for entry in dirs::walk_dir(origin_path) {
let entry = entry?;
let from = &entry.full_path;
let to = dest_path.join(&entry.rel_path);

if entry.file_type().is_dir() {
// create directories (even empty ones!)
if let Err(e) = fs::create_dir(to) {
match e.kind() {
std::io::ErrorKind::AlreadyExists => {}
_ => {
return Err(AxoassetError::LocalAssetCopyFailed {
origin_path: origin_path.to_string(),
dest_path: dest_path.to_string(),
details: Some(e),
})
}
}
}
LocalAsset::create_dir(to)?;
} else if entry.file_type().is_file() {
// copy files
LocalAsset::copy_named(from, to)?;
Expand Down
8 changes: 5 additions & 3 deletions src/remote.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::fs;
use std::path::{Path, PathBuf};

use camino::{Utf8Path, Utf8PathBuf};

use crate::error::*;

/// A remote asset is an asset that is fetched over the network.
Expand Down Expand Up @@ -60,15 +62,15 @@ impl RemoteAsset {
}

/// Copies an asset to the local filesystem.
pub async fn copy(origin_path: &str, dest_dir: &str) -> Result<PathBuf> {
pub async fn copy(origin_path: &str, dest_dir: &str) -> Result<Utf8PathBuf> {
match RemoteAsset::load(origin_path).await {
Ok(a) => {
let dest_path = Path::new(dest_dir).join(a.filename);
let dest_path = Utf8Path::new(dest_dir).join(a.filename);
match fs::write(&dest_path, a.contents) {
Ok(_) => Ok(dest_path),
Err(details) => Err(AxoassetError::RemoteAssetWriteFailed {
origin_path: origin_path.to_string(),
dest_path: dest_path.display().to_string(),
dest_path: dest_path.to_string(),
details,
}),
}
Expand Down

0 comments on commit cf2a9f9

Please sign in to comment.