Create main matcher, implement no-interact logic
This commit is contained in:
parent
7760f56d1d
commit
896c01b6ef
12 changed files with 105 additions and 22 deletions
2
IDEAS.md
2
IDEAS.md
|
@ -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
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
35
cli/src/cmd/matcher/main.rs
Normal file
35
cli/src/cmd/matcher/main.rs
Normal 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,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate failure_derive;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue