Browse Source

Respect download limits enforced by remote server, update ffsend-api

See issue timvisee/ffsend#17
timvisee 6 years ago
parent
commit
888b10afd6
7 changed files with 145 additions and 77 deletions
  1. 8 6
      Cargo.lock
  2. 1 1
      Cargo.toml
  3. 1 1
      src/action/params.rs
  4. 47 35
      src/action/upload.rs
  5. 70 26
      src/cmd/arg/download_limit.rs
  6. 1 1
      src/cmd/matcher/params.rs
  7. 17 7
      src/cmd/matcher/upload.rs

+ 8 - 6
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"

+ 1 - 1
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"

+ 1 - 1
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();
 

+ 47 - 35
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);
-
-        // 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())
+        // We do not authenticate for now
+        let auth = false;
+
+        // 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 {}",
+            // Determine the max file size
+            // TODO: set false parameter to authentication state
+            let max_size = upload_size_max(api_version, auth);
+
+            // 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(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),
-                );
+                        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();
+                    // 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();
 

+ 70 - 26
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<usize> {
+        // 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::<Vec<_>>()
+            .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<u8>;
+    type Value = Option<usize>;
 
     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::<u8>().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::<usize>().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
+}

+ 1 - 1
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<u8> {
+    pub fn download_limit(&'a self) -> Option<usize> {
         ArgDownloadLimit::value(self.matches)
     }
 }

+ 17 - 7
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<u8> {
-        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<usize> {
+        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.