Skip to content

Commit 523f243

Browse files
authored
Merge pull request #38 from axodotdev/feat/more-methods
feat: add more methods used in `cargo-dist`
2 parents ddbf846 + 893c12b commit 523f243

File tree

7 files changed

+431
-4
lines changed

7 files changed

+431
-4
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ toml = { version = "0.5.9", optional = true }
2323
serde_json = { version = "1.0.95", optional = true }
2424
serde = { version = "1.0.159", optional = true, features = ["derive"] }
2525
camino = "1.1.4"
26+
tar = "0.4.38"
27+
zip = "0.6.4"
28+
flate2 = "1.0.25"
29+
xz2 = "0.1.7"
2630

2731
[dev-dependencies]
2832
assert_fs = "1"

src/compression.rs

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
//! Compression-related methods, all used in `axoasset::Local`
2+
3+
use crate::error::*;
4+
use camino::Utf8Path;
5+
use flate2::{write::ZlibEncoder, Compression, GzBuilder};
6+
use std::{
7+
fs::{self, DirEntry},
8+
io::BufReader,
9+
};
10+
use xz2::write::XzEncoder;
11+
use zip::ZipWriter;
12+
13+
/// Internal tar-file compression algorithms
14+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
15+
pub(crate) enum CompressionImpl {
16+
/// .gz
17+
Gzip,
18+
/// .xz
19+
Xzip,
20+
/// .zstd
21+
Zstd,
22+
}
23+
24+
pub(crate) fn tar_dir(
25+
src_path: &Utf8Path,
26+
dest_path: &Utf8Path,
27+
compression: &CompressionImpl,
28+
) -> Result<()> {
29+
// Set up the archive/compression
30+
// The contents of the zip (e.g. a tar)
31+
let dir_name = src_path.file_name().unwrap();
32+
let zip_contents_name = format!("{dir_name}.tar");
33+
let final_zip_file = match fs::File::create(dest_path) {
34+
Ok(file) => file,
35+
Err(details) => {
36+
return Err(AxoassetError::LocalAssetWriteNewFailed {
37+
dest_path: dest_path.to_string(),
38+
details,
39+
})
40+
}
41+
};
42+
43+
match compression {
44+
CompressionImpl::Gzip => {
45+
// Wrap our file in compression
46+
let zip_output = GzBuilder::new()
47+
.filename(zip_contents_name)
48+
.write(final_zip_file, Compression::default());
49+
50+
// Write the tar to the compression stream
51+
let mut tar = tar::Builder::new(zip_output);
52+
53+
// Add the whole dir to the tar
54+
if let Err(details) = tar.append_dir_all(dir_name, src_path) {
55+
return Err(AxoassetError::LocalAssetArchive {
56+
reason: format!("failed to copy directory into tar: {src_path} => {dir_name}",),
57+
details,
58+
});
59+
}
60+
// Finish up the tarring
61+
let zip_output = match tar.into_inner() {
62+
Ok(out) => out,
63+
Err(details) => {
64+
return Err(AxoassetError::LocalAssetArchive {
65+
reason: format!("failed to write tar: {dest_path}"),
66+
details,
67+
})
68+
}
69+
};
70+
// Finish up the compression
71+
let _zip_file = match zip_output.finish() {
72+
Ok(file) => file,
73+
Err(details) => {
74+
return Err(AxoassetError::LocalAssetArchive {
75+
reason: format!("failed to write archive: {dest_path}"),
76+
details,
77+
})
78+
}
79+
};
80+
// Drop the file to close it
81+
}
82+
CompressionImpl::Xzip => {
83+
let zip_output = XzEncoder::new(final_zip_file, 9);
84+
// Write the tar to the compression stream
85+
let mut tar = tar::Builder::new(zip_output);
86+
87+
// Add the whole dir to the tar
88+
if let Err(details) = tar.append_dir_all(dir_name, src_path) {
89+
return Err(AxoassetError::LocalAssetArchive {
90+
reason: format!("failed to copy directory into tar: {src_path} => {dir_name}",),
91+
details,
92+
});
93+
}
94+
// Finish up the tarring
95+
let zip_output = match tar.into_inner() {
96+
Ok(out) => out,
97+
Err(details) => {
98+
return Err(AxoassetError::LocalAssetArchive {
99+
reason: format!("failed to write tar: {dest_path}"),
100+
details,
101+
})
102+
}
103+
};
104+
// Finish up the compression
105+
let _zip_file = match zip_output.finish() {
106+
Ok(file) => file,
107+
Err(details) => {
108+
return Err(AxoassetError::LocalAssetArchive {
109+
reason: format!("failed to write archive: {dest_path}"),
110+
details,
111+
})
112+
}
113+
};
114+
// Drop the file to close it
115+
}
116+
CompressionImpl::Zstd => {
117+
// Wrap our file in compression
118+
let zip_output = ZlibEncoder::new(final_zip_file, Compression::default());
119+
120+
// Write the tar to the compression stream
121+
let mut tar = tar::Builder::new(zip_output);
122+
123+
// Add the whole dir to the tar
124+
if let Err(details) = tar.append_dir_all(dir_name, src_path) {
125+
return Err(AxoassetError::LocalAssetArchive {
126+
reason: format!("failed to copy directory into tar: {src_path} => {dir_name}",),
127+
details,
128+
});
129+
}
130+
// Finish up the tarring
131+
let zip_output = match tar.into_inner() {
132+
Ok(out) => out,
133+
Err(details) => {
134+
return Err(AxoassetError::LocalAssetArchive {
135+
reason: format!("failed to write tar: {dest_path}"),
136+
details,
137+
})
138+
}
139+
};
140+
// Finish up the compression
141+
let _zip_file = match zip_output.finish() {
142+
Ok(file) => file,
143+
Err(details) => {
144+
return Err(AxoassetError::LocalAssetArchive {
145+
reason: format!("failed to write archive: {dest_path}"),
146+
details,
147+
})
148+
}
149+
};
150+
// Drop the file to close it
151+
}
152+
}
153+
154+
Ok(())
155+
}
156+
157+
pub(crate) fn zip_dir(src_path: &Utf8Path, dest_path: &Utf8Path) -> Result<()> {
158+
// Set up the archive/compression
159+
let final_zip_file = match fs::File::create(dest_path) {
160+
Ok(file) => file,
161+
Err(details) => {
162+
return Err(AxoassetError::LocalAssetWriteNewFailed {
163+
dest_path: dest_path.to_string(),
164+
details,
165+
})
166+
}
167+
};
168+
169+
// Wrap our file in compression
170+
let mut zip = ZipWriter::new(final_zip_file);
171+
172+
let dir = match std::fs::read_dir(src_path) {
173+
Ok(dir) => dir,
174+
Err(details) => {
175+
return Err(AxoassetError::LocalAssetReadFailed {
176+
origin_path: src_path.to_string(),
177+
details,
178+
})
179+
}
180+
};
181+
182+
for entry in dir {
183+
if let Err(details) = copy_into_zip(entry, &mut zip) {
184+
return Err(AxoassetError::LocalAssetArchive {
185+
reason: format!("failed to create file in zip: {dest_path}"),
186+
details,
187+
});
188+
}
189+
}
190+
191+
// Finish up the compression
192+
let _zip_file = match zip.finish() {
193+
Ok(file) => file,
194+
Err(details) => {
195+
return Err(AxoassetError::LocalAssetArchive {
196+
reason: format!("failed to write archive: {dest_path}"),
197+
details: details.into(),
198+
})
199+
}
200+
};
201+
// Drop the file to close it
202+
Ok(())
203+
}
204+
205+
/// Copies a file into a provided `ZipWriter`. Mostly factored out so that we can bunch up
206+
/// a bunch of `std::io::Error`s without having to individually handle them.
207+
fn copy_into_zip(
208+
entry: std::result::Result<DirEntry, std::io::Error>,
209+
zip: &mut ZipWriter<fs::File>,
210+
) -> std::result::Result<(), std::io::Error> {
211+
let entry = entry?;
212+
if entry.file_type()?.is_file() {
213+
let options =
214+
zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
215+
let file = fs::File::open(entry.path())?;
216+
let mut buf = BufReader::new(file);
217+
let file_name = entry.file_name();
218+
// FIXME: ...don't do this lossy conversion?
219+
let utf8_file_name = file_name.to_string_lossy();
220+
zip.start_file(utf8_file_name.clone(), options)?;
221+
std::io::copy(&mut buf, zip)?;
222+
} else {
223+
todo!("implement zip subdirs! (or was this a symlink?)");
224+
}
225+
Ok(())
226+
}

src/error.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub enum AxoassetError {
7676
},
7777

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

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

234+
/// This error indicates that axoasset failed to create a new directory
235+
#[error("failed to write a new directory to {dest_path}.")]
236+
#[diagnostic(help("Make sure you have the correct permissions to create a new directory."))]
237+
LocalAssetDirCreationFailed {
238+
/// The path where the directory was meant to be created
239+
dest_path: String,
240+
/// Details of the error
241+
#[source]
242+
details: std::io::Error,
243+
},
244+
245+
/// This error indicates that axoasset failed to delete an asset
246+
#[error("failed to delete asset at {dest_path}.")]
247+
#[diagnostic(help(
248+
"Make sure your path is relative to your oranda config or project manifest file."
249+
))]
250+
LocalAssetRemoveFailed {
251+
/// The path that was going to be deleted
252+
dest_path: String,
253+
/// Details of the error
254+
#[source]
255+
details: std::io::Error,
256+
},
257+
236258
/// This error indicates that axoasset could not determine the filename for
237259
/// a local asset.
238260
#[error("could not determine file name for asset at {origin_path}")]
@@ -243,6 +265,20 @@ pub enum AxoassetError {
243265
/// The origin path of the asset, used as an identifier
244266
origin_path: String,
245267
},
268+
269+
/// This error indicates we ran into an issue when creating an archive.
270+
#[error("failed to create archive: {reason}")]
271+
#[diagnostic(help(
272+
"Make sure your path is relative to your oranda config or project manifest file."
273+
))]
274+
LocalAssetArchive {
275+
/// A specific step that failed
276+
reason: String,
277+
/// Details of the error
278+
#[source]
279+
details: std::io::Error,
280+
},
281+
246282
/// This error indicates we ran `std::env::current_dir` and somehow got an error.
247283
#[error("Failed to get the current working directory")]
248284
CurrentDir {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
1212
use std::path::PathBuf;
1313

14+
pub(crate) mod compression;
1415
pub(crate) mod error;
1516
pub(crate) mod local;
1617
#[cfg(feature = "remote")]

0 commit comments

Comments
 (0)