Create main matcher, implement no-interact logic

This commit is contained in:
timvisee 2018-04-11 16:46:14 +02:00
parent 7760f56d1d
commit 896c01b6ef
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
12 changed files with 105 additions and 22 deletions

View file

@ -1,7 +1,6 @@
# Ideas
- allow creating non existent directories with the `-f` flag
- only allow file extension renaming on upload with `-f` flag
- no interact flag
- `-y` flag for assume yes
- `-f` flag for forcing (no interact?)
- Box errors
@ -17,7 +16,6 @@
- Download to a temporary location first
- Soft limit uploads to 1GB and 2GB
- Allow piping input/output files
- Allow file renaming on upload
- Allow file/directory archiving on upload
- Allow unarchiving on download
- Allow hiding the progress bar, and/or showing simple progress

View file

@ -12,6 +12,7 @@ use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
delete::DeleteMatcher,
main::MainMatcher,
};
use error::ActionError;
use util::{ensure_owner_token, print_success};
@ -33,6 +34,7 @@ impl<'a> Delete<'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_delete = DeleteMatcher::with(self.cmd_matches).unwrap();
// Get the share URL
@ -45,7 +47,7 @@ impl<'a> Delete<'a> {
let mut file = RemoteFile::parse_url(url, matcher_delete.owner())?;
// Ensure the owner token is set
ensure_owner_token(file.owner_token_mut());
ensure_owner_token(file.owner_token_mut(), &matcher_main);
// Send the file deletion request
ApiDelete::new(&file, None).invoke(&client)?;

View file

@ -15,6 +15,7 @@ use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
download::DownloadMatcher,
main::MainMatcher,
};
use progress::ProgressBar;
use util::ensure_password;
@ -36,6 +37,7 @@ impl<'a> Download<'a> {
// TODO: create a trait for this method
pub fn invoke(&self) -> Result<(), Error> {
// Create the command matchers
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_download = DownloadMatcher::with(self.cmd_matches).unwrap();
// Get the share URL
@ -59,7 +61,7 @@ impl<'a> Download<'a> {
}
// Ensure a password is set when required
ensure_password(&mut password, exists.has_password());
ensure_password(&mut password, exists.has_password(), &matcher_main);
// Create a progress bar reporter
let bar = Arc::new(Mutex::new(ProgressBar::new_download()));

View file

@ -18,6 +18,7 @@ use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
info::InfoMatcher,
main::MainMatcher,
};
use util::{ensure_owner_token, ensure_password, print_error};
@ -38,6 +39,7 @@ impl<'a> Info<'a> {
// TODO: create a trait for this method
pub fn invoke(&self) -> Result<(), Error> {
// Create the command matchers
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_info = InfoMatcher::with(self.cmd_matches).unwrap();
// Get the share URL
@ -51,7 +53,7 @@ impl<'a> Info<'a> {
let mut password = matcher_info.password();
// Ensure the owner token is set
ensure_owner_token(file.owner_token_mut());
ensure_owner_token(file.owner_token_mut(), &matcher_main);
// Check whether the file exists
let exists = ApiExists::new(&file).invoke(&client)?;
@ -60,7 +62,7 @@ impl<'a> Info<'a> {
}
// Ensure a password is set when required
ensure_password(&mut password, exists.has_password());
ensure_password(&mut password, exists.has_password(), &matcher_main);
// Fetch both file info and metadata
let info = ApiInfo::new(&file, None).invoke(&client)?;

View file

@ -8,6 +8,7 @@ use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
main::MainMatcher,
params::ParamsMatcher,
};
use error::ActionError;
@ -30,6 +31,7 @@ impl<'a> Params<'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_params = ParamsMatcher::with(self.cmd_matches).unwrap();
// Get the share URL
@ -42,7 +44,7 @@ impl<'a> Params<'a> {
let mut file = RemoteFile::parse_url(url, matcher_params.owner())?;
// Ensure the owner token is set
ensure_owner_token(file.owner_token_mut());
ensure_owner_token(file.owner_token_mut(), &matcher_main);
// Build the parameters data object
let data = ParamsDataBuilder::default()

View file

@ -5,6 +5,7 @@ use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
main::MainMatcher,
password::PasswordMatcher,
};
use error::ActionError;
@ -27,6 +28,7 @@ impl<'a> Password<'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_password = PasswordMatcher::with(self.cmd_matches).unwrap();
// Get the share URL
@ -39,7 +41,7 @@ impl<'a> Password<'a> {
let mut file = RemoteFile::parse_url(url, matcher_password.owner())?;
// Ensure the owner token is set
ensure_owner_token(file.owner_token_mut());
ensure_owner_token(file.owner_token_mut(), &matcher_main);
// Execute an password action
ApiPassword::new(&file, &matcher_password.password(), None).invoke(&client)?;

View file

@ -1,5 +1,6 @@
use clap::{Arg, ArgMatches};
use cmd::matcher::{MainMatcher, Matcher};
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use util::prompt_owner_token;
@ -42,7 +43,10 @@ impl<'a> CmdArgOption<'a> for ArgOwner {
p => return p.map(|p| p.into()),
}
// Create a main matcher
let matcher_main = MainMatcher::with(matches).unwrap();
// Prompt for the owner token
Some(prompt_owner_token())
Some(prompt_owner_token(&matcher_main))
}
}

View file

@ -1,5 +1,6 @@
use clap::{Arg, ArgMatches};
use cmd::matcher::{MainMatcher, Matcher};
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use util::prompt_password;
@ -40,7 +41,10 @@ impl<'a> CmdArgOption<'a> for ArgPassword {
p => return p.map(|p| p.into()),
}
// Create a main matcher
let matcher_main = MainMatcher::with(matches).unwrap();
// Prompt for the password
Some(prompt_password())
Some(prompt_password(&matcher_main))
}
}

View file

@ -0,0 +1,35 @@
use clap::ArgMatches;
use super::Matcher;
/// The main command matcher.
pub struct MainMatcher<'a> {
matches: &'a ArgMatches<'a>,
}
impl<'a: 'b, 'b> MainMatcher<'a> {
/// Check whether to force.
pub fn force(&self) -> bool {
self.matches.is_present("force")
}
/// Check whether to use no-interact mode.
pub fn no_interact(&self) -> bool {
self.matches.is_present("no-interact")
}
/// Check whether to assume yes.
pub fn assume_yes(&self) -> bool {
self.matches.is_present("yes")
}
}
impl<'a> Matcher<'a> for MainMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
Some(
MainMatcher {
matches,
}
)
}
}

View file

@ -2,6 +2,7 @@ pub mod delete;
pub mod download;
pub mod exists;
pub mod info;
pub mod main;
pub mod params;
pub mod password;
pub mod upload;
@ -11,6 +12,7 @@ pub use self::delete::DeleteMatcher;
pub use self::download::DownloadMatcher;
pub use self::exists::ExistsMatcher;
pub use self::info::InfoMatcher;
pub use self::main::MainMatcher;
pub use self::params::ParamsMatcher;
pub use self::password::PasswordMatcher;
pub use self::upload::UploadMatcher;

View file

@ -1,4 +1,5 @@
extern crate clap;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate failure_derive;

View file

@ -17,10 +17,12 @@ use std::process::{exit, ExitStatus};
#[cfg(feature = "clipboard")]
use self::clipboard::{ClipboardContext, ClipboardProvider};
use self::colored::*;
use failure::{self, Fail};
use failure::{self, err_msg, Fail};
use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
use cmd::matcher::MainMatcher;
/// Print a success message.
pub fn print_success(msg: &str) {
println!("{}", msg.green());
@ -89,9 +91,14 @@ pub fn set_clipboard(content: String) -> Result<(), Box<StdError>> {
}
/// Prompt the user to enter a password.
// TODO: do not prompt if no-interactive
// TODO: only allow emtpy password if forced
pub fn prompt_password() -> String {
pub fn prompt_password(main_matcher: &MainMatcher) -> String {
// Quit with an error if we may not interact
if main_matcher.no_interact() {
quit_error(err_msg("Missing password, must be specified in no-interact mode").compat());
}
// Prompt and return
match prompt_password_stderr("Password: ") {
Ok(password) => password,
Err(err) => quit_error(err.context(
@ -107,16 +114,20 @@ pub fn prompt_password() -> String {
/// This method will prompt the user for a password, if one is required but
/// wasn't set. An ignore message will be shown if it was not required while it
/// was set.
pub fn ensure_password(password: &mut Option<String>, needs: bool) {
pub fn ensure_password(
password: &mut Option<String>,
needs: bool,
main_matcher: &MainMatcher,
) {
// Return if we're fine
if password.is_some() == needs {
return;
}
// Ask for a password, or reset it
// Prompt for the password, or clear it if not required
if needs {
println!("This file is protected with a password.");
*password = Some(prompt_password());
*password = Some(prompt_password(main_matcher));
} else {
println!("Ignoring password, it is not required");
*password = None;
@ -127,7 +138,15 @@ pub fn ensure_password(password: &mut Option<String>, needs: bool) {
/// The prompt that is shown should be passed to `msg`,
/// excluding the `:` suffix.
// TODO: do not prompt if no-interactive
pub fn prompt(msg: &str) -> String {
pub fn prompt(msg: &str, main_matcher: &MainMatcher) -> String {
// Quit with an error if we may not interact
if main_matcher.no_interact() {
quit_error(format_err!(
"Could not prompt for '{}', must be specified in no-interact mode",
msg,
).compat());
}
// Show the prompt
eprint!("{}: ", msg);
let _ = stderr().flush();
@ -145,8 +164,8 @@ pub fn prompt(msg: &str) -> String {
}
/// Prompt the user to enter an owner token.
pub fn prompt_owner_token() -> String {
prompt("Owner token")
pub fn prompt_owner_token(main_matcher: &MainMatcher) -> String {
prompt("Owner token", main_matcher)
}
/// Get the owner token.
@ -154,16 +173,26 @@ pub fn prompt_owner_token() -> String {
/// parameter.
///
/// This method will prompt the user for the token, if it wasn't set.
pub fn ensure_owner_token(token: &mut Option<String>) {
pub fn ensure_owner_token(
token: &mut Option<String>,
main_matcher: &MainMatcher,
) {
// Check whehter we allow interaction
let interact = !main_matcher.no_interact();
// Notify that an owner token is required
if token.is_none() {
if interact && token.is_none() {
println!("The file owner token is required for authentication.");
}
loop {
// Prompt for an owner token
if token.is_none() {
*token = Some(prompt_owner_token());
if interact {
*token = Some(prompt_owner_token(main_matcher));
} else {
quit_error(err_msg("Missing owner token, must be specified in no-interact mode").compat());
}
}
// The token must not be empty