Reformat using rustfmt
This commit is contained in:
parent
855da2c53b
commit
16d6a6a4ae
39 changed files with 486 additions and 693 deletions
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
191
src/util.rs
191
src/util.rs
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue