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

See issue timvisee/ffsend#17
This commit is contained in:
timvisee 2019-04-05 16:54:34 +02:00
parent b59650f25d
commit 888b10afd6
No known key found for this signature in database
GPG key ID: B8DB720BC383E172
7 changed files with 145 additions and 77 deletions

14
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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();

View file

@ -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();

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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.