From eb79a6550044d085dbfbac52448476ebc6444e27 Mon Sep 17 00:00:00 2001 From: Emi Simpson Date: Thu, 11 Aug 2022 23:00:15 -0400 Subject: [PATCH] Provide colorful user interface and error handling --- src/errors.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 28 ++++++++++++------ 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 5be540b..dc3cca2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::{path::{PathBuf, Path}, io}; #[derive(Debug)] @@ -92,3 +93,81 @@ impl AviaryError { } } } + +impl fmt::Display for AviaryError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "\x1b[31mError!\x1b[0m\n")?; + match self { + Self::FileDNE(path) => + writeln!(f, "The file at \x1b[36m{}\x1b[0m does not exist", path.display()), + + Self::ReadPermissionDenied(path) => + writeln!(f, "Permission denied for the file \x1b[36m{}\x1b[0m", path.display()), + + Self::StreamReadError(path, err) => + writeln!(f, + concat!( + "Something bad happened while we were reading from ", + "\x1b[36m{}\x1b[0m. This could be caused by removing a ", + "drive/phone without unplugging it, or several other possible ", + "things. This error might give you a clue:\n\n{}" + ), + path.display(), err + ), + + Self::ImageFormatError(path) => + writeln!(f, + concat!( + "We had a little bit of trouble understanding the image at ", + "\x1b[36m{}\x1b[0m. Normally, this indicates that it might not be ", + "an image file (e.g. if it's a video instead). If you're sure that ", + "it's an image, this could indicate that it's corrupted or of an ", + "unsupported format.\n\n", + "\x1b[34mSupported formats:\x1b[0m\n", + " - gif\n", + " - jpg\n", + " - ico\n", + " - png\n", + " - tiff\n", + " - webp\n", + " - bmp\n", + " - hdr", + ), + path.display(), + ), + + Self::ServerError(msg) => + writeln!(f, + concat!( + "The server seems to be down or misbehaving at the minute. ", + "Normally, this issue should resolve by itself once the server ", + "maintainers identify the problem and fix it.\n\n", + "If the issue persists, please submit an issue on the bug ", + "tracker. This error message may help to identify the problem:", + "\n\n\x1b[31m{}\x1b[0m", + ), + msg, + ), + + Self::ConnectionError(msg) => + writeln!(f, + concat!( + "There was an issue connecting to the server! Check that your ", + "internet is online, and you can access websites normally. ", + "This information might help diagnose the issue:\n\n", + "\x1b[31m{}\x1b[0m", + ), + msg, + ), + + Self::BadServerParameter => + writeln!(f, + concat!( + "The server URL you provided was \x1b[31minvalid\x1b[0m! ", + "Please check to make sure that it is properly formatted, like ", + "\x1b[1m0x0.st\x1b[0m or \x1b[1menvs.sh\x1b[0m.", + ), + ), + } + } +} diff --git a/src/main.rs b/src/main.rs index d7e3e23..4fefb44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ fn trim_url<'a>(base_url: &str, url: &'a str) -> Option<&'a str> { fn main() { let args: parse::Args = argh::from_env(); + print!("Checking files..."); let (files, errors): (Vec<_>, Vec<_>) = args.images.iter() .map(Path::new) .map(|path| File::open(path) @@ -42,7 +43,7 @@ fn main() { .partition_result(); if !errors.is_empty() { - println!("[FATAL] The was a problem accessing some of the files you provided!"); + println!(" \x1b[31mError!\x1b[0m"); let (nonexistant, noread): (Vec<_>, Vec<_>) = errors.iter() .partition_map(|e| match e { AviaryError::FileDNE(path) => Either::Left(path), @@ -51,13 +52,14 @@ fn main() { }); if !nonexistant.is_empty() { println!("\nWe didn't see any files at the following locations:"); - nonexistant.iter().for_each(|path| println!("{}", path.display())); + nonexistant.iter().for_each(|path| println!("\x1b[37m- \x1b[31m{}", path.display())); } if !noread.is_empty() { - println!("\nWe found these files, but didn't have permission to open them:"); - noread.iter().for_each(|path| println!("{}", path.display())); + println!("\x1b[0m\nWe found these files, but didn't have permission to open them:"); + noread.iter().for_each(|path| println!("\x1b[37m- \x1b[31m{}", path.display())); } } else { + println!(" \x1b[32mDone!\n"); let agent = upload::get_agent(); let full_server = if args.server.starts_with("http") { Cow::Borrowed(&args.server) @@ -65,6 +67,7 @@ fn main() { Cow::Owned(format!("https://{}", args.server)) }; let index_url = files.into_iter() + .inspect(|(path, _)| print!("\x1b[36m{}\x1b[0m\n\x1b[37m├─\x1b[0m Reading... ", path.display())) .map(|(path, mut file)| (|| { let mut buff = Vec::with_capacity(file.metadata()?.len() as usize); @@ -72,11 +75,13 @@ fn main() { Ok((path, buff)) })().map_err(|e| AviaryError::StreamReadError(path.to_owned(), e)) ) + .inspect(|r| if r.is_ok() { print!("\x1b[32mDone!\n\x1b[37m├─\x1b[0m Thumbnailing... ") }) .map(|r| r.and_then(|(path, raw_dat)| { let (thumbnail, blurhash) = thumbnailing::thumbnail(&raw_dat) .map_err(|_| AviaryError::ImageFormatError(path.to_owned()))?; Ok((raw_dat, thumbnail, blurhash)) })) + .inspect(|r| if r.is_ok() { print!("\x1b[32mDone!\n\x1b[37m├─\x1b[0m Encrypting... ")}) .map_ok(|(raw_dat, thumbnail, blurhash)| { let key = crypto::make_key(); ( @@ -86,6 +91,7 @@ fn main() { blurhash ) }) + .inspect(|r| if r.is_ok() { print!("\x1b[32mDone!\n\x1b[37m└─\x1b[0m Uploading... ")}) .map(|r| r.and_then(|(key, full_img, thumb, blurhash)| upload::put_data(&agent, &*full_server, &thumb) .and_then(|thumb_url| @@ -102,6 +108,7 @@ fn main() { Err(AviaryError::ServerError(format!("Received bad response from server: {}", full_url))) } })) + .inspect(|r| if r.is_ok() { println!("\x1b[32mDone!\n")}) .map_ok(|(key, full_url, thumb_url, blurhash)| protobuf::image::Image { key: key.into(), full_url, thumb_url, blurhash, @@ -122,11 +129,16 @@ fn main() { .expect("Error serializing protocol buffers") ); let encoded_key = base64::encode(index_key); + print!("\x1b[0mUploading index... "); upload::put_data(&agent, &*full_server, &encrypted_index) .map_err(|e| AviaryError::from_upload_error(e)) - .map(|url| format!("{}#{}", url.trim(), &encoded_key)) - }) - .unwrap(); - println!("Success! {}", index_url); + .map(|url| format!("{}#{}", url.trim().trim_end_matches(".bin"), &encoded_key)) + }); + match index_url { + Ok(url) => + println!("\x1b[32mDone!\n\n\x1b[34mYour gallery is: \x1b[1;0m{}", url), + Err(e) => + print!("{}", e), + } } }