Add CLI parameters command, to change parameters of shared files

This commit is contained in:
timvisee 2018-04-02 01:07:47 +02:00
parent 0cb40d177c
commit b66a997d1e
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
10 changed files with 202 additions and 6 deletions

View file

@ -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> {

View file

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

53
cli/src/action/params.rs Normal file
View file

@ -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(())
}
}

View file

@ -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
cli/src/cmd/cmd_params.rs Normal file
View file

@ -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)
})
}
}

View file

@ -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")

View file

@ -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)

View file

@ -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;

View file

@ -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)

View file

@ -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()