diff --git a/IDEAS.md b/IDEAS.md index 05bc144..c4f6d1e 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -1,7 +1,6 @@ # First release -- Only allow file extension renaming on upload with `-f` flag - Check for file expiry everywhere -- Soft limit uploads to 1GB and 2GB +- On download, mention a wrong or missing password with a HTTP 401 response - Remember all uploaded files, make files listable - Incognito mode, to not remember files `--incognito` - Automatically get owner token, from file history when setting password @@ -12,7 +11,7 @@ # Other ideas - Box errors -- On download, mention a wrong or missing password with a HTTP 401 response +- Only allow file extension renaming on upload with `-f` flag - Implement error handling everywhere properly - Quick upload/download without `upload` or `download` subcommands? - Flag to explicitly delete file after download diff --git a/api/src/config.rs b/api/src/config.rs new file mode 100644 index 0000000..a58a543 --- /dev/null +++ b/api/src/config.rs @@ -0,0 +1,5 @@ +/// The recommended maximum upload size in bytes. +pub const UPLOAD_SIZE_MAX_RECOMMENDED: u64 = 1_073_741_824; + +/// The maximum upload size in bytes. +pub const UPLOAD_SIZE_MAX: u64 = 2_147_483_648; diff --git a/api/src/lib.rs b/api/src/lib.rs index e58f6d7..eb2825e 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -16,6 +16,7 @@ pub extern crate url; pub mod action; mod api; +pub mod config; pub mod crypto; mod ext; pub mod file; diff --git a/cli/src/action/upload.rs b/cli/src/action/upload.rs index 82c38f5..7f6adf9 100644 --- a/cli/src/action/upload.rs +++ b/cli/src/action/upload.rs @@ -1,3 +1,4 @@ +use std::fs::File; use std::path::Path; use std::sync::{Arc, Mutex}; @@ -5,17 +6,24 @@ use clap::ArgMatches; use failure::{err_msg, Fail}; use ffsend_api::action::params::ParamsDataBuilder; use ffsend_api::action::upload::Upload as ApiUpload; +use ffsend_api::config::{UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_RECOMMENDED}; use ffsend_api::reqwest::Client; -use cmd::matcher::{ - Matcher, - upload::UploadMatcher, -}; +use cmd::matcher::{Matcher, MainMatcher, UploadMatcher}; use error::ActionError; use progress::ProgressBar; -use util::open_url; +use util::{ + ErrorHintsBuilder, + format_bytes, + open_url, + print_error, + print_error_msg, + prompt_yes, + quit, + quit_error_msg, +}; #[cfg(feature = "clipboard")] -use util::{print_error, set_clipboard}; +use util::set_clipboard; /// A file upload action. pub struct Upload<'a> { @@ -34,12 +42,52 @@ impl<'a> Upload<'a> { // TODO: create a trait for this method pub fn invoke(&self) -> Result<(), ActionError> { // Create the command matchers + let matcher_main = MainMatcher::with(self.cmd_matches).unwrap(); let matcher_upload = UploadMatcher::with(self.cmd_matches).unwrap(); // Get API parameters let path = Path::new(matcher_upload.file()).to_path_buf(); let host = matcher_upload.host(); + // TODO: ensure the file exists and is accessible + + // Get the file size to warn about large files + if let Ok(size) = File::open(&path) + .and_then(|f| f.metadata()) + .map(|m| m.len()) + { + if size > UPLOAD_SIZE_MAX && !matcher_main.force() { + // The file is too large, show an error and quit + quit_error_msg( + format!( + "The file size is {}, bigger than the maximum allowed of {}", + format_bytes(size), + format_bytes(UPLOAD_SIZE_MAX), + ), + ErrorHintsBuilder::default() + .force(true) + .verbose(false) + .build() + .unwrap(), + ); + } else if size > UPLOAD_SIZE_MAX_RECOMMENDED && !matcher_main.force() { + // The file is larger than the recommended maximum, warn + eprintln!( + "The file size is {}, bigger than the recommended maximum of {}", + format_bytes(size), + format_bytes(UPLOAD_SIZE_MAX_RECOMMENDED), + ); + + // Prompt the user to continue, quit if the user answered no + if !prompt_yes("Continue uploading?", Some(true), &matcher_main) { + println!("Upload cancelled"); + quit(); + } + } + } else { + print_error_msg("Failed to check the file size, ignoring"); + } + // Create a reqwest client let client = Client::new(); diff --git a/cli/src/util.rs b/cli/src/util.rs index 5740178..8199aba 100644 --- a/cli/src/util.rs +++ b/cli/src/util.rs @@ -48,6 +48,15 @@ pub fn print_error(err: E) { } } +/// Print the given error message in a proper format for the user, +/// with it's causes. +pub fn print_error_msg(err: S) + where + S: AsRef + Display + Debug + Sync + Send + 'static +{ + print_error(err_msg(err).compat()); +} + /// Quit the application regularly. pub fn quit() -> ! { exit(0); @@ -393,3 +402,16 @@ pub fn ensure_owner_token( } } } + +/// Format the given number of bytes readable for humans. +pub fn format_bytes(bytes: u64) -> String { + let bytes = bytes as f64; + let kb = 1024f64; + match bytes { + bytes if bytes >= kb.powf(4_f64) => format!("{:.*} TiB", 2, bytes / kb.powf(4_f64)), + bytes if bytes >= kb.powf(3_f64) => format!("{:.*} GiB", 2, bytes / kb.powf(3_f64)), + bytes if bytes >= kb.powf(2_f64) => format!("{:.*} MiB", 2, bytes / kb.powf(2_f64)), + bytes if bytes >= kb => format!("{:.*} KiB", 2, bytes / kb), + _ => format!("{:.*} B", 0, bytes), + } +}