Use human readable format to specify expiry time

This commit is contained in:
timvisee 2019-10-24 17:27:32 +02:00
parent e39add8ffd
commit 98150f7f44
No known key found for this signature in database
GPG key ID: B8DB720BC383E172
4 changed files with 87 additions and 5 deletions

1
Cargo.lock generated
View file

@ -705,6 +705,7 @@ dependencies = [
"pbr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"qr2term 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rpassword 4.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -118,6 +118,7 @@ pathdiff = "0.1"
pbr = "1"
prettytable-rs = "0.8"
qr2term = { version = "0.1", optional = true }
regex = "1.3.1"
rpassword = "4.0"
serde = "1.0"
serde_derive = "1.0"

View file

@ -1,10 +1,14 @@
use chrono::Duration;
use clap::{Arg, ArgMatches};
use failure::Fail;
use ffsend_api::api::Version as ApiVersion;
use ffsend_api::config::expiry_max;
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use crate::cmd::matcher::MainMatcher;
use crate::util::{highlight, prompt_yes, quit};
use crate::util::{
format_duration, highlight, parse_duration, prompt_yes, quit, quit_error, ErrorHints,
};
/// The download limit argument.
pub struct ArgExpiryTime {}
@ -25,10 +29,13 @@ impl ArgExpiryTime {
return Some(expiry);
}
// Define function to format seconds
let format_secs = |secs: usize| format_duration(Duration::seconds(secs as i64));
// Prompt the user the specified expiry time is invalid
let allowed_str = allowed
.iter()
.map(|value| format!("{}", value))
.map(|secs| format_secs(*secs))
.collect::<Vec<_>>()
.join(", ");
eprintln!("The expiry time must be one of: {}", allowed_str);
@ -44,7 +51,10 @@ impl ArgExpiryTime {
// Ask to use closest limit, quit if user cancelled
let closest = closest(allowed, expiry);
if !prompt_yes(
&format!("Would you like to set expiry time to {} instead?", closest),
&format!(
"Would you like to set expiry time to {} instead?",
format_secs(closest)
),
None,
main_matcher,
) {
@ -78,8 +88,13 @@ impl<'a> CmdArgOption<'a> for ArgExpiryTime {
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::<usize>().expect("invalid expiry time"))
Self::value_raw(matches).map(|t| match parse_duration(t) {
Ok(seconds) => seconds,
Err(err) => quit_error(
err.context("specified invalid file expiry time"),
ErrorHints::default(),
),
})
}
}

View file

@ -4,6 +4,7 @@ extern crate colored;
extern crate directories;
extern crate fs2;
extern crate open;
extern crate regex;
#[cfg(feature = "clipboard-bin")]
extern crate which;
@ -38,6 +39,7 @@ use ffsend_api::{
reqwest,
url::Url,
};
use regex::Regex;
use rpassword::prompt_password_stderr;
#[cfg(feature = "clipboard-bin")]
use which::which;
@ -824,6 +826,69 @@ pub fn format_bytes(bytes: u64) -> String {
}
}
/// Parse the given duration string from human readable format into seconds.
/// This method parses a string of time components to represent the given duration.
///
/// The following time units are used:
/// - `w`: weeks
/// - `d`: days
/// - `h`: hours
/// - `m`: minutes
/// - `s`: seconds
/// The following time strings can be parsed:
/// - `8w6d`
/// - `23h14m`
/// - `9m55s`
/// - `1s1s1s1s1s`
pub fn parse_duration(duration: &str) -> Result<usize, ParseDurationError> {
// Build a regex to grab time parts
let re = Regex::new(r"(?i)([0-9]+)(([a-z]|\s*$))")
.expect("failed to compile duration parsing regex");
// We must find any match
if re.find(duration).is_none() {
return Err(ParseDurationError::Empty);
}
// Parse each time part, sum it's seconds
let mut seconds = 0;
for capture in re.captures_iter(duration) {
// Parse time value and modifier
let number = capture[1]
.parse::<usize>()
.map_err(ParseDurationError::InvalidValue)?;
let modifier = capture[2].trim().to_lowercase();
// Multiply and sum seconds by modifier
seconds += match modifier.as_str() {
"" | "s" => number,
"m" => number * 60,
"h" => number * 60 * 60,
"d" => number * 60 * 60 * 24,
"w" => number * 60 * 60 * 24 * 7,
m => return Err(ParseDurationError::UnknownIdentifier(m.into())),
};
}
Ok(seconds)
}
/// Represents a duration parsing error.
#[derive(Debug, Fail)]
pub enum ParseDurationError {
/// The given duration string did not contain any duration part.
#[fail(display = "given string did not contain any duration part")]
Empty,
/// A numeric value was invalid.
#[fail(display = "duration part has invalid numeric value")]
InvalidValue(std::num::ParseIntError),
/// The given duration string contained an invalid duration modifier.
#[fail(display = "duration part has unknown time identifier '{}'", _0)]
UnknownIdentifier(String),
}
/// Format the given duration in a human readable format.
/// This method builds a string of time components to represent
/// the given duration.