Add support to upload file from stdin
Fixes https://github.com/timvisee/ffsend/issues/39
This commit is contained in:
parent
9908084745
commit
37cdba8ddd
2 changed files with 80 additions and 8 deletions
|
@ -1,6 +1,6 @@
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Error as IoError;
|
use std::io::{Error as IoError, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
#[cfg(feature = "archive")]
|
#[cfg(feature = "archive")]
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -36,7 +36,7 @@ use crate::urlshorten;
|
||||||
use crate::util::set_clipboard;
|
use crate::util::set_clipboard;
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
format_bytes, open_url, print_error, print_error_msg, prompt_yes, quit, quit_error_msg,
|
format_bytes, open_url, print_error, print_error_msg, prompt_yes, quit, quit_error_msg,
|
||||||
rand_alphanum_string, ErrorHintsBuilder,
|
rand_alphanum_string, stdin_read_file, ErrorHintsBuilder, StdinErr,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A file upload action.
|
/// A file upload action.
|
||||||
|
@ -57,20 +57,49 @@ impl<'a> Upload<'a> {
|
||||||
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
|
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
|
||||||
let matcher_upload = UploadMatcher::with(self.cmd_matches).unwrap();
|
let matcher_upload = UploadMatcher::with(self.cmd_matches).unwrap();
|
||||||
|
|
||||||
|
// The file name to use
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut file_name = matcher_upload.name().map(|s| s.to_owned());
|
||||||
|
|
||||||
|
// The selected files
|
||||||
|
let mut files = matcher_upload.files();
|
||||||
|
|
||||||
|
// If file is `-`, upload from stdin
|
||||||
|
// TODO: write stdin directly to file, or directly to upload buffer
|
||||||
|
let mut tmp_stdin: Option<NamedTempFile> = None;
|
||||||
|
if files.len() == 1 && files[0] == "-" {
|
||||||
|
// Obtain data from stdin
|
||||||
|
let data = stdin_read_file(!matcher_main.quiet()).map_err(Error::Stdin)?;
|
||||||
|
|
||||||
|
// Create temporary stdin buffer file
|
||||||
|
tmp_stdin = Some(
|
||||||
|
TempBuilder::new()
|
||||||
|
.prefix(&format!(".{}-stdin-", crate_name!()))
|
||||||
|
.tempfile()
|
||||||
|
.map_err(Error::StdinTempFile)?,
|
||||||
|
);
|
||||||
|
let file = tmp_stdin.as_ref().unwrap();
|
||||||
|
|
||||||
|
// Fill temporary file with data, update list of files we upload, suggest name
|
||||||
|
file.as_file()
|
||||||
|
.write_all(&data)
|
||||||
|
.map_err(Error::StdinTempFile)?;
|
||||||
|
files = vec![file
|
||||||
|
.path()
|
||||||
|
.to_str()
|
||||||
|
.expect("failed to obtain file name for stdin buffer file")];
|
||||||
|
file_name = file_name.or_else(|| Some("stdin.txt".into()));
|
||||||
|
}
|
||||||
|
|
||||||
// Get API parameters
|
// Get API parameters
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut paths: Vec<_> = matcher_upload
|
let mut paths: Vec<_> = files
|
||||||
.files()
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| Path::new(p).to_path_buf())
|
.map(|p| Path::new(p).to_path_buf())
|
||||||
.collect();
|
.collect();
|
||||||
let mut path = Path::new(paths.first().unwrap()).to_path_buf();
|
let mut path = Path::new(paths.first().unwrap()).to_path_buf();
|
||||||
let host = matcher_upload.host();
|
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());
|
|
||||||
|
|
||||||
// All paths must exist
|
// All paths must exist
|
||||||
// TODO: ensure the file exists and is accessible
|
// TODO: ensure the file exists and is accessible
|
||||||
for path in &paths {
|
for path in &paths {
|
||||||
|
@ -454,6 +483,16 @@ impl<'a> Upload<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close the temporary stdin buffer file, to ensure it's removed
|
||||||
|
if let Some(tmp_stdin) = tmp_stdin.take() {
|
||||||
|
if let Err(err) = tmp_stdin.close() {
|
||||||
|
print_error(
|
||||||
|
err.context("failed to clean up temporary stdin buffer file, ignoring")
|
||||||
|
.compat(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "archive")]
|
#[cfg(feature = "archive")]
|
||||||
{
|
{
|
||||||
// Close the temporary zip file, to ensure it's removed
|
// Close the temporary zip file, to ensure it's removed
|
||||||
|
@ -581,6 +620,14 @@ pub enum Error {
|
||||||
/// An error occurred while deleting a local file after upload.
|
/// An error occurred while deleting a local file after upload.
|
||||||
#[fail(display = "failed to delete local file")]
|
#[fail(display = "failed to delete local file")]
|
||||||
Delete(#[cause] IoError),
|
Delete(#[cause] IoError),
|
||||||
|
|
||||||
|
/// An error occurred while reading data from stdin.
|
||||||
|
#[fail(display = "failed to read data from stdin")]
|
||||||
|
Stdin(#[cause] StdinErr),
|
||||||
|
|
||||||
|
/// An error occurred while creating the temporary stdin file.
|
||||||
|
#[fail(display = "failed to create temporary stdin buffer file")]
|
||||||
|
StdinTempFile(#[cause] IoError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<VersionError> for Error {
|
impl From<VersionError> for Error {
|
||||||
|
|
25
src/util.rs
25
src/util.rs
|
@ -11,6 +11,7 @@ use std::fmt;
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
#[cfg(feature = "clipboard-bin")]
|
#[cfg(feature = "clipboard-bin")]
|
||||||
use std::io::ErrorKind as IoErrorKind;
|
use std::io::ErrorKind as IoErrorKind;
|
||||||
|
use std::io::{self, Read};
|
||||||
use std::io::{stderr, stdin, Error as IoError, Write};
|
use std::io::{stderr, stdin, Error as IoError, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -1131,3 +1132,27 @@ pub fn rand_alphanum_string(len: usize) -> String {
|
||||||
.take(len)
|
.take(len)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read file from stdin.
|
||||||
|
pub fn stdin_read_file(prompt: bool) -> Result<Vec<u8>, StdinErr> {
|
||||||
|
if prompt {
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
eprintln!("Enter input. Use [CTRL+D] to stop:");
|
||||||
|
#[cfg(windows)]
|
||||||
|
eprintln!("Enter input. Use [CTRL+Z] to stop:");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = vec![];
|
||||||
|
io::stdin()
|
||||||
|
.lock()
|
||||||
|
.read_to_end(&mut data)
|
||||||
|
.map_err(StdinErr::Stdin)?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// URL following error.
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum StdinErr {
|
||||||
|
#[fail(display = "failed to read from stdin")]
|
||||||
|
Stdin(#[cause] io::Error),
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue