diff --git a/Cargo.lock b/Cargo.lock index 2d74977..7471938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,10 +377,12 @@ dependencies = [ [[package]] name = "csv" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -582,7 +584,7 @@ dependencies = [ "derive_builder 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ffsend-api 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ffsend-api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "open 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -603,7 +605,7 @@ dependencies = [ [[package]] name = "ffsend-api" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1240,7 +1242,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "csv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2248,7 +2250,7 @@ dependencies = [ "checksum crossterm_utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d26f24386ea91f9c55a85531dd3ee3673e4c82729e64567928665aca3a47c741" "checksum crossterm_winapi 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "71dc52848cd3c5e06d5d0e193c0d64ce93b71502e124723a33bf615ef7089610" "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -"checksum csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd1c44c58078cfbeaf11fbb3eac9ae5534c23004ed770cc4bfb48e658ae4f04" +"checksum csv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f0782c7154d8dd08f4adeb5aa22ab178c10281915f7da68d10bb646f03aaee73" "checksum csv-core 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa5cdef62f37e6ffe7d1f07a381bc0db32b7a3ff1cac0de56cb0d81e71f53d65" "checksum darling 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "49fc76d30c96cc0bdc8b966968e6535d900f3e42c56204d355192a670d989c6e" "checksum darling 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9158d690bc62a3a57c3e45b85e4d50de2008b39345592c64efd79345c7e24be0" @@ -2269,7 +2271,7 @@ dependencies = [ "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -"checksum ffsend-api 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca01e082c12a10fc477f7bb7ea4e264508c0c5d2a8f7bad85862dd403477ab57" +"checksum ffsend-api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c1ce8879c0aae328782352fc937abf2bfafe9b2570735a555fa45caac8dcb91f" "checksum filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646" "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" diff --git a/Cargo.toml b/Cargo.toml index 4a33a90..37202df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ colored = "1.7" derive_builder = "0.7" directories = "1.0" failure = "0.1" -ffsend-api = { version = "0.3.0", default-features = false } +ffsend-api = { version = "0.3.2", default-features = false } fs2 = "0.4" lazy_static = "1.0" open = "1" diff --git a/src/action/params.rs b/src/action/params.rs index bb00848..cf0d707 100644 --- a/src/action/params.rs +++ b/src/action/params.rs @@ -44,7 +44,7 @@ impl<'a> Params<'a> { // Build the parameters data object let data = ParamsDataBuilder::default() - .download_limit(matcher_params.download_limit()) + .download_limit(matcher_params.download_limit().map(|d| d as u8)) .build() .unwrap(); diff --git a/src/action/upload.rs b/src/action/upload.rs index aec6fa1..59aa739 100644 --- a/src/action/upload.rs +++ b/src/action/upload.rs @@ -68,47 +68,55 @@ impl<'a> Upload<'a> { // TODO: ensure the file exists and is accessible - // Determine the max file size - // TODO: set false parameter to authentication state - let max_size = upload_size_max(api_version, false); + // We do not authenticate for now + let auth = false; - // 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()) + // TODO: extract this into external function { - if size > max_size && !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(max_size), - ), - 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), - ); + // Determine the max file size + // TODO: set false parameter to authentication state + let max_size = upload_size_max(api_version, auth); - // Prompt the user to continue, quit if the user answered no - if !prompt_yes("Continue uploading?", Some(true), &matcher_main) { - println!("Upload cancelled"); - quit(); + // 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 > max_size && !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(max_size), + ), + 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"); } - } else { - print_error_msg("failed to check the file size, ignoring"); } + // TODO: assert max expiry time for file + // Create a reqwest client capable for uploading files let transfer_client = client_config.client(true); @@ -119,7 +127,11 @@ impl<'a> Upload<'a> { let params = { // Build the parameters data object let params = ParamsDataBuilder::default() - .download_limit(matcher_upload.download_limit()) + .download_limit( + matcher_upload + .download_limit(&matcher_main, api_version, auth) + .map(|d| d as u8), + ) .build() .unwrap(); diff --git a/src/cmd/arg/download_limit.rs b/src/cmd/arg/download_limit.rs index 912a4d3..a67a207 100644 --- a/src/cmd/arg/download_limit.rs +++ b/src/cmd/arg/download_limit.rs @@ -1,15 +1,63 @@ use clap::{Arg, ArgMatches}; -use ffsend_api::action::params::{ - PARAMS_DOWNLOAD_MAX as DOWNLOAD_MAX, PARAMS_DOWNLOAD_MIN as DOWNLOAD_MIN, -}; +use ffsend_api::api::Version as ApiVersion; +use ffsend_api::config::downloads_max; use super::{CmdArg, CmdArgFlag, CmdArgOption}; +use crate::cmd::matcher::MainMatcher; +use crate::util::{highlight, prompt_yes}; -use crate::util::{quit_error_msg, ErrorHintsBuilder}; +use crate::util::{quit, ErrorHintsBuilder}; /// The download limit argument. pub struct ArgDownloadLimit {} +impl ArgDownloadLimit { + pub fn value_checked<'a>( + matches: &ArgMatches<'a>, + main_matcher: &MainMatcher, + api_version: ApiVersion, + auth: bool, + ) -> Option { + // Get the download value + let mut downloads = Self::value(matches)?; + + // Get number of allowed downloads, return if allowed + let allowed = downloads_max(api_version, auth); + if allowed.contains(&downloads) { + return Some(downloads); + } + + // Prompt the user the specified downloads limit is invalid + let allowed_str = allowed + .iter() + .map(|value| format!("{}", value)) + .collect::>() + .join(", "); + eprintln!("The downloads limit must be one of: {}", allowed_str,); + if auth { + eprintln!("Use '{}' to force", highlight("--force")); + } else { + eprintln!( + "Use '{}' to force, authenticate for higher limits", + highlight("--force") + ); + } + + // Ask to use closest limit, quit if user cancelled + let closest = closest(allowed, downloads); + if !prompt_yes( + &format!("Would you like to limit downloads to {} instead?", closest), + None, + main_matcher, + ) { + quit(); + } + downloads = closest; + + Some(downloads) + } +} + impl CmdArg for ArgDownloadLimit { fn name() -> &'static str { "download-limit" @@ -29,30 +77,26 @@ impl CmdArg for ArgDownloadLimit { impl CmdArgFlag for ArgDownloadLimit {} impl<'a> CmdArgOption<'a> for ArgDownloadLimit { - type Value = Option; + type Value = Option; fn value<'b: 'a>(matches: &'a ArgMatches<'b>) -> Self::Value { // TODO: do not unwrap, report an error - Self::value_raw(matches) - .map(|d| d.parse::().expect("invalid download limit")) - .and_then(|d| { - // Check the download limit bounds - // TODO: somehow allow to force a different number here - if d < DOWNLOAD_MIN || d > DOWNLOAD_MAX { - quit_error_msg( - format!( - "invalid download limit, must be between {} and {}", - DOWNLOAD_MIN, DOWNLOAD_MAX, - ), - ErrorHintsBuilder::default() - .force(false) - .verbose(false) - .build() - .unwrap(), - ); - } - - Some(d) - }) + Self::value_raw(matches).map(|d| d.parse::().expect("invalid download limit")) } } + +/// Find the closest value to `current` in the given `values` range. +fn closest(values: &[usize], current: usize) -> usize { + // Own the values, sort and reverse, start with biggest first + let mut values = values.to_vec(); + values.sort_unstable(); + + // Find the closest value, return it + *values + .iter() + .rev() + .map(|value| (value, (current as i64 - *value as i64).abs())) + .min_by_key(|value| value.1) + .expect("failed to find closest value, none given") + .0 +} diff --git a/src/cmd/matcher/params.rs b/src/cmd/matcher/params.rs index 537f91b..00ab481 100644 --- a/src/cmd/matcher/params.rs +++ b/src/cmd/matcher/params.rs @@ -26,7 +26,7 @@ impl<'a: 'b, 'b> ParamsMatcher<'a> { } /// Get the download limit. - pub fn download_limit(&'a self) -> Option { + pub fn download_limit(&'a self) -> Option { ArgDownloadLimit::value(self.matches) } } diff --git a/src/cmd/matcher/upload.rs b/src/cmd/matcher/upload.rs index 24ab718..35ce512 100644 --- a/src/cmd/matcher/upload.rs +++ b/src/cmd/matcher/upload.rs @@ -1,10 +1,12 @@ use clap::ArgMatches; use ffsend_api::action::params::PARAMS_DEFAULT_DOWNLOAD as DOWNLOAD_DEFAULT; +use ffsend_api::api::Version as ApiVersion; use ffsend_api::url::Url; use super::Matcher; -use crate::cmd::arg::{ - ArgDownloadLimit, ArgGenPassphrase, ArgHost, ArgPassword, CmdArgFlag, CmdArgOption, +use crate::cmd::{ + arg::{ArgDownloadLimit, ArgGenPassphrase, ArgHost, ArgPassword, CmdArgFlag, CmdArgOption}, + matcher::MainMatcher, }; use crate::util::{bin_name, env_var_present, quit_error_msg, ErrorHintsBuilder}; @@ -73,13 +75,21 @@ impl<'a: 'b, 'b> UploadMatcher<'a> { } /// Get the download limit. + /// /// If the download limit was the default, `None` is returned to not /// explicitly set it. - pub fn download_limit(&'a self) -> Option { - ArgDownloadLimit::value(self.matches).and_then(|d| match d { - DOWNLOAD_DEFAULT => None, - d => Some(d), - }) + pub fn download_limit( + &'a self, + main_matcher: &MainMatcher, + api_version: ApiVersion, + auth: bool, + ) -> Option { + ArgDownloadLimit::value_checked(self.matches, main_matcher, api_version, auth).and_then( + |d| match d { + d if d == DOWNLOAD_DEFAULT as usize => None, + d => Some(d), + }, + ) } /// Check whether to archive the file to upload.