1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use clap::ArgMatches;
use ffsend_api::{api::Version as ApiVersion, config, url::Url};
use super::Matcher;
use crate::cmd::{
arg::{
ArgDownloadLimit, ArgExpiryTime, ArgGenPassphrase, ArgHost, ArgPassword, CmdArgFlag,
CmdArgOption,
},
matcher::MainMatcher,
};
use crate::util::{bin_name, env_var_present, quit_error_msg, ErrorHintsBuilder};
/// The upload command matcher.
pub struct UploadMatcher<'a> {
matches: &'a ArgMatches<'a>,
}
impl<'a: 'b, 'b> UploadMatcher<'a> {
/// Get the selected file to upload.
// TODO: maybe return a file or path instance here
pub fn files(&'a self) -> Vec<&'a str> {
self.matches
.values_of("FILE")
.expect("no file specified to upload")
.collect()
}
/// The the name to use for the uploaded file.
/// If no custom name is given, none is returned.
// TODO: validate custom names, no path separators
// TODO: only allow extension renaming with force flag
pub fn name(&'a self) -> Option<&'a str> {
// Get the chosen file name
let name = self.matches.value_of("name")?;
// The file name must not be empty
// TODO: allow to force an empty name here, and process empty names on downloading
if name.trim().is_empty() {
quit_error_msg(
"the file name must not be empty",
ErrorHintsBuilder::default()
.force(false)
.verbose(false)
.build()
.unwrap(),
);
}
Some(name)
}
/// Get the host to upload to.
///
/// This method parses the host into an `Url`.
/// If the given host is invalid,
/// the program will quit with an error message.
pub fn host(&'a self) -> Url {
ArgHost::value(self.matches)
}
/// Get the password.
/// A generated passphrase will be returned if the user requested so,
/// otherwise the specified password is returned.
/// If no password was set, `None` is returned instead.
///
/// The password is returned in the following format:
/// `(password, generated)`
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));
}
// Use a specified password or use nothing
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,
main_matcher: &MainMatcher,
api_version: ApiVersion,
auth: bool,
) -> Option<usize> {
ArgDownloadLimit::value_checked(self.matches, main_matcher, api_version, auth).and_then(
|d| match d {
d if d == config::downloads_default(api_version, auth) => None,
d => Some(d),
},
)
}
/// Get the expiry time in seconds.
///
/// If the expiry time was not set, `None` is returned.
pub fn expiry_time(
&'a self,
main_matcher: &MainMatcher,
api_version: ApiVersion,
auth: bool,
) -> Option<usize> {
ArgExpiryTime::value_checked(self.matches, main_matcher, api_version, auth)
}
/// Check whether to archive the file to upload.
#[cfg(feature = "archive")]
pub fn archive(&self) -> bool {
self.matches.is_present("archive") || env_var_present("SNDR_ARCHIVE")
}
/// Check whether to open the file URL in the user's browser.
pub fn open(&self) -> bool {
self.matches.is_present("open") || env_var_present("SNDR_OPEN")
}
/// Check whether to to delete local files after uploading.
pub fn delete(&self) -> bool {
self.matches.is_present("delete")
}
/// Check whether to copy the file URL in the user's clipboard, get the copy mode.
#[cfg(feature = "clipboard")]
pub fn copy(&self) -> Option<CopyMode> {
// Get the options
let copy = self.matches.is_present("copy") || env_var_present("SNDR_COPY");
let copy_cmd = self.matches.is_present("copy-cmd") || env_var_present("SNDR_COPY_CMD");
// Return the corresponding copy mode
if copy_cmd {
Some(CopyMode::DownloadCmd)
} else if copy {
Some(CopyMode::Url)
} else {
None
}
}
/// Check whether to shorten a share URL
#[cfg(feature = "urlshorten")]
pub fn shorten(&self) -> bool {
self.matches.is_present("shorten")
}
/// Check whether to print a QR code for the share URL.
#[cfg(feature = "qrcode")]
pub fn qrcode(&self) -> bool {
self.matches.is_present("qrcode")
}
}
impl<'a> Matcher<'a> for UploadMatcher<'a> {
fn with(matches: &'a ArgMatches) -> Option<Self> {
matches
.subcommand_matches("upload")
.map(|matches| UploadMatcher { matches })
}
}
/// The copy mode.
#[derive(Debug, Copy, Clone)]
pub enum CopyMode {
/// Copy the public share link.
Url,
/// Copy a sndr download command.
DownloadCmd,
}
impl CopyMode {
/// Build the string to copy, based on the given `url` and current mode.
pub fn build(&self, url: &str) -> String {
match self {
CopyMode::Url => url.into(),
CopyMode::DownloadCmd => format!("{} download {}", bin_name(), url),
}
}
}