Allow uploading multiple files by archiving, rework archiving

This commit is contained in:
timvisee 2019-06-20 21:16:33 +02:00
parent 12503d0d21
commit ce39eada4c
No known key found for this signature in database
GPG key ID: B8DB720BC383E172
5 changed files with 151 additions and 82 deletions

7
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

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