mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-05 18:24:18 +02:00
Experiment with archiving, to support multifile/directory uploading
This commit is contained in:
parent
2798cdf1ac
commit
8e9da139d2
10 changed files with 235 additions and 34 deletions
|
@ -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"
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
74
cli/src/archive/archiver.rs
Normal file
74
cli/src/archive/archiver.rs
Normal 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
1
cli/src/archive/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod archiver;
|
|
@ -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")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -17,6 +17,7 @@ extern crate serde;
|
|||
extern crate serde_derive;
|
||||
|
||||
mod action;
|
||||
mod archive;
|
||||
mod cmd;
|
||||
mod error;
|
||||
#[cfg(feature = "history")]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue