Browse Source

Add CLI parameters command, to change parameters of shared files

timvisee 7 years ago
parent
commit
b66a997d1e

+ 2 - 2
api/src/action/params.rs

@@ -20,11 +20,11 @@ const HEADER_AUTH_NONCE: &'static str = "WWW-Authenticate";
 const PARAMS_DEFAULT_DOWNLOAD: u8 = 1;
 
 /// The minimum allowed number of downloads, enforced by the server.
-const PARAMS_DOWNLOAD_MIN: u8 = 1;
+pub const PARAMS_DOWNLOAD_MIN: u8 = 1;
 
 /// The maximum (inclusive) allowed number of downloads,
 /// enforced by the server.
-const PARAMS_DOWNLOAD_MAX: u8 = 20;
+pub const PARAMS_DOWNLOAD_MAX: u8 = 20;
 
 /// An action to set parameters for a shared file.
 pub struct Params<'a> {

+ 1 - 0
cli/src/action/mod.rs

@@ -1,3 +1,4 @@
 pub mod download;
+pub mod params;
 pub mod password;
 pub mod upload;

+ 53 - 0
cli/src/action/params.rs

@@ -0,0 +1,53 @@
+use ffsend_api::action::params::{
+    Params as ApiParams,
+    ParamsData,
+};
+use ffsend_api::file::remote_file::RemoteFile;
+use ffsend_api::reqwest::Client;
+
+use cmd::cmd_params::CmdParams;
+use error::ActionError;
+use util::print_success;
+
+/// A file parameters action.
+pub struct Params<'a> {
+    cmd: &'a CmdParams<'a>,
+}
+
+impl<'a> Params<'a> {
+    /// Construct a new parameters action.
+    pub fn new(cmd: &'a CmdParams<'a>) -> Self {
+        Self {
+            cmd,
+        }
+    }
+
+    /// Invoke the parameters action.
+    // TODO: create a trait for this method
+    pub fn invoke(&self) -> Result<(), ActionError> {
+        // Get the share URL
+        let url = self.cmd.url();
+
+        // Create a reqwest client
+        let client = Client::new();
+
+        // Parse the remote file based on the share URL
+        // TODO: handle error here
+        let file = RemoteFile::parse_url(url, self.cmd.owner())?;
+
+        // TODO: show an informative error if the owner token isn't set
+
+        // Build the params data object
+        let data = ParamsData::from(self.cmd.downloads());
+
+        // TODO: make sure the data isn't empty
+
+        // Execute an password action
+        ApiParams::new(&file, data, None).invoke(&client)?;
+
+        // Print a success message
+        print_success("Parameters set");
+
+        Ok(())
+    }
+}

+ 1 - 2
cli/src/cmd/cmd_download.rs

@@ -16,8 +16,7 @@ impl<'a: 'b, 'b> CmdDownload<'a> {
     /// Build the sub command definition.
     pub fn build<'y, 'z>() -> App<'y, 'z> {
         // Build the subcommand
-        #[allow(unused_mut)]
-        let mut cmd = SubCommand::with_name("download")
+        let cmd = SubCommand::with_name("download")
             .about("Download files.")
             .visible_alias("d")
             .visible_alias("down")

+ 118 - 0
cli/src/cmd/cmd_params.rs

@@ -0,0 +1,118 @@
+use ffsend_api::action::params::{
+    // TODO: test min/max
+    PARAMS_DOWNLOAD_MIN as DOWNLOAD_MIN,
+    PARAMS_DOWNLOAD_MAX as DOWNLOAD_MAX,
+};
+use ffsend_api::url::{ParseError, Url};
+
+use super::clap::{App, Arg, ArgMatches, SubCommand};
+
+use util::quit_error_msg;
+
+/// The parameters command.
+pub struct CmdParams<'a> {
+    matches: &'a ArgMatches<'a>,
+}
+
+impl<'a: 'b, 'b> CmdParams<'a> {
+    /// Build the sub command definition.
+    pub fn build<'y, 'z>() -> App<'y, 'z> {
+        // Build a list of data parameter arguments of which one is required
+        let param_args = ["downloads"];
+
+        // Build the subcommand
+        let cmd = SubCommand::with_name("parameters")
+            .about("Change parameters of a shared file.")
+            .visible_alias("params")
+            .alias("par")
+            .alias("param")
+            .alias("parameter")
+            .arg(Arg::with_name("URL")
+                .help("The share URL")
+                .required(true)
+                .multiple(false))
+            .arg(Arg::with_name("owner")
+                .long("owner")
+                .short("o")
+                .alias("own")
+                .alias("owner-token")
+                .alias("token")
+                .value_name("TOKEN")
+                .help("File owner token"))
+            .arg(Arg::with_name("downloads")
+                .long("downloads")
+                .short("d")
+                .alias("download")
+                .alias("down")
+                .alias("dlimit")
+                .required_unless_one(&param_args)
+                .value_name("COUNT")
+                .help("Set the download limit parameter"));
+
+        cmd
+    }
+
+    /// Parse CLI arguments, from the given parent command matches.
+    pub fn parse(parent: &'a ArgMatches<'a>) -> Option<CmdParams<'a>> {
+        parent.subcommand_matches("parameters")
+            .map(|matches| CmdParams { matches })
+    }
+
+    /// Get the file share URL.
+    ///
+    /// This method parses the URL into an `Url`.
+    /// If the given URL is invalid,
+    /// the program will quit with an error message.
+    pub fn url(&'a self) -> Url {
+        // Get the host
+        let url = self.matches.value_of("URL")
+            .expect("missing URL");
+
+        // Parse the URL
+        // TODO: improve these error messages
+        match Url::parse(url) {
+            Ok(url) => url,
+            Err(ParseError::EmptyHost) =>
+                quit_error_msg("Emtpy host given"),
+            Err(ParseError::InvalidPort) =>
+                quit_error_msg("Invalid host port"),
+            Err(ParseError::InvalidIpv4Address) =>
+                quit_error_msg("Invalid IPv4 address in host"),
+            Err(ParseError::InvalidIpv6Address) =>
+                quit_error_msg("Invalid IPv6 address in host"),
+            Err(ParseError::InvalidDomainCharacter) =>
+                quit_error_msg("Host domains contains an invalid character"),
+            Err(ParseError::RelativeUrlWithoutBase) =>
+                quit_error_msg("Host domain doesn't contain a host"),
+            _ => quit_error_msg("The given host is invalid"),
+        }
+    }
+
+    /// Get the owner token.
+    pub fn owner(&'a self) -> Option<String> {
+        // TODO: validate the owner token if set
+        self.matches.value_of("owner")
+            .map(|token| token.to_owned())
+    }
+
+    /// Get the download counts.
+    pub fn downloads(&'a self) -> Option<u8> {
+        // Get the number of downloads
+        // TODO: do not unwrap, report an error
+        self.matches.value_of("downloads")
+            .map(|d| d.parse::<u8>().expect("invalid number of downloads"))
+            .and_then(|d| {
+                // Check the download count bounds
+                if d < DOWNLOAD_MIN || d > DOWNLOAD_MAX {
+                    panic!(
+                        "invalid number of downloads, must be between {} and {}",
+                        DOWNLOAD_MIN,
+                        DOWNLOAD_MAX,
+                    );
+                }
+
+                // Return the value
+                Some(d)
+            })
+    }
+}

+ 1 - 2
cli/src/cmd/cmd_password.rs

@@ -14,8 +14,7 @@ impl<'a: 'b, 'b> CmdPassword<'a> {
     /// Build the sub command definition.
     pub fn build<'y, 'z>() -> App<'y, 'z> {
         // Build the subcommand
-        #[allow(unused_mut)]
-        let mut cmd = SubCommand::with_name("password")
+        let cmd = SubCommand::with_name("password")
             .about("Change the password of a shared file.")
             .visible_alias("p")
             .visible_alias("pass")

+ 7 - 0
cli/src/cmd/handler.rs

@@ -3,6 +3,7 @@ use super::clap::{App, ArgMatches};
 use app::*;
 
 use super::cmd_download::CmdDownload;
+use super::cmd_params::CmdParams;
 use super::cmd_password::CmdPassword;
 use super::cmd_upload::CmdUpload;
 
@@ -21,6 +22,7 @@ impl<'a: 'b, 'b> Handler<'a> {
             .about(APP_ABOUT)
             .subcommand(CmdUpload::build().display_order(1))
             .subcommand(CmdDownload::build().display_order(2))
+            .subcommand(CmdParams::build())
             .subcommand(CmdPassword::build())
     }
 
@@ -42,6 +44,11 @@ impl<'a: 'b, 'b> Handler<'a> {
         CmdDownload::parse(&self.matches)
     }
 
+    /// Get the parameters sub command, if matched.
+    pub fn params(&'a self) -> Option<CmdParams<'a>> {
+        CmdParams::parse(&self.matches)
+    }
+
     /// Get the password sub command, if matched.
     pub fn password(&'a self) -> Option<CmdPassword<'a>> {
         CmdPassword::parse(&self.matches)

+ 1 - 0
cli/src/cmd/mod.rs

@@ -1,6 +1,7 @@
 extern crate clap;
 
 pub mod cmd_download;
+pub mod cmd_params;
 pub mod cmd_password;
 pub mod cmd_upload;
 pub mod handler;

+ 11 - 0
cli/src/error.rs

@@ -1,4 +1,5 @@
 use ffsend_api::action::download::Error as DownloadError;
+use ffsend_api::action::params::Error as ParamsError;
 use ffsend_api::action::password::Error as PasswordError;
 use ffsend_api::action::upload::Error as UploadError;
 use ffsend_api::file::remote_file::FileParseError;
@@ -27,6 +28,10 @@ pub enum ActionError {
     #[fail(display = "Failed to download the requested file")]
     Download(#[cause] DownloadError),
 
+    /// An error occurred while invoking the params action.
+    #[fail(display = "Failed to change the parameters")]
+    Params(#[cause] ParamsError),
+
     /// An error occurred while invoking the password action.
     #[fail(display = "Failed to change the password")]
     Password(#[cause] PasswordError),
@@ -43,6 +48,12 @@ impl From<DownloadError> for ActionError {
     }
 }
 
+impl From<ParamsError> for ActionError {
+    fn from(err: ParamsError) -> ActionError {
+        ActionError::Params(err)
+    }
+}
+
 impl From<PasswordError> for ActionError {
     fn from(err: PasswordError) -> ActionError {
         ActionError::Password(err)

+ 7 - 0
cli/src/main.rs

@@ -12,6 +12,7 @@ mod progress;
 mod util;
 
 use action::download::Download;
+use action::params::Params;
 use action::password::Password;
 use action::upload::Upload;
 use cmd::Handler;
@@ -46,6 +47,12 @@ fn invoke_action(handler: &Handler) -> Result<(), Error> {
             .map_err(|err| err.into());
     }
 
+    // Match the parameters command
+    if let Some(cmd) = handler.params() {
+        return Params::new(&cmd).invoke()
+            .map_err(|err| err.into());
+    }
+
     // Match the password command
     if let Some(cmd) = handler.password() {
         return Password::new(&cmd).invoke()