Reformat using rustfmt

This commit is contained in:
timvisee 2018-09-18 22:01:22 +02:00
parent 855da2c53b
commit 16d6a6a4ae
No known key found for this signature in database
GPG key ID: 109CBA0BF74036C2
39 changed files with 486 additions and 693 deletions

View file

@ -1,18 +1,9 @@
use chrono::Duration;
use clap::ArgMatches;
use ffsend_api::config::SEND_DEFAULT_EXPIRE_TIME;
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use prettytable::{cell::Cell, format::FormatBuilder, row::Row, Table};
use cmd::matcher::{
debug::DebugMatcher,
main::MainMatcher,
Matcher,
};
use cmd::matcher::{debug::DebugMatcher, main::MainMatcher, Matcher};
use error::ActionError;
use util::{features_list, format_bool, format_duration};
@ -24,9 +15,7 @@ pub struct Debug<'a> {
impl<'a> Debug<'a> {
/// Construct a new debug action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the debug action.
@ -56,7 +45,9 @@ impl<'a> Debug<'a> {
// The default host
table.add_row(Row::new(vec![
Cell::new("Default expiry:"),
Cell::new(&format_duration(Duration::seconds(SEND_DEFAULT_EXPIRE_TIME))),
Cell::new(&format_duration(Duration::seconds(
SEND_DEFAULT_EXPIRE_TIME,
))),
]));
// Render a list of compiled features

View file

@ -1,19 +1,9 @@
use clap::ArgMatches;
use ffsend_api::action::delete::{
Error as DeleteError,
Delete as ApiDelete,
};
use ffsend_api::file::remote_file::{
FileParseError,
RemoteFile,
};
use ffsend_api::action::delete::{Delete as ApiDelete, Error as DeleteError};
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
delete::DeleteMatcher,
main::MainMatcher,
};
use cmd::matcher::{delete::DeleteMatcher, main::MainMatcher, Matcher};
use error::ActionError;
#[cfg(feature = "history")]
use history_tool;
@ -27,9 +17,7 @@ pub struct Delete<'a> {
impl<'a> Delete<'a> {
/// Construct a new delete action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the delete action.

View file

@ -7,45 +7,23 @@ use std::sync::{Arc, Mutex};
use clap::ArgMatches;
use failure::Fail;
use ffsend_api::action::download::{
Download as ApiDownload,
Error as DownloadError,
};
use ffsend_api::action::exists::{
Error as ExistsError,
Exists as ApiExists,
};
use ffsend_api::action::metadata::{
Error as MetadataError,
Metadata as ApiMetadata,
};
use ffsend_api::action::download::{Download as ApiDownload, Error as DownloadError};
use ffsend_api::action::exists::{Error as ExistsError, Exists as ApiExists};
use ffsend_api::action::metadata::{Error as MetadataError, Metadata as ApiMetadata};
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
use ffsend_api::reader::ProgressReporter;
use ffsend_api::reqwest::Client;
#[cfg(feature = "archive")]
use tempfile::{
Builder as TempBuilder,
NamedTempFile,
};
use tempfile::{Builder as TempBuilder, NamedTempFile};
#[cfg(feature = "archive")]
use archive::archive::Archive;
use cmd::matcher::{
Matcher,
download::DownloadMatcher,
main::MainMatcher,
};
use cmd::matcher::{download::DownloadMatcher, main::MainMatcher, Matcher};
#[cfg(feature = "history")]
use history_tool;
use progress::ProgressBar;
use util::{
ensure_enough_space,
ensure_password,
ErrorHints,
prompt_yes,
quit,
quit_error,
quit_error_msg,
ensure_enough_space, ensure_password, prompt_yes, quit, quit_error, quit_error_msg, ErrorHints,
};
/// A file download action.
@ -56,9 +34,7 @@ pub struct Download<'a> {
impl<'a> Download<'a> {
/// Construct a new download action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the download action.
@ -95,11 +71,7 @@ impl<'a> Download<'a> {
ensure_password(&mut password, exists.has_password(), &matcher_main);
// Fetch the file metadata
let metadata = ApiMetadata::new(
&file,
password.clone(),
false,
).invoke(&client)?;
let metadata = ApiMetadata::new(&file, password.clone(), false).invoke(&client)?;
// A temporary archive file, only used when archiving
// The temporary file is stored here, to ensure it's lifetime exceeds the upload process
@ -110,7 +82,8 @@ impl<'a> Download<'a> {
#[cfg(feature = "archive")]
let mut extract = matcher_download.extract();
#[cfg(feature = "archive")] {
#[cfg(feature = "archive")]
{
// Ask to extract if downloading an archive
if !extract && metadata.metadata().is_archive() {
if prompt_yes(
@ -138,7 +111,8 @@ impl<'a> Download<'a> {
#[cfg(feature = "archive")]
let output_path = target.clone();
#[cfg(feature = "archive")] {
#[cfg(feature = "archive")]
{
// Allocate an archive file, and update the download and target paths
if extract {
// TODO: select the extention dynamically
@ -150,7 +124,7 @@ impl<'a> Download<'a> {
.prefix(&format!(".{}-archive-", crate_name!()))
.suffix(archive_extention)
.tempfile()
.map_err(ExtractError::TempFile)?
.map_err(ExtractError::TempFile)?,
);
if let Some(tmp_archive) = &tmp_archive {
target = tmp_archive.path().to_path_buf();
@ -168,16 +142,12 @@ impl<'a> Download<'a> {
let progress_reader: Arc<Mutex<ProgressReporter>> = progress_bar;
// Execute an download action
ApiDownload::new(
&file,
target,
password,
false,
Some(metadata),
).invoke(&client, &progress_reader)?;
ApiDownload::new(&file, target, password, false, Some(metadata))
.invoke(&client, &progress_reader)?;
// Extract the downloaded file if working with an archive
#[cfg(feature = "archive")] {
#[cfg(feature = "archive")]
{
if extract {
eprintln!("Extracting...");
@ -243,10 +213,7 @@ impl<'a> Download<'a> {
let dir = if file {
match target.parent() {
Some(parent) => parent,
None => quit_error_msg(
"invalid output file path",
ErrorHints::default(),
),
None => quit_error_msg("invalid output file path", ErrorHints::default()),
}
} else {
&target
@ -268,9 +235,10 @@ impl<'a> Download<'a> {
// Create the parent directories
if let Err(err) = create_dir_all(dir) {
quit_error(err.context(
"failed to create parent directories for output file",
), ErrorHints::default());
quit_error(
err.context("failed to create parent directories for output file"),
ErrorHints::default(),
);
}
}
}
@ -314,15 +282,14 @@ impl<'a> Download<'a> {
let path = target.to_str();
// If the path is emtpy, use the working directory with the name hint
let use_workdir = path
.map(|path| path.trim().is_empty())
.unwrap_or(true);
let use_workdir = path.map(|path| path.trim().is_empty()).unwrap_or(true);
if use_workdir {
match current_dir() {
Ok(target) => return target.join(name_hint),
Err(err) => quit_error(err.context(
"failed to determine working directory to use for the output file"
), ErrorHints::default()),
Err(err) => quit_error(
err.context("failed to determine working directory to use for the output file"),
ErrorHints::default(),
),
}
}
let path = path.unwrap();
@ -339,9 +306,10 @@ impl<'a> Download<'a> {
if target.is_relative() {
match current_dir() {
Ok(workdir) => target = workdir.join(target),
Err(err) => quit_error(err.context(
"failed to determine working directory to use for the output file"
), ErrorHints::default()),
Err(err) => quit_error(
err.context("failed to determine working directory to use for the output file"),
ErrorHints::default(),
),
}
}

View file

@ -1,14 +1,11 @@
use clap::ArgMatches;
use ffsend_api::action::exists::{
Error as ExistsError,
Exists as ApiExists,
};
use ffsend_api::action::exists::{Error as ExistsError, Exists as ApiExists};
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
use ffsend_api::reqwest::Client;
use cmd::matcher::{Matcher, exists::ExistsMatcher};
#[cfg(feature = "history")]
use cmd::matcher::main::MainMatcher;
use cmd::matcher::{exists::ExistsMatcher, Matcher};
use error::ActionError;
#[cfg(feature = "history")]
use history_tool;
@ -21,9 +18,7 @@ pub struct Exists<'a> {
impl<'a> Exists<'a> {
/// Construct a new exists action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the exists action.
@ -44,8 +39,7 @@ impl<'a> Exists<'a> {
let file = RemoteFile::parse_url(url, None)?;
// Make sure the file exists
let exists_response = ApiExists::new(&file)
.invoke(&client)?;
let exists_response = ApiExists::new(&file).invoke(&client)?;
let exists = exists_response.exists();
// Print the results

View file

@ -1,20 +1,9 @@
use clap::ArgMatches;
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use prettytable::{cell::Cell, format::FormatBuilder, row::Row, Table};
use cmd::matcher::{
Matcher,
main::MainMatcher,
};
use cmd::matcher::{main::MainMatcher, Matcher};
use error::ActionError;
use history::{
History as HistoryManager,
LoadError as HistoryLoadError,
};
use history::{History as HistoryManager, LoadError as HistoryLoadError};
use util::format_duration;
/// A history action.
@ -25,9 +14,7 @@ pub struct History<'a> {
impl<'a> History<'a> {
/// Construct a new history action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the history action.

View file

@ -1,42 +1,17 @@
use chrono::Duration;
use clap::ArgMatches;
use failure::Fail;
use ffsend_api::action::exists::{
Error as ExistsError,
Exists as ApiExists,
};
use ffsend_api::action::info::{
Error as InfoError,
Info as ApiInfo,
};
use ffsend_api::action::exists::{Error as ExistsError, Exists as ApiExists};
use ffsend_api::action::info::{Error as InfoError, Info as ApiInfo};
use ffsend_api::action::metadata::Metadata as ApiMetadata;
use ffsend_api::file::remote_file::{
FileParseError,
RemoteFile,
};
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
use ffsend_api::reqwest::Client;
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use prettytable::{cell::Cell, format::FormatBuilder, row::Row, Table};
use cmd::matcher::{
Matcher,
info::InfoMatcher,
main::MainMatcher,
};
use cmd::matcher::{info::InfoMatcher, main::MainMatcher, Matcher};
#[cfg(feature = "history")]
use history_tool;
use util::{
ensure_owner_token,
ensure_password,
format_bytes,
format_duration,
print_error,
};
use util::{ensure_owner_token, ensure_password, format_bytes, format_duration, print_error};
/// A file info action.
pub struct Info<'a> {
@ -46,9 +21,7 @@ pub struct Info<'a> {
impl<'a> Info<'a> {
/// Construct a new info action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the info action.
@ -92,10 +65,9 @@ impl<'a> Info<'a> {
let info = ApiInfo::new(&file, None).invoke(&client)?;
let metadata = ApiMetadata::new(&file, password, false)
.invoke(&client)
.map_err(|err| print_error(err.context(
"failed to fetch file metadata, showing limited info",
)))
.ok();
.map_err(|err| {
print_error(err.context("failed to fetch file metadata, showing limited info"))
}).ok();
// Get the TTL duration
let ttl_millis = info.ttl_millis() as i64;
@ -113,10 +85,7 @@ impl<'a> Info<'a> {
table.set_format(FormatBuilder::new().padding(0, 2).build());
// Add the ID
table.add_row(Row::new(vec![
Cell::new("ID:"),
Cell::new(file.id()),
]));
table.add_row(Row::new(vec![Cell::new("ID:"), Cell::new(file.id())]));
// Metadata related details
if let Some(metadata) = metadata {
@ -130,13 +99,11 @@ impl<'a> Info<'a> {
let size = metadata.size();
table.add_row(Row::new(vec![
Cell::new("Size:"),
Cell::new(
&if size >= 1024 {
format!("{} ({} B)", format_bytes(size), size)
} else {
format_bytes(size)
}
),
Cell::new(&if size >= 1024 {
format!("{} ({} B)", format_bytes(size), size)
} else {
format_bytes(size)
}),
]));
// The file MIME
@ -149,19 +116,21 @@ impl<'a> Info<'a> {
// The download count
table.add_row(Row::new(vec![
Cell::new("Downloads:"),
Cell::new(&format!("{} of {}", info.download_count(), info.download_limit())),
Cell::new(&format!(
"{} of {}",
info.download_count(),
info.download_limit()
)),
]));
// The time to live
table.add_row(Row::new(vec![
Cell::new("Expiry:"),
Cell::new(
&if ttl_millis >= 60 * 1000 {
format!("{} ({}s)", format_duration(&ttl), ttl.num_seconds())
} else {
format_duration(&ttl)
}
),
Cell::new(&if ttl_millis >= 60 * 1000 {
format!("{} ({}s)", format_duration(&ttl), ttl.num_seconds())
} else {
format_duration(&ttl)
}),
]));
// Print the info table

View file

@ -1,17 +1,9 @@
use clap::ArgMatches;
use ffsend_api::action::params::{
Error as ParamsError,
Params as ApiParams,
ParamsDataBuilder,
};
use ffsend_api::action::params::{Error as ParamsError, Params as ApiParams, ParamsDataBuilder};
use ffsend_api::file::remote_file::RemoteFile;
use ffsend_api::reqwest::Client;
use cmd::matcher::{
Matcher,
main::MainMatcher,
params::ParamsMatcher,
};
use cmd::matcher::{main::MainMatcher, params::ParamsMatcher, Matcher};
use error::ActionError;
#[cfg(feature = "history")]
use history_tool;
@ -25,9 +17,7 @@ pub struct Params<'a> {
impl<'a> Params<'a> {
/// Construct a new parameters action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the parameters action.

View file

@ -1,22 +1,10 @@
use clap::ArgMatches;
use ffsend_api::action::password::{
Error as PasswordError,
Password as ApiPassword,
};
use ffsend_api::action::password::{Error as PasswordError, Password as ApiPassword};
use ffsend_api::file::remote_file::RemoteFile;
use ffsend_api::reqwest::Client;
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use prettytable::{cell::Cell, format::FormatBuilder, row::Row, Table};
use cmd::matcher::{
main::MainMatcher,
Matcher,
password::PasswordMatcher,
};
use cmd::matcher::{main::MainMatcher, password::PasswordMatcher, Matcher};
use error::ActionError;
#[cfg(feature = "history")]
use history_tool;
@ -30,9 +18,7 @@ pub struct Password<'a> {
impl<'a> Password<'a> {
/// Construct a new password action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the password action.
@ -60,11 +46,7 @@ impl<'a> Password<'a> {
let (password, password_generated) = matcher_password.password();
// Execute an password action
let result = ApiPassword::new(
&file,
&password,
None,
).invoke(&client);
let result = ApiPassword::new(&file, &password, None).invoke(&client);
if let Err(PasswordError::Expired) = result {
// Remove the file from the history if expired
#[cfg(feature = "history")]

View file

@ -7,43 +7,26 @@ use std::sync::{Arc, Mutex};
use clap::ArgMatches;
use failure::Fail;
use ffsend_api::action::params::ParamsDataBuilder;
use ffsend_api::action::upload::{
Error as UploadError,
Upload as ApiUpload,
};
use ffsend_api::action::upload::{Error as UploadError, Upload as ApiUpload};
use ffsend_api::config::{UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_RECOMMENDED};
use ffsend_api::reader::ProgressReporter;
use ffsend_api::reqwest::Client;
use prettytable::{
cell::Cell,
format::FormatBuilder,
row::Row,
Table,
};
use prettytable::{cell::Cell, format::FormatBuilder, row::Row, Table};
#[cfg(feature = "archive")]
use tempfile::{
Builder as TempBuilder,
NamedTempFile,
};
use tempfile::{Builder as TempBuilder, NamedTempFile};
#[cfg(feature = "archive")]
use archive::archiver::Archiver;
use cmd::matcher::{Matcher, MainMatcher, UploadMatcher};
use cmd::matcher::{MainMatcher, Matcher, UploadMatcher};
#[cfg(feature = "history")]
use history_tool;
use progress::ProgressBar;
use util::{
ErrorHintsBuilder,
format_bytes,
open_url,
print_error,
print_error_msg,
prompt_yes,
quit,
quit_error_msg,
};
#[cfg(feature = "clipboard")]
use util::set_clipboard;
use util::{
format_bytes, open_url, print_error, print_error_msg, prompt_yes, quit, quit_error_msg,
ErrorHintsBuilder,
};
/// A file upload action.
pub struct Upload<'a> {
@ -53,9 +36,7 @@ pub struct Upload<'a> {
impl<'a> Upload<'a> {
/// Construct a new upload action.
pub fn new(cmd_matches: &'a ArgMatches<'a>) -> Self {
Self {
cmd_matches,
}
Self { cmd_matches }
}
/// Invoke the upload action.
@ -141,13 +122,14 @@ impl<'a> Upload<'a> {
#[cfg(feature = "archive")]
let mut tmp_archive: Option<NamedTempFile> = None;
#[cfg(feature = "archive")] {
#[cfg(feature = "archive")]
{
// Determine whether to archive, ask if a directory was selected
let mut archive = matcher_upload.archive();
if !archive && path.is_dir() {
if prompt_yes(
"You've selected a directory, only a single file may be uploaded.\n\
Archive the directory into a single file?",
Archive the directory into a single file?",
Some(true),
&matcher_main,
) {
@ -166,12 +148,13 @@ impl<'a> Upload<'a> {
.prefix(&format!(".{}-archive-", crate_name!()))
.suffix(archive_extention)
.tempfile()
.map_err(ArchiveError::TempFile)?
.map_err(ArchiveError::TempFile)?,
);
if let Some(tmp_archive) = &tmp_archive {
// Get the path, and the actual file
let archive_path = tmp_archive.path().to_path_buf();
let archive_file = tmp_archive.as_file()
let archive_file = tmp_archive
.as_file()
.try_clone()
.map_err(ArchiveError::CloneHandle)?;
@ -184,13 +167,14 @@ impl<'a> Upload<'a> {
.ok_or(ArchiveError::FileName(None))?
.to_str()
.map(|s| s.to_owned())
.ok_or(ArchiveError::FileName(None))?
.ok_or(ArchiveError::FileName(None))?,
);
}
// Build an archiver and append the file
let mut archiver = Archiver::new(archive_file);
archiver.append_path(file_name.as_ref().unwrap(), &path)
archiver
.append_path(file_name.as_ref().unwrap(), &path)
.map_err(ArchiveError::AddFile)?;
// Finish the archival process, writes the archive file
@ -210,18 +194,12 @@ impl<'a> Upload<'a> {
// Get the password to use and whether it was generated
let password = matcher_upload.password();
let (password, password_generated) = password
.map(|(p, g)| (Some(p), g))
.unwrap_or((None, false));
let (password, password_generated) =
password.map(|(p, g)| (Some(p), g)).unwrap_or((None, false));
// Execute an upload action
let file = ApiUpload::new(
host,
path.clone(),
file_name,
password.clone(),
params,
).invoke(&client, &progress_reporter)?;
let file = ApiUpload::new(host, path.clone(), file_name, password.clone(), params)
.invoke(&client, &progress_reporter)?;
// Get the download URL, and report it in the console in a table
let url = file.download_url(true);
@ -252,27 +230,30 @@ impl<'a> Upload<'a> {
// Open the URL in the browser
if matcher_upload.open() {
if let Err(err) = open_url(&url) {
print_error(
err.context("failed to open the share link in the browser")
);
print_error(err.context("failed to open the share link in the browser"));
};
}
// Copy the URL in the user's clipboard
#[cfg(feature = "clipboard")] {
#[cfg(feature = "clipboard")]
{
if matcher_upload.copy() {
if let Err(err) = set_clipboard(url.as_str().to_owned()) {
print_error(err.context("failed to copy the share link to the clipboard, ignoring"));
print_error(
err.context("failed to copy the share link to the clipboard, ignoring"),
);
}
}
}
#[cfg(feature = "archive")] {
#[cfg(feature = "archive")]
{
// Close the temporary zip file, to ensure it's removed
if let Some(tmp_archive) = tmp_archive.take() {
if let Err(err) = tmp_archive.close() {
print_error(
err.context("failed to clean up temporary archive file, ignoring").compat(),
err.context("failed to clean up temporary archive file, ignoring")
.compat(),
);
}
}

View file

@ -1,7 +1,4 @@
use std::io::{
Error as IoError,
Read,
};
use std::io::{Error as IoError, Read};
use std::path::Path;
use super::tar::Archive as TarArchive;

View file

@ -1,8 +1,5 @@
use std::fs::File;
use std::io::{
Error as IoError,
Write,
};
use std::io::{Error as IoError, Write};
use std::path::Path;
use super::tar::Builder as TarBuilder;
@ -29,9 +26,9 @@ impl<W: Write> Archiver<W> {
///
/// If no entry exists at the given `src_path`, an error is returned.
pub fn append_path<P, Q>(&mut self, path: P, src_path: Q) -> Result<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
// Append the path as file or directory
if src_path.as_ref().is_file() {
@ -46,8 +43,8 @@ impl<W: Write> Archiver<W> {
/// Append a file to the archive builder.
pub fn append_file<P>(&mut self, path: P, file: &mut File) -> Result<()>
where
P: AsRef<Path>,
where
P: AsRef<Path>,
{
self.inner.append_file(path, file)
}
@ -55,9 +52,9 @@ impl<W: Write> Archiver<W> {
/// Append a directory to the archive builder.
// TODO: Define a flag to add recursively or not
pub fn append_dir<P, Q>(&mut self, path: P, src_path: Q) -> Result<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
self.inner.append_dir_all(path, src_path)
}

View file

@ -1,15 +1,14 @@
use clap::{Arg, ArgMatches};
use ffsend_api::action::params::{
PARAMS_DOWNLOAD_MIN as DOWNLOAD_MIN,
PARAMS_DOWNLOAD_MAX as DOWNLOAD_MAX,
PARAMS_DOWNLOAD_MAX as DOWNLOAD_MAX, PARAMS_DOWNLOAD_MIN as DOWNLOAD_MIN,
};
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use util::{ErrorHintsBuilder, quit_error_msg};
use util::{quit_error_msg, ErrorHintsBuilder};
/// The download limit argument.
pub struct ArgDownloadLimit { }
pub struct ArgDownloadLimit {}
impl CmdArg for ArgDownloadLimit {
fn name() -> &'static str {
@ -27,7 +26,7 @@ impl CmdArg for ArgDownloadLimit {
}
}
impl CmdArgFlag for ArgDownloadLimit { }
impl CmdArgFlag for ArgDownloadLimit {}
impl<'a> CmdArgOption<'a> for ArgDownloadLimit {
type Value = Option<u8>;
@ -43,8 +42,7 @@ impl<'a> CmdArgOption<'a> for ArgDownloadLimit {
quit_error_msg(
format!(
"invalid download limit, must be between {} and {}",
DOWNLOAD_MIN,
DOWNLOAD_MAX,
DOWNLOAD_MIN, DOWNLOAD_MAX,
),
ErrorHintsBuilder::default()
.force(false)

View file

@ -1,10 +1,10 @@
use clap::Arg;
use chbs;
use clap::Arg;
use super::{CmdArg, CmdArgFlag};
/// The passphrase generation argument.
pub struct ArgGenPassphrase { }
pub struct ArgGenPassphrase {}
impl ArgGenPassphrase {
/// Generate a cryptographically secure passphrase that is easily
@ -30,4 +30,4 @@ impl CmdArg for ArgGenPassphrase {
}
}
impl CmdArgFlag for ArgGenPassphrase { }
impl CmdArgFlag for ArgGenPassphrase {}

View file

@ -3,12 +3,12 @@ use failure::Fail;
use ffsend_api::config::SEND_DEFAULT_HOST;
use ffsend_api::url::Url;
use host::parse_host;
use super::{CmdArg, CmdArgOption};
use util::{ErrorHints, quit_error};
use host::parse_host;
use util::{quit_error, ErrorHints};
/// The host argument.
pub struct ArgHost { }
pub struct ArgHost {}
impl CmdArg for ArgHost {
fn name() -> &'static str {

View file

@ -47,5 +47,6 @@ pub trait CmdArgOption<'a>: CmdArg {
/// Get the raw argument value, as a string reference.
fn value_raw<'b: 'a>(matches: &'a ArgMatches<'b>) -> Option<&'a str> {
matches.value_of(Self::name()) }
matches.value_of(Self::name())
}
}

View file

@ -1,11 +1,11 @@
use clap::{Arg, ArgMatches};
use cmd::matcher::{MainMatcher, Matcher};
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use cmd::matcher::{MainMatcher, Matcher};
use util::prompt_owner_token;
/// The owner argument.
pub struct ArgOwner { }
pub struct ArgOwner {}
impl CmdArg for ArgOwner {
fn name() -> &'static str {
@ -24,7 +24,7 @@ impl CmdArg for ArgOwner {
}
}
impl CmdArgFlag for ArgOwner { }
impl CmdArgFlag for ArgOwner {}
impl<'a> CmdArgOption<'a> for ArgOwner {
type Value = Option<String>;
@ -37,7 +37,7 @@ impl<'a> CmdArgOption<'a> for ArgOwner {
// Get the owner token from the argument if set
match Self::value_raw(matches) {
None => {},
None => {}
p => return p.map(|p| p.into()),
}

View file

@ -1,11 +1,11 @@
use clap::{Arg, ArgMatches};
use cmd::matcher::{MainMatcher, Matcher};
use super::{CmdArg, CmdArgFlag, CmdArgOption};
use cmd::matcher::{MainMatcher, Matcher};
use util::{check_empty_password, prompt_password};
/// The password argument.
pub struct ArgPassword { }
pub struct ArgPassword {}
impl CmdArg for ArgPassword {
fn name() -> &'static str {
@ -23,7 +23,7 @@ impl CmdArg for ArgPassword {
}
}
impl CmdArgFlag for ArgPassword { }
impl CmdArgFlag for ArgPassword {}
impl<'a> CmdArgOption<'a> for ArgPassword {
type Value = Option<String>;

View file

@ -2,12 +2,12 @@ use clap::{Arg, ArgMatches};
use failure::Fail;
use ffsend_api::url::Url;
use host::parse_host;
use super::{CmdArg, CmdArgOption};
use util::{ErrorHints, quit_error};
use host::parse_host;
use util::{quit_error, ErrorHints};
/// The URL argument.
pub struct ArgUrl { }
pub struct ArgUrl {}
impl CmdArg for ArgUrl {
fn name() -> &'static str {

View file

@ -2,31 +2,17 @@ extern crate directories;
use clap::{App, AppSettings, Arg, ArgMatches};
use super::matcher::{
DebugMatcher,
DeleteMatcher,
DownloadMatcher,
ExistsMatcher,
InfoMatcher,
Matcher,
ParamsMatcher,
PasswordMatcher,
UploadMatcher,
};
#[cfg(feature = "history")]
use super::matcher::HistoryMatcher;
use super::subcmd::{
CmdDebug,
CmdDelete,
CmdDownload,
CmdExists,
CmdInfo,
CmdParams,
CmdPassword,
CmdUpload,
use super::matcher::{
DebugMatcher, DeleteMatcher, DownloadMatcher, ExistsMatcher, InfoMatcher, Matcher,
ParamsMatcher, PasswordMatcher, UploadMatcher,
};
#[cfg(feature = "history")]
use super::subcmd::CmdHistory;
use super::subcmd::{
CmdDebug, CmdDelete, CmdDownload, CmdExists, CmdInfo, CmdParams, CmdPassword, CmdUpload,
};
#[cfg(feature = "history")]
use util::app_history_file_path_string;
@ -50,38 +36,43 @@ impl<'a: 'b, 'b> Handler<'a> {
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.after_help("\
The public Send service that is used as default host is provided by Mozilla.\n\
This application is not affiliated with Mozilla, Firefox or Firefox Send.\
")
.global_setting(AppSettings::GlobalVersion)
.after_help(
"\
The public Send service that is used as default host is provided by Mozilla.\n\
This application is not affiliated with Mozilla, Firefox or Firefox Send.\
",
).global_setting(AppSettings::GlobalVersion)
.global_setting(AppSettings::VersionlessSubcommands)
// TODO: enable below command when it doesn't break `p` anymore.
// .global_setting(AppSettings::InferSubcommands)
.arg(Arg::with_name("force")
.long("force")
.short("f")
.global(true)
.help("Force the action, ignore warnings"))
.arg(Arg::with_name("no-interact")
.long("no-interact")
.short("I")
.alias("no-interactive")
.global(true)
.help("Not interactive, do not prompt"))
.arg(Arg::with_name("yes")
.long("yes")
.short("y")
.alias("assume-yes")
.global(true)
.help("Assume yes for prompts"))
.arg(Arg::with_name("verbose")
.long("verbose")
.short("v")
.multiple(true)
.global(true)
.help("Enable verbose information and logging"))
.subcommand(CmdDebug::build())
.arg(
Arg::with_name("force")
.long("force")
.short("f")
.global(true)
.help("Force the action, ignore warnings"),
).arg(
Arg::with_name("no-interact")
.long("no-interact")
.short("I")
.alias("no-interactive")
.global(true)
.help("Not interactive, do not prompt"),
).arg(
Arg::with_name("yes")
.long("yes")
.short("y")
.alias("assume-yes")
.global(true)
.help("Assume yes for prompts"),
).arg(
Arg::with_name("verbose")
.long("verbose")
.short("v")
.multiple(true)
.global(true)
.help("Enable verbose information and logging"),
).subcommand(CmdDebug::build())
.subcommand(CmdDelete::build())
.subcommand(CmdDownload::build().display_order(2))
.subcommand(CmdExists::build())
@ -92,25 +83,28 @@ impl<'a: 'b, 'b> Handler<'a> {
// With history support, a flag for the history file and incognito mode
#[cfg(feature = "history")]
let app = app.arg(Arg::with_name("history")
.long("history")
.short("H")
.value_name("FILE")
.global(true)
.help("Use the specified history file")
.default_value(&DEFAULT_HISTORY_FILE)
.hide_default_value(true)
.env("FFSEND_HISTORY")
.hide_env_values(true))
.arg(Arg::with_name("incognito")
.long("incognito")
.short("i")
.alias("incog")
.alias("private")
.alias("priv")
.global(true)
.help("Don't update local history for actions"))
.subcommand(CmdHistory::build());
let app = app
.arg(
Arg::with_name("history")
.long("history")
.short("H")
.value_name("FILE")
.global(true)
.help("Use the specified history file")
.default_value(&DEFAULT_HISTORY_FILE)
.hide_default_value(true)
.env("FFSEND_HISTORY")
.hide_env_values(true),
).arg(
Arg::with_name("incognito")
.long("incognito")
.short("i")
.alias("incog")
.alias("private")
.alias("priv")
.global(true)
.help("Don't update local history for actions"),
).subcommand(CmdHistory::build());
// Disable color usage if compiled without color support
#[cfg(feature = "no-color")]

View file

@ -1,8 +1,8 @@
use clap::ArgMatches;
use ffsend_api::url::Url;
use cmd::arg::{ArgHost, CmdArgOption};
use super::Matcher;
use cmd::arg::{ArgHost, CmdArgOption};
/// The debug command matcher.
pub struct DebugMatcher<'a> {
@ -22,11 +22,8 @@ impl<'a: 'b, 'b> DebugMatcher<'a> {
impl<'a> Matcher<'a> for DebugMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("debug")
.map(|matches|
DebugMatcher {
matches,
}
)
matches
.subcommand_matches("debug")
.map(|matches| DebugMatcher { matches })
}
}

View file

@ -1,8 +1,8 @@
use clap::ArgMatches;
use ffsend_api::url::Url;
use cmd::arg::{ArgOwner, ArgUrl, CmdArgOption};
use super::Matcher;
use cmd::arg::{ArgOwner, ArgUrl, CmdArgOption};
/// The delete command matcher.
pub struct DeleteMatcher<'a> {
@ -22,18 +22,14 @@ impl<'a: 'b, 'b> DeleteMatcher<'a> {
/// Get the owner token.
pub fn owner(&'a self) -> Option<String> {
// TODO: just return a string reference here?
ArgOwner::value(self.matches)
.map(|token| token.to_owned())
ArgOwner::value(self.matches).map(|token| token.to_owned())
}
}
impl<'a> Matcher<'a> for DeleteMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("delete")
.map(|matches|
DeleteMatcher {
matches,
}
)
matches
.subcommand_matches("delete")
.map(|matches| DeleteMatcher { matches })
}
}

View file

@ -3,8 +3,8 @@ use std::path::PathBuf;
use clap::ArgMatches;
use ffsend_api::url::Url;
use cmd::arg::{ArgPassword, ArgUrl, CmdArgOption};
use super::Matcher;
use cmd::arg::{ArgPassword, ArgUrl, CmdArgOption};
#[cfg(feature = "archive")]
use util::env_var_present;
@ -33,7 +33,8 @@ impl<'a: 'b, 'b> DownloadMatcher<'a> {
/// If a directory is given, the file name of the original uploaded file
/// will be used.
pub fn output(&'a self) -> PathBuf {
self.matches.value_of("output")
self.matches
.value_of("output")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("./"))
}
@ -47,11 +48,8 @@ impl<'a: 'b, 'b> DownloadMatcher<'a> {
impl<'a> Matcher<'a> for DownloadMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("download")
.map(|matches|
DownloadMatcher {
matches,
}
)
matches
.subcommand_matches("download")
.map(|matches| DownloadMatcher { matches })
}
}

View file

@ -2,8 +2,8 @@ use ffsend_api::url::Url;
use clap::ArgMatches;
use cmd::arg::{ArgUrl, CmdArgOption};
use super::Matcher;
use cmd::arg::{ArgUrl, CmdArgOption};
/// The exists command matcher.
pub struct ExistsMatcher<'a> {
@ -23,11 +23,8 @@ impl<'a: 'b, 'b> ExistsMatcher<'a> {
impl<'a> Matcher<'a> for ExistsMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("exists")
.map(|matches|
ExistsMatcher {
matches,
}
)
matches
.subcommand_matches("exists")
.map(|matches| ExistsMatcher { matches })
}
}

View file

@ -10,11 +10,8 @@ pub struct HistoryMatcher<'a> {
impl<'a> Matcher<'a> for HistoryMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("history")
.map(|matches|
HistoryMatcher {
matches,
}
)
matches
.subcommand_matches("history")
.map(|matches| HistoryMatcher { matches })
}
}

View file

@ -2,8 +2,8 @@ use ffsend_api::url::Url;
use clap::ArgMatches;
use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArgOption};
use super::Matcher;
use cmd::arg::{ArgOwner, ArgPassword, ArgUrl, CmdArgOption};
/// The info command matcher.
pub struct InfoMatcher<'a> {
@ -23,8 +23,7 @@ impl<'a: 'b, 'b> InfoMatcher<'a> {
/// Get the owner token.
pub fn owner(&'a self) -> Option<String> {
// TODO: just return a string reference here?
ArgOwner::value(self.matches)
.map(|token| token.to_owned())
ArgOwner::value(self.matches).map(|token| token.to_owned())
}
/// Get the password.
@ -36,11 +35,8 @@ impl<'a: 'b, 'b> InfoMatcher<'a> {
impl<'a> Matcher<'a> for InfoMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("info")
.map(|matches|
InfoMatcher {
matches,
}
)
matches
.subcommand_matches("info")
.map(|matches| InfoMatcher { matches })
}
}

View file

@ -6,7 +6,7 @@ use clap::ArgMatches;
use super::Matcher;
use util::env_var_present;
#[cfg(feature = "history")]
use util::{ErrorHintsBuilder, quit_error_msg};
use util::{quit_error_msg, ErrorHintsBuilder};
/// The main command matcher.
pub struct MainMatcher<'a> {
@ -33,8 +33,7 @@ impl<'a: 'b, 'b> MainMatcher<'a> {
#[cfg(feature = "history")]
pub fn history(&self) -> PathBuf {
// Get the path
let path = self.matches.value_of("history")
.map(PathBuf::from);
let path = self.matches.value_of("history").map(PathBuf::from);
// Ensure the path is correct
match path {
@ -64,10 +63,6 @@ impl<'a: 'b, 'b> MainMatcher<'a> {
impl<'a> Matcher<'a> for MainMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
Some(
MainMatcher {
matches,
}
)
Some(MainMatcher { matches })
}
}

View file

@ -1,8 +1,8 @@
use clap::ArgMatches;
use ffsend_api::url::Url;
use cmd::arg::{ArgDownloadLimit, ArgOwner, ArgUrl, CmdArgOption};
use super::Matcher;
use cmd::arg::{ArgDownloadLimit, ArgOwner, ArgUrl, CmdArgOption};
/// The params command matcher.
pub struct ParamsMatcher<'a> {
@ -22,8 +22,7 @@ impl<'a: 'b, 'b> ParamsMatcher<'a> {
/// Get the owner token.
pub fn owner(&'a self) -> Option<String> {
// TODO: just return a string reference here?
ArgOwner::value(self.matches)
.map(|token| token.to_owned())
ArgOwner::value(self.matches).map(|token| token.to_owned())
}
/// Get the download limit.
@ -34,11 +33,8 @@ impl<'a: 'b, 'b> ParamsMatcher<'a> {
impl<'a> Matcher<'a> for ParamsMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("parameters")
.map(|matches|
ParamsMatcher {
matches,
}
)
matches
.subcommand_matches("parameters")
.map(|matches| ParamsMatcher { matches })
}
}

View file

@ -2,14 +2,7 @@ use clap::ArgMatches;
use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
use cmd::arg::{
ArgGenPassphrase,
ArgOwner,
ArgPassword,
ArgUrl,
CmdArgFlag,
CmdArgOption,
};
use cmd::arg::{ArgGenPassphrase, ArgOwner, ArgPassword, ArgUrl, CmdArgFlag, CmdArgOption};
use cmd::matcher::{MainMatcher, Matcher};
use util::check_empty_password;
@ -31,8 +24,7 @@ impl<'a: 'b, 'b> PasswordMatcher<'a> {
/// Get the owner token.
pub fn owner(&'a self) -> Option<String> {
// TODO: just return a string reference here?
ArgOwner::value(self.matches)
.map(|token| token.to_owned())
ArgOwner::value(self.matches).map(|token| token.to_owned())
}
/// Get the password.
@ -54,7 +46,7 @@ impl<'a: 'b, 'b> PasswordMatcher<'a> {
// TODO: create utility function for this
prompt_password_stderr("New password: ")
.expect("failed to read password from stdin")
},
}
};
// Create a main matcher
@ -69,11 +61,8 @@ impl<'a: 'b, 'b> PasswordMatcher<'a> {
impl<'a> Matcher<'a> for PasswordMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("password")
.map(|matches|
PasswordMatcher {
matches,
}
)
matches
.subcommand_matches("password")
.map(|matches| PasswordMatcher { matches })
}
}

View file

@ -1,19 +1,12 @@
use clap::ArgMatches;
use ffsend_api::action::params::{
PARAMS_DEFAULT_DOWNLOAD as DOWNLOAD_DEFAULT,
};
use ffsend_api::action::params::PARAMS_DEFAULT_DOWNLOAD as DOWNLOAD_DEFAULT;
use ffsend_api::url::Url;
use cmd::arg::{
ArgDownloadLimit,
ArgGenPassphrase,
ArgHost,
ArgPassword,
CmdArgFlag,
CmdArgOption,
};
use super::Matcher;
use util::{env_var_present, ErrorHintsBuilder, quit_error_msg};
use cmd::arg::{
ArgDownloadLimit, ArgGenPassphrase, ArgHost, ArgPassword, CmdArgFlag, CmdArgOption,
};
use util::{env_var_present, quit_error_msg, ErrorHintsBuilder};
/// The upload command matcher.
pub struct UploadMatcher<'a> {
@ -24,7 +17,8 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
/// Get the selected file to upload.
// TODO: maybe return a file or path instance here
pub fn file(&'a self) -> &'a str {
self.matches.value_of("FILE")
self.matches
.value_of("FILE")
.expect("no file specified to upload")
}
@ -71,26 +65,21 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
pub fn password(&'a self) -> Option<(String, bool)> {
// Generate a passphrase if requested
if ArgGenPassphrase::is_present(self.matches) {
return Some((
ArgGenPassphrase::gen_passphrase(),
true,
));
return Some((ArgGenPassphrase::gen_passphrase(), true));
}
// Use a specified password or use nothing
ArgPassword::value(self.matches)
.map(|password| (password, false))
ArgPassword::value(self.matches).map(|password| (password, false))
}
/// Get the download limit.
/// If the download limit was the default, `None` is returned to not
/// explicitly set it.
pub fn download_limit(&'a self) -> Option<u8> {
ArgDownloadLimit::value(self.matches)
.and_then(|d| match d {
DOWNLOAD_DEFAULT => None,
d => Some(d),
})
ArgDownloadLimit::value(self.matches).and_then(|d| match d {
DOWNLOAD_DEFAULT => None,
d => Some(d),
})
}
/// Check whether to archive the file to upload.
@ -113,11 +102,8 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
impl<'a> Matcher<'a> for UploadMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches.subcommand_matches("upload")
.map(|matches|
UploadMatcher {
matches,
}
)
matches
.subcommand_matches("upload")
.map(|matches| UploadMatcher { matches })
}
}

View file

@ -1,7 +1,7 @@
pub mod arg;
pub mod subcmd;
pub mod handler;
pub mod matcher;
pub mod subcmd;
// Reexport modules
pub use self::handler::Handler;

View file

@ -15,24 +15,29 @@ impl CmdDownload {
.visible_alias("down")
.arg(ArgUrl::build())
.arg(ArgPassword::build())
.arg(Arg::with_name("output")
.long("output")
.short("o")
.alias("output-file")
.alias("out")
.alias("file")
.value_name("PATH")
.help("The output file or directory"));
.arg(
Arg::with_name("output")
.long("output")
.short("o")
.alias("output-file")
.alias("out")
.alias("file")
.value_name("PATH")
.help("The output file or directory"),
);
// Optional archive support
#[cfg(feature = "archive")] {
cmd = cmd.arg(Arg::with_name("extract")
.long("extract")
.short("e")
.alias("archive")
.alias("arch")
.alias("a")
.help("Extract an archived file"))
#[cfg(feature = "archive")]
{
cmd = cmd.arg(
Arg::with_name("extract")
.long("extract")
.short("e")
.alias("archive")
.alias("arch")
.alias("a")
.help("Extract an archived file"),
)
}
cmd

View file

@ -8,9 +8,7 @@ pub struct CmdParams;
impl CmdParams {
pub fn build<'a, 'b>() -> App<'a, 'b> {
// Create a list of parameter arguments, of which one is required
let param_args = [
ArgDownloadLimit::name(),
];
let param_args = [ArgDownloadLimit::name()];
SubCommand::with_name("parameters")
.about("Change parameters of a shared file")

View file

@ -12,8 +12,7 @@ impl CmdPassword {
.visible_alias("pass")
.visible_alias("p")
.arg(ArgUrl::build())
.arg(ArgPassword::build()
.help("Specify a password, do not prompt"))
.arg(ArgPassword::build().help("Specify a password, do not prompt"))
.arg(ArgGenPassphrase::build())
.arg(ArgOwner::build())
}

View file

@ -1,15 +1,7 @@
use clap::{App, Arg, SubCommand};
use ffsend_api::action::params::{
PARAMS_DEFAULT_DOWNLOAD_STR as DOWNLOAD_DEFAULT,
};
use ffsend_api::action::params::PARAMS_DEFAULT_DOWNLOAD_STR as DOWNLOAD_DEFAULT;
use cmd::arg::{
ArgDownloadLimit,
ArgGenPassphrase,
ArgHost,
ArgPassword,
CmdArg,
};
use cmd::arg::{ArgDownloadLimit, ArgGenPassphrase, ArgHost, ArgPassword, CmdArg};
/// The upload command definition.
pub struct CmdUpload;
@ -22,43 +14,51 @@ impl CmdUpload {
.about("Upload files")
.visible_alias("u")
.visible_alias("up")
.arg(Arg::with_name("FILE")
.help("The file to upload")
.required(true)
.multiple(false))
.arg(ArgPassword::build()
.help("Protect the file with a password"))
.arg(
Arg::with_name("FILE")
.help("The file to upload")
.required(true)
.multiple(false),
).arg(ArgPassword::build().help("Protect the file with a password"))
.arg(ArgGenPassphrase::build())
.arg(ArgDownloadLimit::build()
.default_value(DOWNLOAD_DEFAULT))
.arg(ArgDownloadLimit::build().default_value(DOWNLOAD_DEFAULT))
.arg(ArgHost::build())
.arg(Arg::with_name("name")
.long("name")
.short("n")
.alias("file")
.alias("f")
.value_name("NAME")
.help("Rename the file being uploaded"))
.arg(Arg::with_name("open")
.long("open")
.short("o")
.help("Open the share link in your browser"));
.arg(
Arg::with_name("name")
.long("name")
.short("n")
.alias("file")
.alias("f")
.value_name("NAME")
.help("Rename the file being uploaded"),
).arg(
Arg::with_name("open")
.long("open")
.short("o")
.help("Open the share link in your browser"),
);
// Optional archive support
#[cfg(feature = "archive")] {
cmd = cmd.arg(Arg::with_name("archive")
.long("archive")
.short("a")
.alias("arch")
.help("Archive the upload in a single file"))
#[cfg(feature = "archive")]
{
cmd = cmd.arg(
Arg::with_name("archive")
.long("archive")
.short("a")
.alias("arch")
.help("Archive the upload in a single file"),
)
}
// Optional clipboard support
#[cfg(feature = "clipboard")] {
cmd = cmd.arg(Arg::with_name("copy")
.long("copy")
.short("c")
.help("Copy the share link to your clipboard"));
#[cfg(feature = "clipboard")]
{
cmd = cmd.arg(
Arg::with_name("copy")
.long("copy")
.short("c")
.help("Copy the share link to your clipboard"),
);
}
cmd

View file

@ -5,14 +5,11 @@ use std::fs;
use std::io::Error as IoError;
use std::path::PathBuf;
use failure::Fail;
use ffsend_api::file::remote_file::RemoteFile;
use self::toml::de::Error as DeError;
use self::toml::ser::Error as SerError;
use self::version_compare::{
CompOp,
VersionCompare,
};
use self::version_compare::{CompOp, VersionCompare};
use failure::Fail;
use ffsend_api::file::remote_file::RemoteFile;
use util::{print_error, print_warning};
@ -59,7 +56,7 @@ impl History {
history.autosave = Some(path);
// Make sure the file version is supported
if history.version.is_none() {
if history.version.is_none() {
print_warning("History file has no version, ignoring");
history.version = Some(crate_version!().into());
} else {
@ -97,15 +94,12 @@ impl History {
self.gc();
// Get the path
let path = self.autosave
.as_ref()
.ok_or(SaveError::NoPath)?;
let path = self.autosave.as_ref().ok_or(SaveError::NoPath)?;
// If we have no files, remove the history file if it exists
if self.files.is_empty() {
if path.is_file() {
fs::remove_file(&path)
.map_err(SaveError::Delete)?;
fs::remove_file(&path).map_err(SaveError::Delete)?;
}
return Ok(());
}
@ -135,7 +129,9 @@ impl History {
// Merge any existing file with the same ID
{
// Find anything to merge
let merge_info: Vec<bool> = self.files.iter_mut()
let merge_info: Vec<bool> = self
.files
.iter_mut()
.filter(|f| f.id() == file.id())
.map(|ref mut f| f.merge(&file, overwrite))
.collect();
@ -161,7 +157,9 @@ impl History {
/// If any file was removed, true is returned.
pub fn remove(&mut self, file: &RemoteFile) -> bool {
// Get the indices of files that have expired
let expired_indices: Vec<usize> = self.files.iter()
let expired_indices: Vec<usize> = self
.files
.iter()
.enumerate()
.filter(|&(_, f)| f.id() == file.id())
.map(|(i, _)| i)
@ -189,7 +187,9 @@ impl History {
/// If multiple files exist within the history that are equal, only one is returned.
/// If no matching file was found, `None` is returned.
pub fn get_file(&self, file: &RemoteFile) -> Option<&RemoteFile> {
self.files.iter().find(|f| f.id() == file.id() && f.host() == file.host())
self.files
.iter()
.find(|f| f.id() == file.id() && f.host() == file.host())
}
/// Garbage collect (remove) all files that have been expired,
@ -200,7 +200,8 @@ impl History {
/// The number of exired files is returned.
pub fn gc(&mut self) -> usize {
// Get a list of expired files
let expired: Vec<RemoteFile> = self.files
let expired: Vec<RemoteFile> = self
.files
.iter()
.filter(|f| f.has_expired())
.cloned()
@ -227,9 +228,7 @@ impl Drop for History {
if self.autosave.is_some() && self.changed {
// Save and report errors
if let Err(err) = self.save() {
print_error(
err.context("failed to auto save history, ignoring"),
);
print_error(err.context("failed to auto save history, ignoring"));
}
}
}

View file

@ -2,10 +2,7 @@ use failure::Fail;
use ffsend_api::file::remote_file::RemoteFile;
use cmd::matcher::MainMatcher;
use history::{
Error as HistoryError,
History,
};
use history::{Error as HistoryError, History};
use util::print_error;
/// Load the history from the given path, add the given file, and save it
@ -16,9 +13,11 @@ use util::print_error;
/// overwrite properties in the already existing file when merging.
///
/// If there is no file at the given path, new history will be created.
fn add_error(matcher_main: &MainMatcher, file: RemoteFile, overwrite: bool)
-> Result<(), HistoryError>
{
fn add_error(
matcher_main: &MainMatcher,
file: RemoteFile,
overwrite: bool,
) -> Result<(), HistoryError> {
// Ignore if incognito
if matcher_main.incognito() {
return Ok(());
@ -41,18 +40,14 @@ fn add_error(matcher_main: &MainMatcher, file: RemoteFile, overwrite: bool)
/// If an error occurred, the error is printed and ignored.
pub fn add(matcher_main: &MainMatcher, file: RemoteFile, overwrite: bool) {
if let Err(err) = add_error(matcher_main, file, overwrite) {
print_error(err.context(
"failed to add file to local history, ignoring",
));
print_error(err.context("failed to add file to local history, ignoring"));
}
}
/// Load the history from the given path, remove the given file by it's
/// ID, and save it again.
/// True is returned if any file was removed.
fn remove_error(matcher_main: &MainMatcher, file: &RemoteFile)
-> Result<bool, HistoryError>
{
fn remove_error(matcher_main: &MainMatcher, file: &RemoteFile) -> Result<bool, HistoryError> {
// Ignore if incognito
if matcher_main.incognito() {
return Ok(false);
@ -72,9 +67,7 @@ pub fn remove(matcher_main: &MainMatcher, file: &RemoteFile) -> bool {
let result = remove_error(matcher_main, file);
let ok = result.is_ok();
if let Err(err) = result {
print_error(err.context(
"failed to remove file from local history, ignoring",
));
print_error(err.context("failed to remove file from local history, ignoring"));
}
ok
}
@ -104,9 +97,7 @@ pub fn derive_file_properties(matcher_main: &MainMatcher, file: &mut RemoteFile)
let history = match History::load_or_new(matcher_main.history()) {
Ok(history) => history,
Err(err) => {
print_error(err.context(
"failed to derive file properties from history, ignoring",
));
print_error(err.context("failed to derive file properties from history, ignoring"));
return false;
}
};
@ -126,7 +117,7 @@ pub fn derive_file_properties(matcher_main: &MainMatcher, file: &mut RemoteFile)
// Return whether any property was derived
f.has_secret() || f.has_owner_token()
},
}
None => false,
}
}

View file

@ -44,7 +44,7 @@ use action::password::Password;
use action::upload::Upload;
use cmd::Handler;
use error::Error;
use util::{ErrorHints, exe_name, highlight, quit_error};
use util::{exe_name, highlight, quit_error, ErrorHints};
/// Application entrypoint.
fn main() {
@ -67,25 +67,29 @@ fn main() {
fn invoke_action(handler: &Handler) -> Result<(), Error> {
// Match the debug command
if handler.debug().is_some() {
return Debug::new(handler.matches()).invoke()
return Debug::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
// Match the delete command
if handler.delete().is_some() {
return Delete::new(handler.matches()).invoke()
return Delete::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
// Match the download command
if handler.download().is_some() {
return Download::new(handler.matches()).invoke()
return Download::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
// Match the exists command
if handler.exists().is_some() {
return Exists::new(handler.matches()).invoke()
return Exists::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
@ -93,32 +97,37 @@ fn invoke_action(handler: &Handler) -> Result<(), Error> {
#[cfg(feature = "history")]
{
if handler.history().is_some() {
return History::new(handler.matches()).invoke()
return History::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
}
// Match the info command
if handler.info().is_some() {
return Info::new(handler.matches()).invoke()
return Info::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
// Match the parameters command
if handler.params().is_some() {
return Params::new(handler.matches()).invoke()
return Params::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
// Match the password command
if handler.password().is_some() {
return Password::new(handler.matches()).invoke()
return Password::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}
// Match the upload command
if handler.upload().is_some() {
return Upload::new(handler.matches()).invoke()
return Upload::new(handler.matches())
.invoke()
.map_err(|err| err.into());
}

View file

@ -3,11 +3,8 @@ extern crate pbr;
use std::io::{stderr, Stderr};
use std::time::Duration;
use self::pbr::{ProgressBar as Pbr, Units};
use ffsend_api::reader::ProgressReporter;
use self::pbr::{
ProgressBar as Pbr,
Units,
};
/// The refresh rate of the progress bar, in milliseconds.
const PROGRESS_BAR_FPS_MILLIS: u64 = 200;
@ -45,9 +42,7 @@ impl<'a> ProgressReporter for ProgressBar<'a> {
fn start(&mut self, total: u64) {
// Initialize the progress bar
let mut progress_bar = Pbr::on(stderr(), total);
progress_bar.set_max_refresh_rate(
Some(Duration::from_millis(PROGRESS_BAR_FPS_MILLIS))
);
progress_bar.set_max_refresh_rate(Some(Duration::from_millis(PROGRESS_BAR_FPS_MILLIS)));
progress_bar.set_units(Units::Bytes);
progress_bar.message(self.msg_progress);
@ -56,14 +51,16 @@ impl<'a> ProgressReporter for ProgressBar<'a> {
/// A progress update.
fn progress(&mut self, progress: u64) {
self.progress_bar.as_mut()
self.progress_bar
.as_mut()
.expect("progress bar not yet instantiated, cannot set progress")
.set(progress);
}
/// Finish the progress.
fn finish(&mut self) {
self.progress_bar.as_mut()
self.progress_bar
.as_mut()
.expect("progress bar not yet instantiated")
.finish_print(self.msg_finish);
}

View file

@ -9,14 +9,9 @@ use std::borrow::Borrow;
use std::env::{current_exe, var_os};
use std::ffi::OsStr;
use std::fmt::{Debug, Display};
use std::io::{
Error as IoError,
stdin,
stderr,
Write,
};
#[cfg(feature = "clipboard")]
use std::io::ErrorKind as IoErrorKind;
use std::io::{stderr, stdin, Error as IoError, Write};
use std::path::Path;
#[cfg(feature = "history")]
use std::path::PathBuf;
@ -24,18 +19,18 @@ use std::process::{exit, ExitStatus};
#[cfg(all(feature = "clipboard", target_os = "linux"))]
use std::process::{Command, Stdio};
use chrono::Duration;
use failure::{err_msg, Fail};
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
use failure::{Compat, Error};
use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
use self::clipboard::{ClipboardContext, ClipboardProvider};
use self::colored::*;
#[cfg(feature = "history")]
use self::directories::ProjectDirs;
use self::fs2::available_space;
use chrono::Duration;
use failure::{err_msg, Fail};
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
use failure::{Compat, Error};
use ffsend_api::url::Url;
use rpassword::prompt_password_stderr;
use cmd::matcher::MainMatcher;
@ -48,28 +43,35 @@ pub fn print_success(msg: &str) {
/// with it's causes.
pub fn print_error<E: Fail>(err: impl Borrow<E>) {
// Report each printable error, count them
let count = err.borrow()
let count = err
.borrow()
.causes()
.map(|err| format!("{}", err))
.filter(|err| !err.is_empty())
.enumerate()
.map(|(i, err)| if i == 0 {
eprintln!("{} {}", highlight_error("error:"), err);
} else {
eprintln!("{} {}", highlight_error("caused by:"), err);
})
.count();
.map(|(i, err)| {
if i == 0 {
eprintln!("{} {}", highlight_error("error:"), err);
} else {
eprintln!("{} {}", highlight_error("caused by:"), err);
}
}).count();
// Fall back to a basic message
if count == 0 {
eprintln!("{} {}", highlight_error("error:"), "an undefined error occurred");
eprintln!(
"{} {}",
highlight_error("error:"),
"an undefined error occurred"
);
}
}
}
/// Print the given error message in a proper format for the user,
/// with it's causes.
pub fn print_error_msg<S>(err: S)
where
S: AsRef<str> + Display + Debug + Sync + Send + 'static
where
S: AsRef<str> + Display + Debug + Sync + Send + 'static,
{
print_error(err_msg(err).compat());
}
@ -77,8 +79,8 @@ pub fn print_error_msg<S>(err: S)
/// Print a warning.
#[cfg(feature = "history")]
pub fn print_warning<S>(err: S)
where
S: AsRef<str> + Display + Debug + Sync + Send + 'static
where
S: AsRef<str> + Display + Debug + Sync + Send + 'static,
{
eprintln!("{} {}", highlight_warning("warning:"), err);
}
@ -104,8 +106,8 @@ pub fn quit_error<E: Fail>(err: E, hints: impl Borrow<ErrorHints>) -> ! {
/// Quit the application with an error code,
/// and print the given error message.
pub fn quit_error_msg<S>(err: S, hints: impl Borrow<ErrorHints>) -> !
where
S: AsRef<str> + Display + Debug + Sync + Send + 'static
where
S: AsRef<str> + Display + Debug + Sync + Send + 'static,
{
quit_error(err_msg(err).compat(), hints);
}
@ -142,11 +144,7 @@ impl ErrorHints {
pub fn any(&self) -> bool {
// Determine the result
#[allow(unused_mut)]
let mut result = self.password
|| self.owner
|| self.force
|| self.verbose
|| self.help;
let mut result = self.password || self.owner || self.force || self.verbose || self.help;
// Factor in the history hint when enabled
#[cfg(feature = "history")]
@ -173,15 +171,24 @@ impl ErrorHints {
// Print hints
if self.password {
eprintln!("Use '{}' to specify a password", highlight("--password <PASSWORD>"));
eprintln!(
"Use '{}' to specify a password",
highlight("--password <PASSWORD>")
);
}
if self.owner {
eprintln!("Use '{}' to specify an owner token", highlight("--owner <TOKEN>"));
eprintln!(
"Use '{}' to specify an owner token",
highlight("--owner <TOKEN>")
);
}
#[cfg(feature = "history")]
{
if self.history {
eprintln!("Use '{}' to specify a history file", highlight("--history <FILE>"));
eprintln!(
"Use '{}' to specify a history file",
highlight("--history <FILE>")
);
}
}
if self.force {
@ -267,14 +274,16 @@ pub fn open_path(path: &str) -> Result<ExitStatus, IoError> {
/// Set the clipboard of the user to the given `content` string.
#[cfg(feature = "clipboard")]
pub fn set_clipboard(content: String) -> Result<(), ClipboardError> {
#[cfg(not(target_os = "linux"))] {
#[cfg(not(target_os = "linux"))]
{
ClipboardProvider::new()
.and_then(|mut context: ClipboardContext| context.set_contents(content))
.map_err(|err| format_err!("{}", err).compat())
.map_err(ClipboardError::Generic)
}
#[cfg(target_os = "linux")] {
#[cfg(target_os = "linux")]
{
// Open an xclip process
let mut process = match Command::new("xclip")
.arg("-sel")
@ -283,20 +292,24 @@ pub fn set_clipboard(content: String) -> Result<(), ClipboardError> {
.spawn()
{
Ok(process) => process,
Err(err) => return Err(match err.kind() {
IoErrorKind::NotFound => ClipboardError::NoXclip,
_ => ClipboardError::Xclip(err),
}),
Err(err) => {
return Err(match err.kind() {
IoErrorKind::NotFound => ClipboardError::NoXclip,
_ => ClipboardError::Xclip(err),
})
}
};
// Write the contents to the xclip process
process.stdin.as_mut().unwrap()
process
.stdin
.as_mut()
.unwrap()
.write_all(content.as_bytes())
.map_err(ClipboardError::Xclip)?;
// Wait for xclip to exit
let status = process.wait()
.map_err(ClipboardError::Xclip)?;
let status = process.wait().map_err(ClipboardError::Xclip)?;
if !status.success() {
return Err(ClipboardError::XclipStatus(status.code().unwrap_or(0)));
}
@ -327,7 +340,10 @@ pub enum ClipboardError {
/// Xclip unexpectetly exited with a non-successful status code.
#[cfg(target_os = "linux")]
#[fail(display = "failed to use clipboard, xclip exited with status code {}", _0)]
#[fail(
display = "failed to use clipboard, xclip exited with status code {}",
_0
)]
XclipStatus(i32),
}
@ -367,9 +383,10 @@ pub fn prompt_password(main_matcher: &MainMatcher) -> String {
// Prompt for the password
match prompt_password_stderr("Password: ") {
Ok(password) => password,
Err(err) => quit_error(err.context(
"failed to read password from password prompt"
), ErrorHints::default()),
Err(err) => quit_error(
err.context("failed to read password from password prompt"),
ErrorHints::default(),
),
}
}
@ -380,11 +397,7 @@ pub fn prompt_password(main_matcher: &MainMatcher) -> 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,
main_matcher: &MainMatcher,
) {
pub fn ensure_password(password: &mut Option<String>, needs: bool, main_matcher: &MainMatcher) {
// Return if we're fine
if password.is_some() == needs {
return;
@ -406,10 +419,13 @@ pub fn ensure_password(
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_msg(format!(
"could not prompt for '{}' in no-interact mode, maybe specify it",
msg,
), ErrorHints::default());
quit_error_msg(
format!(
"could not prompt for '{}' in no-interact mode, maybe specify it",
msg,
),
ErrorHints::default(),
);
}
// Show the prompt
@ -419,9 +435,10 @@ pub fn prompt(msg: &str, main_matcher: &MainMatcher) -> String {
// Get the input
let mut input = String::new();
if let Err(err) = stdin().read_line(&mut input) {
quit_error(err.context(
"failed to read input from prompt"
), ErrorHints::default());
quit_error(
err.context("failed to read input from prompt"),
ErrorHints::default(),
);
}
// Trim and return
@ -433,19 +450,19 @@ pub fn prompt(msg: &str, main_matcher: &MainMatcher) -> String {
///
/// A default may be given, which is chosen if no-interact mode is
/// enabled, or if enter was pressed by the user without entering anything.
pub fn prompt_yes(
msg: &str,
def: Option<bool>,
main_matcher: &MainMatcher,
) -> bool {
pub fn prompt_yes(msg: &str, def: Option<bool>, main_matcher: &MainMatcher) -> bool {
// Define the available options string
let options = format!("[{}/{}]", match def {
Some(def) if def => "Y",
_ => "y",
}, match def {
Some(def) if !def => "N",
_ => "n",
});
let options = format!(
"[{}/{}]",
match def {
Some(def) if def => "Y",
_ => "y",
},
match def {
Some(def) if !def => "N",
_ => "n",
}
);
// Assume yes
if main_matcher.assume_yes() {
@ -456,17 +473,16 @@ pub fn prompt_yes(
// Autoselect if in no-interact mode
if main_matcher.no_interact() {
if let Some(def) = def {
eprintln!("{} {}: {}", msg, options, if def {
"yes"
} else {
"no"
});
eprintln!("{} {}: {}", msg, options, if def { "yes" } else { "no" });
return def;
} else {
quit_error_msg(format!(
"could not prompt question '{}' in no-interact mode, maybe specify it",
msg,
), ErrorHints::default());
quit_error_msg(
format!(
"could not prompt question '{}' in no-interact mode, maybe specify it",
msg,
),
ErrorHints::default(),
);
}
}
@ -495,7 +511,7 @@ fn derive_bool(input: &str) -> Option<bool> {
match input.as_str() {
"y" | "ye" | "t" | "1" => return Some(true),
"n" | "f" | "0" => return Some(false),
_ => {},
_ => {}
}
// Handle complete answers with any suffix
@ -520,10 +536,7 @@ pub fn prompt_owner_token(main_matcher: &MainMatcher) -> 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>,
main_matcher: &MainMatcher,
) {
pub fn ensure_owner_token(token: &mut Option<String>, main_matcher: &MainMatcher) {
// Check whehter we allow interaction
let interact = !main_matcher.no_interact();
@ -658,7 +671,7 @@ pub fn ensure_enough_space<P: AsRef<Path>>(path: P, size: u64) {
Err(err) => {
print_error(err.context("failed to check available space on disk, ignoring"));
return;
},
}
};
// Return if enough disk space is avaiable
@ -703,9 +716,7 @@ pub fn app_history_file_path() -> PathBuf {
/// Get the default path to use for the history file, as a string.
#[cfg(feature = "history")]
pub fn app_history_file_path_string() -> String {
app_history_file_path().to_str()
.unwrap()
.to_owned()
app_history_file_path().to_str().unwrap().to_owned()
}
/// Check whether an environment variable with the given key is present in the context of the