Add support to upload file from stdin

Fixes https://github.com/timvisee/ffsend/issues/39
This commit is contained in:
timvisee 2021-04-09 15:53:06 +02:00
parent 9908084745
commit 37cdba8ddd
No known key found for this signature in database
GPG key ID: B8DB720BC383E172
2 changed files with 80 additions and 8 deletions

View file

@ -1,6 +1,6 @@
use std::env::current_dir;
use std::fs;
use std::io::Error as IoError;
use std::io::{Error as IoError, Write};
use std::path::Path;
#[cfg(feature = "archive")]
use std::path::PathBuf;
@ -36,7 +36,7 @@ use crate::urlshorten;
use crate::util::set_clipboard;
use crate::util::{
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.
@ -57,20 +57,49 @@ impl<'a> Upload<'a> {
let matcher_main = MainMatcher::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
#[allow(unused_mut)]
let mut paths: Vec<_> = matcher_upload
.files()
let mut paths: Vec<_> = files
.into_iter()
.map(|p| Path::new(p).to_path_buf())
.collect();
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());
// All paths must exist
// TODO: ensure the file exists and is accessible
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")]
{
// 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.
#[fail(display = "failed to delete local file")]
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 {

View file

@ -11,6 +11,7 @@ use std::fmt;
use std::fmt::{Debug, Display};
#[cfg(feature = "clipboard-bin")]
use std::io::ErrorKind as IoErrorKind;
use std::io::{self, Read};
use std::io::{stderr, stdin, Error as IoError, Write};
use std::iter;
use std::path::Path;
@ -1131,3 +1132,27 @@ pub fn rand_alphanum_string(len: usize) -> String {
.take(len)
.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),
}