diff options
author | boasting-squirrel <boasting.squirrel@gmail.com> | 2019-03-11 23:25:56 +0000 |
---|---|---|
committer | boasting-squirrel <boasting.squirrel@gmail.com> | 2019-03-11 23:25:56 +0000 |
commit | aeb51dcf43665741a3438360151a4424e9b243e0 (patch) | |
tree | 8f79e2b303512468170883b1d0ada02e344a6e5b | |
parent | Changed Info: to info: to be consistent (diff) | |
download | miniserve-aeb51dcf43665741a3438360151a4424e9b243e0.tar.gz miniserve-aeb51dcf43665741a3438360151a4424e9b243e0.zip |
Started to add helpful messages for errors which could occur during archiving
Diffstat (limited to '')
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/archive.rs | 82 | ||||
-rw-r--r-- | src/errors.rs | 90 | ||||
-rw-r--r-- | src/listing.rs | 44 | ||||
-rw-r--r-- | src/main.rs | 1 |
6 files changed, 171 insertions, 50 deletions
@@ -777,6 +777,7 @@ dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "libflate 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", @@ -41,4 +41,5 @@ tar = "0.4" tempfile = "3.0.7" bytes = "0.4.12" futures = "0.1.25" -libflate = "0.1.20"
\ No newline at end of file +libflate = "0.1.20" +failure = "0.1.5"
\ No newline at end of file diff --git a/src/archive.rs b/src/archive.rs index bc3a1c8..afbcc6b 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,5 +1,6 @@ use actix_web::http::ContentEncoding; use bytes::Bytes; +use failure::ResultExt; use libflate::gzip::Encoder; use serde::Deserialize; use std::fs::{File, OpenOptions}; @@ -7,6 +8,9 @@ use std::io::{self, Read}; use std::path::PathBuf; use tar::Builder; use tempfile::tempdir; +use yansi::Color; + +use crate::errors; /// Available compression methods #[derive(Debug, Deserialize, Clone)] @@ -45,43 +49,25 @@ impl CompressionMethod { } } -/// Possible errors -#[derive(Debug)] -pub enum CompressionError { - IOError(std::io::Error), - NoneError(std::option::NoneError), -} - -impl From<std::option::NoneError> for CompressionError { - fn from(err: std::option::NoneError) -> CompressionError { - CompressionError::NoneError(err) - } -} - -impl From<std::io::Error> for CompressionError { - fn from(err: std::io::Error) -> CompressionError { - CompressionError::IOError(err) - } -} - pub fn create_archive_file( method: &CompressionMethod, dir: &PathBuf, -) -> Result<(String, Bytes), CompressionError> { +) -> Result<(String, Bytes), errors::CompressionError> { match method { CompressionMethod::TarGz => tgz_compress(&dir), } } /// Compresses a given folder in .tar.gz format -fn tgz_compress(dir: &PathBuf) -> Result<(String, Bytes), CompressionError> { +fn tgz_compress(dir: &PathBuf) -> Result<(String, Bytes), errors::CompressionError> { let src_dir = dir.display().to_string(); let inner_folder = dir.file_name()?.to_str()?; let dst_filename = format!("{}.tar", inner_folder); let dst_tgz_filename = format!("{}.gz", dst_filename); - let tar_content = tar(src_dir, dst_filename, inner_folder.to_string())?; - let gz_data = gzip(&tar_content)?; + let tar_content = tar(src_dir, dst_filename, inner_folder.to_string()) + .context(errors::CompressionErrorKind::TarContentError)?; + let gz_data = gzip(&tar_content).context(errors::CompressionErrorKind::GZipContentError)?; let mut data = Bytes::new(); data.extend_from_slice(&gz_data); @@ -94,10 +80,13 @@ fn tar( src_dir: String, dst_filename: String, inner_folder: String, -) -> Result<Vec<u8>, CompressionError> { - let tmp_dir = tempdir()?; +) -> Result<Vec<u8>, errors::CompressionError> { + let tmp_dir = tempdir().context(errors::CompressionErrorKind::CreateTemporaryFileError)?; let dst_filepath = tmp_dir.path().join(dst_filename.clone()); - let tar_file = File::create(&dst_filepath)?; + let tar_file = + File::create(&dst_filepath).context(errors::CompressionErrorKind::CreateFileError { + path: color_path(&dst_filepath.display().to_string()), + })?; // Create a TAR file of src_dir let mut tar_builder = Builder::new(&tar_file); @@ -106,22 +95,47 @@ fn tar( // https://github.com/alexcrichton/tar-rs/issues/147 // https://github.com/alexcrichton/tar-rs/issues/174 tar_builder.follow_symlinks(false); - tar_builder.append_dir_all(inner_folder, src_dir)?; - tar_builder.into_inner()?; + tar_builder.append_dir_all(inner_folder, &src_dir).context( + errors::CompressionErrorKind::TarBuildingError { + message: format!( + "failed to append the content of {} in the TAR archive", + color_path(&src_dir) + ), + }, + )?; + tar_builder + .into_inner() + .context(errors::CompressionErrorKind::TarBuildingError { + message: "failed to finish writing the TAR archive".to_string(), + })?; // Read the content of the TAR file and store it as bytes - let mut tar_file = OpenOptions::new().read(true).open(&dst_filepath)?; + let mut tar_file = OpenOptions::new().read(true).open(&dst_filepath).context( + errors::CompressionErrorKind::OpenFileError { + path: color_path(&dst_filepath.display().to_string()), + }, + )?; let mut tar_content = Vec::new(); - tar_file.read_to_end(&mut tar_content)?; + tar_file + .read_to_end(&mut tar_content) + .context(errors::CompressionErrorKind::TarContentError)?; Ok(tar_content) } /// Compresses a stream of bytes using the GZIP algorithm -fn gzip(mut data: &[u8]) -> Result<Vec<u8>, CompressionError> { - let mut encoder = Encoder::new(Vec::new())?; - io::copy(&mut data, &mut encoder)?; - let data = encoder.finish().into_result()?; +fn gzip(mut data: &[u8]) -> Result<Vec<u8>, errors::CompressionError> { + let mut encoder = + Encoder::new(Vec::new()).context(errors::CompressionErrorKind::GZipBuildingError)?; + io::copy(&mut data, &mut encoder).context(errors::CompressionErrorKind::GZipBuildingError)?; + let data = encoder + .finish() + .into_result() + .context(errors::CompressionErrorKind::GZipBuildingError)?; Ok(data) } + +fn color_path(path: &str) -> String { + Color::White.paint(path).bold().to_string() +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..c85d123 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,90 @@ +use failure::{Backtrace, Context, Fail}; +use std::fmt::{self, Debug, Display}; +use yansi::Color; + +/// Kinds of error which might happen during folder archive generation +#[derive(Clone, Debug, PartialEq, Eq, Fail)] +pub enum CompressionErrorKind { + #[fail(display = "Could not open file {}", path)] + OpenFileError { path: String }, + #[fail(display = "Could not create temporary file")] + CreateTemporaryFileError, + #[fail(display = "Could not create file {}", path)] + CreateFileError { path: String }, + #[fail(display = "Could not retrieve entity name from the given path. + This can either mean that the entity has non UTF-8 characters in its name, + or that its name ends with \"..\"")] + InvalidDirectoryName, + #[fail(display = "Failed to create the TAR archive: {}", message)] + TarBuildingError { message: String }, + #[fail(display = "Failed to create the GZIP archive")] + GZipBuildingError, + #[fail(display = "Failed to retrieve TAR content")] + TarContentError, + #[fail(display = "Failed to retrieve GZIP content")] + GZipContentError, +} + +pub fn print_chain(err: CompressionError) { + for cause in Fail::iter_causes(&err) { + println!( + "{} {}", + Color::Magenta.paint("Caused by:").to_string(), + cause + ); + } +} + +pub struct CompressionError { + inner: Context<CompressionErrorKind>, +} + +impl CompressionError { + fn new(kind: CompressionErrorKind) -> CompressionError { + CompressionError { + inner: Context::new(kind), + } + } +} + +impl Fail for CompressionError { + fn cause(&self) -> Option<&Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.inner.backtrace() + } +} + +impl Display for CompressionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.inner, f) + } +} + +impl Debug for CompressionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(&self.inner, f) + } +} + +impl From<Context<CompressionErrorKind>> for CompressionError { + fn from(inner: Context<CompressionErrorKind>) -> CompressionError { + CompressionError { inner } + } +} + +impl From<CompressionErrorKind> for CompressionError { + fn from(kind: CompressionErrorKind) -> CompressionError { + CompressionError { + inner: Context::new(kind), + } + } +} + +impl From<std::option::NoneError> for CompressionError { + fn from(_: std::option::NoneError) -> CompressionError { + CompressionError::new(CompressionErrorKind::InvalidDirectoryName) + } +} diff --git a/src/listing.rs b/src/listing.rs index 88dab40..ab3b28b 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -7,9 +7,10 @@ use serde::Deserialize; use std::io; use std::path::Path; use std::time::SystemTime; -use yansi::Paint; +use yansi::{Color, Paint}; use crate::archive; +use crate::errors; use crate::renderer; /// Query parameters @@ -228,24 +229,37 @@ pub fn directory_listing<S>( } if let Some(compression_method) = &download { + println!( + "{info} Creating an archive ({extension}) of {path}...", + info = Color::Blue.paint("info:").bold(), + extension = Color::White.paint(compression_method.extension()).bold(), + path = Color::White.paint(&dir.path.display().to_string()).bold() + ); match archive::create_archive_file(&compression_method, &dir.path) { - Ok((filename, content)) => Ok(HttpResponse::Ok() - .content_type(compression_method.content_type()) - .content_length(content.len() as u64) - .content_encoding(compression_method.content_encoding()) - .header("Content-Transfer-Encoding", "binary") - .header( - "Content-Disposition", - format!("attachment; filename={:?}", filename), - ) - .chunked() - .body(Body::Streaming(Box::new(once(Ok(content)))))), - Err(_) => { + Ok((filename, content)) => { + println!( + "{success} Archive successfully created !", + success = Color::Green.paint("success:").bold() + ); + Ok(HttpResponse::Ok() + .content_type(compression_method.content_type()) + .content_length(content.len() as u64) + .content_encoding(compression_method.content_encoding()) + .header("Content-Transfer-Encoding", "binary") + .header( + "Content-Disposition", + format!("attachment; filename={:?}", filename), + ) + .chunked() + .body(Body::Streaming(Box::new(once(Ok(content)))))) + } + Err(err) => { println!( - "{error} an error occured while compressing {folder}", + "{error} {err}", error = Paint::red("error:").bold(), - folder = dir.path.display(), + err = Paint::white(&err).bold() ); + errors::print_chain(err); Ok(HttpResponse::Ok() .status(http::StatusCode::INTERNAL_SERVER_ERROR) .body("")) diff --git a/src/main.rs b/src/main.rs index 1bddd6d..59389b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ mod args; mod auth; mod listing; mod renderer; +mod errors; #[derive(Clone, Debug)] /// Configuration of the Miniserve application |