Allow uploading multiple files by archiving, rework archiving
This commit is contained in:
parent
12503d0d21
commit
ce39eada4c
5 changed files with 151 additions and 82 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -646,6 +646,7 @@ dependencies = [
|
|||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"open 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pathdiff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"prettytable-rs 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"qr2term 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1251,6 +1252,11 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "pbr"
|
||||
version = "1.0.1"
|
||||
|
@ -2433,6 +2439,7 @@ dependencies = [
|
|||
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
|
||||
"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
|
||||
"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
|
||||
"checksum pathdiff 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a3bf70094d203e07844da868b634207e71bfab254fe713171fae9a6e751ccf31"
|
||||
"checksum pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907"
|
||||
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
|
||||
"checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
|
||||
|
|
|
@ -108,6 +108,7 @@ fs2 = "0.4"
|
|||
lazy_static = "1.0"
|
||||
open = "1"
|
||||
openssl-probe = "0.1"
|
||||
pathdiff = "0.1"
|
||||
pbr = "1"
|
||||
prettytable-rs = "0.8"
|
||||
qr2term = { version = "0.1", optional = true }
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::env::current_dir;
|
||||
#[cfg(feature = "archive")]
|
||||
use std::io::Error as IoError;
|
||||
use std::path::Path;
|
||||
#[cfg(feature = "archive")]
|
||||
use std::process::exit;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
@ -10,6 +13,7 @@ use ffsend_api::action::upload::{Error as UploadError, Upload as ApiUpload};
|
|||
use ffsend_api::action::version::Error as VersionError;
|
||||
use ffsend_api::config::{upload_size_max, UPLOAD_SIZE_MAX_RECOMMENDED};
|
||||
use ffsend_api::pipe::ProgressReporter;
|
||||
use pathdiff::diff_paths;
|
||||
use prettytable::{format::FormatBuilder, Cell, Row, Table};
|
||||
#[cfg(feature = "qrcode")]
|
||||
use qr2term::print_qr;
|
||||
|
@ -53,9 +57,142 @@ impl<'a> Upload<'a> {
|
|||
|
||||
// Get API parameters
|
||||
#[allow(unused_mut)]
|
||||
let mut path = Path::new(matcher_upload.file()).to_path_buf();
|
||||
let mut paths = matcher_upload.files();
|
||||
let mut path = Path::new(paths.first().unwrap()).to_path_buf();
|
||||
let host = matcher_upload.host();
|
||||
|
||||
// The file name to use
|
||||
#[allow(unused_mut)]
|
||||
let mut file_name = matcher_upload.name().map(|s| s.to_owned());
|
||||
|
||||
// A temporary archive file, only used when archiving
|
||||
// The temporary file is stored here, to ensure it's lifetime exceeds the upload process
|
||||
#[allow(unused_mut)]
|
||||
#[cfg(feature = "archive")]
|
||||
let mut tmp_archive: Option<NamedTempFile> = None;
|
||||
|
||||
#[cfg(feature = "archive")]
|
||||
{
|
||||
// Determine whether to archive, we must archive for multiple files/directory
|
||||
let mut archive = matcher_upload.archive();
|
||||
if !archive {
|
||||
if paths.len() > 1 {
|
||||
if prompt_yes(
|
||||
"You've selected multiple files, only a single file may be uploaded.\n\
|
||||
Archive the files into a single file?",
|
||||
Some(true),
|
||||
&matcher_main,
|
||||
) {
|
||||
archive = true;
|
||||
} else {
|
||||
exit(1);
|
||||
}
|
||||
} else if 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?",
|
||||
Some(true),
|
||||
&matcher_main,
|
||||
) {
|
||||
archive = true;
|
||||
} else {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Archive the selected file or directory
|
||||
if archive {
|
||||
eprintln!("Archiving...");
|
||||
let archive_extention = ".tar";
|
||||
|
||||
// Create a new temporary file to write the archive to
|
||||
tmp_archive = Some(
|
||||
TempBuilder::new()
|
||||
.prefix(&format!(".{}-archive-", crate_name!()))
|
||||
.suffix(archive_extention)
|
||||
.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()
|
||||
.try_clone()
|
||||
.map_err(ArchiveError::CloneHandle)?;
|
||||
|
||||
// Select the file name to use if not set
|
||||
// TODO: require user to define if multiple paths
|
||||
if file_name.is_none() {
|
||||
file_name = Some(
|
||||
path.canonicalize()
|
||||
.map_err(|err| ArchiveError::FileName(Some(err)))?
|
||||
.file_name()
|
||||
.ok_or(ArchiveError::FileName(None))?
|
||||
.to_str()
|
||||
.map(|s| s.to_owned())
|
||||
.ok_or(ArchiveError::FileName(None))?,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the current working directory, canonicalize it
|
||||
let mut working_dir =
|
||||
current_dir().expect("failed to get current working directory");
|
||||
if let Ok(p) = working_dir.canonicalize() {
|
||||
working_dir = p;
|
||||
}
|
||||
|
||||
// Build an archiver, append each file
|
||||
let mut archiver = Archiver::new(archive_file);
|
||||
for path in &paths {
|
||||
// Find the relative path, get path name of file to add
|
||||
let path = diff_paths(Path::new(path), &working_dir)
|
||||
.expect("failed to determine relative path of file to archive");
|
||||
let name = path.to_str().expect("failed to get file path");
|
||||
|
||||
// Add file to archiver
|
||||
archiver
|
||||
.append_path(name, &path)
|
||||
.map_err(ArchiveError::AddFile)?;
|
||||
}
|
||||
|
||||
// Finish the archival process, writes the archive file
|
||||
archiver.finish().map_err(ArchiveError::Write)?;
|
||||
|
||||
// Append archive extention to name, set to upload archived file
|
||||
if let Some(ref mut file_name) = file_name {
|
||||
file_name.push_str(archive_extention);
|
||||
}
|
||||
path = archive_path;
|
||||
paths.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Quit with error when uploading multiple files or directory, if we cannot archive
|
||||
#[cfg(not(feature = "archive"))]
|
||||
{
|
||||
if paths.len() > 1 {
|
||||
quit_error_msg(
|
||||
"uploading multiple files is not supported, ffsend must be compiled with 'archive' feature for this",
|
||||
ErrorHintsBuilder::default()
|
||||
.verbose(false)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
if path.is_dir() {
|
||||
quit_error_msg(
|
||||
"uploading a directory is not supported, ffsend must be compiled with 'archive' feature for this",
|
||||
ErrorHintsBuilder::default()
|
||||
.verbose(false)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a reqwest client capable for uploading files
|
||||
let client_config = create_config(&matcher_main);
|
||||
let client = client_config.clone().client(false);
|
||||
|
@ -155,83 +292,6 @@ impl<'a> Upload<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
// The file name to use
|
||||
#[allow(unused_mut)]
|
||||
let mut file_name = matcher_upload.name().map(|s| s.to_owned());
|
||||
|
||||
// A temporary archive file, only used when archiving
|
||||
// The temporary file is stored here, to ensure it's lifetime exceeds the upload process
|
||||
#[allow(unused_mut)]
|
||||
#[cfg(feature = "archive")]
|
||||
let mut tmp_archive: Option<NamedTempFile> = None;
|
||||
|
||||
#[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?",
|
||||
Some(true),
|
||||
&matcher_main,
|
||||
) {
|
||||
archive = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Archive the selected file or directory
|
||||
if archive {
|
||||
eprintln!("Archiving...");
|
||||
let archive_extention = ".tar";
|
||||
|
||||
// Create a new temporary file to write the archive to
|
||||
tmp_archive = Some(
|
||||
TempBuilder::new()
|
||||
.prefix(&format!(".{}-archive-", crate_name!()))
|
||||
.suffix(archive_extention)
|
||||
.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()
|
||||
.try_clone()
|
||||
.map_err(ArchiveError::CloneHandle)?;
|
||||
|
||||
// Select the file name to use if not set
|
||||
if file_name.is_none() {
|
||||
file_name = Some(
|
||||
path.canonicalize()
|
||||
.map_err(|err| ArchiveError::FileName(Some(err)))?
|
||||
.file_name()
|
||||
.ok_or(ArchiveError::FileName(None))?
|
||||
.to_str()
|
||||
.map(|s| s.to_owned())
|
||||
.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)
|
||||
.map_err(ArchiveError::AddFile)?;
|
||||
|
||||
// Finish the archival process, writes the archive file
|
||||
archiver.finish().map_err(ArchiveError::Write)?;
|
||||
|
||||
// Append archive extention to name, set to upload archived file
|
||||
if let Some(ref mut file_name) = file_name {
|
||||
file_name.push_str(archive_extention);
|
||||
}
|
||||
path = archive_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the progress reporter
|
||||
let progress_reporter: Arc<Mutex<ProgressReporter>> = progress_bar;
|
||||
|
||||
|
|
|
@ -18,10 +18,11 @@ pub struct UploadMatcher<'a> {
|
|||
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 {
|
||||
pub fn files(&'a self) -> Vec<&'a str> {
|
||||
self.matches
|
||||
.value_of("FILE")
|
||||
.values_of("FILE")
|
||||
.expect("no file specified to upload")
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The the name to use for the uploaded file.
|
||||
|
|
|
@ -16,9 +16,9 @@ impl CmdUpload {
|
|||
.visible_alias("up")
|
||||
.arg(
|
||||
Arg::with_name("FILE")
|
||||
.help("The file to upload")
|
||||
.help("The file(s) to upload")
|
||||
.required(true)
|
||||
.multiple(false),
|
||||
.multiple(true),
|
||||
)
|
||||
.arg(ArgPassword::build().help("Protect the file with a password"))
|
||||
.arg(ArgGenPassphrase::build())
|
||||
|
|
Loading…
Reference in a new issue