Experiment with archiving, to support multifile/directory uploading

This commit is contained in:
timvisee 2018-05-15 10:13:19 +02:00
parent 2798cdf1ac
commit 8e9da139d2
No known key found for this signature in database
GPG key ID: A28432A0AE6E6306
10 changed files with 235 additions and 34 deletions

View file

@ -39,5 +39,7 @@ prettytable-rs = "0.6"
rpassword = "2.0"
serde = "1.0"
serde_derive = "1.0"
tar = "0.4"
tempfile = "3"
toml = "0.4"
version-compare = "0.0.6"

View file

@ -1,3 +1,7 @@
// TODO: remove all expect unwraps, replace them with proper errors
extern crate tempfile;
use std::fs::File;
use std::path::Path;
use std::sync::{Arc, Mutex};
@ -8,7 +12,9 @@ use ffsend_api::action::params::ParamsDataBuilder;
use ffsend_api::action::upload::Upload as ApiUpload;
use ffsend_api::config::{UPLOAD_SIZE_MAX, UPLOAD_SIZE_MAX_RECOMMENDED};
use ffsend_api::reqwest::Client;
use self::tempfile::NamedTempFile;
use archive::archiver::Archiver;
use cmd::matcher::{Matcher, MainMatcher, UploadMatcher};
use error::ActionError;
#[cfg(feature = "history")]
@ -48,7 +54,7 @@ impl<'a> Upload<'a> {
let matcher_upload = UploadMatcher::with(self.cmd_matches).unwrap();
// Get API parameters
let path = Path::new(matcher_upload.file()).to_path_buf();
let mut path = Path::new(matcher_upload.file()).to_path_buf();
let host = matcher_upload.host();
// TODO: ensure the file exists and is accessible
@ -112,11 +118,59 @@ impl<'a> Upload<'a> {
}
};
// The file name to use
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
let mut tmp_archive: Option<NamedTempFile> = None;
// Archive the file if specified
if matcher_upload.archive() {
println!("Archiving file...");
// Create a new temporary file to write the archive to
tmp_archive = Some(
NamedTempFile::new().expect("failed to create temporary archive file"),
);
if let Some(tmp_archive) = &tmp_archive {
// Get the path, and the actual file
let archive_path = tmp_archive.path().clone().to_path_buf();
let archive_file = tmp_archive.as_file().try_clone()
.expect("failed to clone archive file");
// Select the file name to use if not set
if file_name.is_none() {
file_name = Some(
path.file_name()
.expect("failed to determine file name")
.to_str()
.map(|s| s.to_owned())
.expect("failed to create string from file name")
);
}
// Build an archiver and append the file
let mut archiver = Archiver::new(archive_file);
archiver.append_path(file_name.as_ref().unwrap(), &path)
.expect("failed to append file to archive");
// Finish the archival process, writes the archive file
archiver.finish().expect("failed to write archive file");
// Append archive extention to name, set to upload archived file
if let Some(ref mut file_name) = file_name {
file_name.push_str(".tar");
}
path = archive_path;
}
}
// Execute an upload action
let file = ApiUpload::new(
host,
path,
matcher_upload.name().map(|name| name.to_owned()),
path.clone(),
file_name,
matcher_upload.password(),
params,
).invoke(&client, bar)?;
@ -149,6 +203,11 @@ impl<'a> Upload<'a> {
}
}
// Close the temporary zip file, to ensure it's removed
if let Some(tmp_archive) = tmp_archive.take() {
tmp_archive.close();
}
Ok(())
}
}

View file

@ -0,0 +1,74 @@
extern crate tar;
use std::fs::File;
use std::io::{
Error as IoError,
Write,
};
use std::path::Path;
use self::tar::Builder as TarBuilder;
pub type Result<T> = ::std::result::Result<T, IoError>;
pub struct Archiver<W: Write> {
/// The tar builder.
inner: TarBuilder<W>,
}
impl<W: Write> Archiver<W> {
/// Construct a new archive builder.
pub fn new(writer: W) -> Archiver<W> {
Archiver {
inner: TarBuilder::new(writer),
}
}
/// Add the entry at the given `src` path, to the given relative `path` in the archive.
///
/// If a directory path is given, the whole directory including it's contents is added to the
/// archive.
///
/// 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>,
{
// Append the path as file or directory
if src_path.as_ref().is_file() {
self.append_file(path, &mut File::open(src_path)?)
} else if src_path.as_ref().is_dir() {
self.append_dir(path, src_path)
} else {
// TODO: return a IO NotFound error here!
panic!("Unable to append path to archive, not a file or directory");
}
}
/// Append a file to the archive builder.
pub fn append_file<P>(&mut self, path: P, file: &mut File)
-> Result<()>
where
P: AsRef<Path>,
{
self.inner.append_file(path, file)
}
/// 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>,
{
self.inner.append_dir_all(path, src_path)
}
// TODO: some description
pub fn finish(mut self) -> Result<()> {
self.inner.finish()
}
}

1
cli/src/archive/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod archiver;

View file

@ -32,6 +32,11 @@ impl CmdUpload {
.alias("f")
.value_name("NAME")
.help("Rename the file being uploaded"))
.arg(Arg::with_name("archive")
.long("archive")
.short("a")
.alias("arch")
.help("Package the file as an archive"))
.arg(Arg::with_name("open")
.long("open")
.short("o")

View file

@ -48,7 +48,10 @@ impl<'a: 'b, 'b> Handler<'a> {
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.after_help("This application is not affiliated with Mozilla, Firefox or Firefox Send.")
.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.

View file

@ -71,6 +71,12 @@ impl<'a: 'b, 'b> UploadMatcher<'a> {
})
}
/// Check whether to archive the file to upload.
/// TODO: infer to use this flag if a directory is selected
pub fn archive(&self) -> bool {
self.matches.is_present("archive")
}
/// Check whether to open the file URL in the user's browser.
pub fn open(&self) -> bool {
self.matches.is_present("open")

View file

@ -17,6 +17,7 @@ extern crate serde;
extern crate serde_derive;
mod action;
mod archive;
mod cmd;
mod error;
#[cfg(feature = "history")]