Jelajahi Sumber

Create main matcher, implement no-interact logic

timvisee 7 tahun lalu
induk
melakukan
896c01b6ef

+ 0 - 2
IDEAS.md

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

+ 3 - 1
cli/src/action/delete.rs

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

+ 3 - 1
cli/src/action/download.rs

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

+ 4 - 2
cli/src/action/info.rs

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

+ 3 - 1
cli/src/action/params.rs

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

+ 3 - 1
cli/src/action/password.rs

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

+ 5 - 1
cli/src/cmd/arg/owner.rs

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

+ 5 - 1
cli/src/cmd/arg/password.rs

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

+ 35 - 0
cli/src/cmd/matcher/main.rs

@@ -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 - 0
cli/src/cmd/matcher/mod.rs

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

+ 1 - 0
cli/src/main.rs

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

+ 41 - 12
cli/src/util.rs

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