mirror of
https://github.com/timvisee/ffsend.git
synced 2025-10-03 17:49:15 +02:00
Allow specifying an output file or directory when downloading [WIP]
This commit is contained in:
parent
649190b17a
commit
8259106c17
5 changed files with 109 additions and 22 deletions
15
IDEAS.md
15
IDEAS.md
|
@ -1,12 +1,15 @@
|
||||||
# Ideas
|
# Ideas
|
||||||
- Rename DownloadFile to RemoteFile
|
- custom file name when uploading
|
||||||
|
- allow creating non existent directories with the `-f` flag
|
||||||
|
- only allow file extension renaming on upload with `-f` flag
|
||||||
|
- no interact flag
|
||||||
|
- `-y` flag for assume yes
|
||||||
|
- `-f` flag for forcing (no interact?)
|
||||||
- Box errors
|
- Box errors
|
||||||
- Info endpoint, to view file info
|
- Info endpoint, to view file info
|
||||||
- On download, mention a wrong or missing password with a HTTP 401 response
|
- On download, mention a wrong or missing password with a HTTP 401 response
|
||||||
- Automatically get owner token, from file history when setting password
|
- Automatically get owner token, from file history when setting password
|
||||||
- Implement error handling everywhere properly
|
- Implement error handling everywhere properly
|
||||||
- `-y` flag for assume yes
|
|
||||||
- `-f` flag for forcing (no interact?)
|
|
||||||
- Quick upload/download without `upload` or `download` subcommands.
|
- Quick upload/download without `upload` or `download` subcommands.
|
||||||
- Set file password
|
- Set file password
|
||||||
- Set file download count
|
- Set file download count
|
||||||
|
@ -33,4 +36,8 @@
|
||||||
- Ubuntu PPA package
|
- Ubuntu PPA package
|
||||||
- Move API URL generator methods out of remote file class
|
- Move API URL generator methods out of remote file class
|
||||||
- Prompt if a file download password is required
|
- Prompt if a file download password is required
|
||||||
- Do not allow empty passwords (must force with `-f`)
|
- Do not allow empty passwords (must force with `-f`) (as not usable on web)
|
||||||
|
- Must use `-f` to overwrite existing file
|
||||||
|
- Rename host to server?
|
||||||
|
- Read and write files from and to stdin and stdout with `-` as file
|
||||||
|
- Ask to add MIME extension to downloaded files without one on Windows
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::io::{
|
||||||
Error as IoError,
|
Error as IoError,
|
||||||
Read,
|
Read,
|
||||||
};
|
};
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use failure::Error as FailureError;
|
use failure::Error as FailureError;
|
||||||
|
@ -34,15 +35,23 @@ pub struct Download<'a> {
|
||||||
/// The remote file to download.
|
/// The remote file to download.
|
||||||
file: &'a RemoteFile,
|
file: &'a RemoteFile,
|
||||||
|
|
||||||
|
/// The target file or directory, to download the file to.
|
||||||
|
target: PathBuf,
|
||||||
|
|
||||||
/// An optional password to decrypt a protected file.
|
/// An optional password to decrypt a protected file.
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Download<'a> {
|
impl<'a> Download<'a> {
|
||||||
/// Construct a new download action for the given remote file.
|
/// Construct a new download action for the given remote file.
|
||||||
pub fn new(file: &'a RemoteFile, password: Option<String>) -> Self {
|
pub fn new(
|
||||||
|
file: &'a RemoteFile,
|
||||||
|
target: PathBuf,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
file,
|
file,
|
||||||
|
target,
|
||||||
password,
|
password,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,15 +68,25 @@ impl<'a> Download<'a> {
|
||||||
// Fetch the authentication nonce
|
// Fetch the authentication nonce
|
||||||
let auth_nonce = self.fetch_auth_nonce(client)?;
|
let auth_nonce = self.fetch_auth_nonce(client)?;
|
||||||
|
|
||||||
// Fetch the meta nonce, set the input vector
|
// Fetch the meta data, apply the derived input vector
|
||||||
let meta_nonce = self.fetch_meta_nonce(&client, &mut key, auth_nonce)?;
|
let (metadata, meta_nonce) = self.fetch_metadata_apply_iv(
|
||||||
|
&client,
|
||||||
|
&mut key,
|
||||||
|
auth_nonce,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Decide what actual file target to use
|
||||||
|
let path = self.decide_path(metadata.name());
|
||||||
|
let path_str = path.to_str().unwrap_or("?").to_owned();
|
||||||
|
|
||||||
// Open the file we will write to
|
// Open the file we will write to
|
||||||
// TODO: this should become a temporary file first
|
// TODO: this should become a temporary file first
|
||||||
// TODO: use the uploaded file name as default
|
// TODO: use the uploaded file name as default
|
||||||
let path = "downloaded.zip";
|
|
||||||
let out = File::create(path)
|
let out = File::create(path)
|
||||||
.map_err(|err| Error::File(path.into(), FileError::Create(err)))?;
|
.map_err(|err| Error::File(
|
||||||
|
path_str.clone(),
|
||||||
|
FileError::Create(err),
|
||||||
|
))?;
|
||||||
|
|
||||||
// Create the file reader for downloading
|
// Create the file reader for downloading
|
||||||
let (reader, len) = self.create_file_reader(&key, meta_nonce, &client)?;
|
let (reader, len) = self.create_file_reader(&key, meta_nonce, &client)?;
|
||||||
|
@ -78,7 +97,7 @@ impl<'a> Download<'a> {
|
||||||
len,
|
len,
|
||||||
&key,
|
&key,
|
||||||
reporter.clone(),
|
reporter.clone(),
|
||||||
).map_err(|err| Error::File(path.into(), err))?;
|
).map_err(|err| Error::File(path_str.clone(), err))?;
|
||||||
|
|
||||||
// Download the file
|
// Download the file
|
||||||
self.download(reader, writer, len, reporter)?;
|
self.download(reader, writer, len, reporter)?;
|
||||||
|
@ -127,24 +146,29 @@ impl<'a> Download<'a> {
|
||||||
).map_err(|_| AuthError::MalformedNonce.into())
|
).map_err(|_| AuthError::MalformedNonce.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch the metadata nonce.
|
|
||||||
/// This method also sets the input vector on the given key set,
|
/// Create a metadata nonce, and fetch the metadata for the file from the
|
||||||
/// extracted from the metadata.
|
/// server.
|
||||||
///
|
///
|
||||||
/// The key set, along with the authentication nonce must be given.
|
/// The key set, along with the authentication nonce must be given.
|
||||||
/// The meta nonce is returned.
|
///
|
||||||
fn fetch_meta_nonce(
|
/// The metadata, with the meta nonce is returned.
|
||||||
|
///
|
||||||
|
/// This method is similar to `fetch_metadata`, and additionally applies
|
||||||
|
/// the derived input vector to the given key set.
|
||||||
|
fn fetch_metadata_apply_iv(
|
||||||
&self,
|
&self,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
key: &mut KeySet,
|
key: &mut KeySet,
|
||||||
auth_nonce: Vec<u8>,
|
auth_nonce: Vec<u8>,
|
||||||
) -> Result<Vec<u8>, MetaError> {
|
) -> Result<(Metadata, Vec<u8>), MetaError> {
|
||||||
// Fetch the metadata and the nonce
|
// Fetch the metadata and the nonce
|
||||||
let (metadata, meta_nonce) = self.fetch_metadata(client, key, auth_nonce)?;
|
let data = self.fetch_metadata(client, key, auth_nonce)?;
|
||||||
|
|
||||||
// Set the input vector, and return the nonce
|
// Set the input vector bas
|
||||||
key.set_iv(metadata.iv());
|
key.set_iv(data.0.iv());
|
||||||
Ok(meta_nonce)
|
|
||||||
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a metadata nonce, and fetch the metadata for the file from the
|
/// Create a metadata nonce, and fetch the metadata for the file from the
|
||||||
|
@ -203,6 +227,35 @@ impl<'a> Download<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decide what path we will download the file to.
|
||||||
|
///
|
||||||
|
/// A target file or directory, and a file name hint must be given.
|
||||||
|
/// The name hint can be derived from the retrieved metadata on this file.
|
||||||
|
///
|
||||||
|
/// The name hint is used as file name, if a directory was given.
|
||||||
|
fn decide_path(&self, name_hint: &str) -> PathBuf {
|
||||||
|
// Return the target if it is an existing file
|
||||||
|
if self.target.is_file() {
|
||||||
|
return self.target.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the name hint if this is a directory
|
||||||
|
if self.target.is_dir() {
|
||||||
|
return self.target.join(name_hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if the parent is an existing directory
|
||||||
|
if self.target.parent().map(|p| p.is_dir()).unwrap_or(false) {
|
||||||
|
return self.target.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: canonicalize the path when possible
|
||||||
|
// TODO: allow using `file.toml` as target without directory indication
|
||||||
|
// TODO: return a nice error here as the path may be invalid
|
||||||
|
// TODO: maybe prompt the user to create the directory
|
||||||
|
panic!("Invalid (non-existing) output path given, not yet supported");
|
||||||
|
}
|
||||||
|
|
||||||
/// Make a download request, and create a reader that downloads the
|
/// Make a download request, and create a reader that downloads the
|
||||||
/// encrypted file.
|
/// encrypted file.
|
||||||
///
|
///
|
||||||
|
|
|
@ -47,6 +47,11 @@ impl Metadata {
|
||||||
serde_json::to_string(&self).unwrap()
|
serde_json::to_string(&self).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the file name.
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the input vector
|
/// Get the input vector
|
||||||
// TODO: use an input vector length from a constant
|
// TODO: use an input vector length from a constant
|
||||||
pub fn iv(&self) -> [u8; 12] {
|
pub fn iv(&self) -> [u8; 12] {
|
||||||
|
|
|
@ -34,12 +34,18 @@ impl<'a> Download<'a> {
|
||||||
// TODO: handle error here
|
// TODO: handle error here
|
||||||
let file = RemoteFile::parse_url(url, None)?;
|
let file = RemoteFile::parse_url(url, None)?;
|
||||||
|
|
||||||
|
// Get the target file or directory
|
||||||
|
let target = self.cmd.file();
|
||||||
|
|
||||||
// Create a progress bar reporter
|
// Create a progress bar reporter
|
||||||
let bar = Arc::new(Mutex::new(ProgressBar::new_download()));
|
let bar = Arc::new(Mutex::new(ProgressBar::new_download()));
|
||||||
|
|
||||||
// Execute an download action
|
// Execute an download action
|
||||||
ApiDownload::new(&file, self.cmd.password())
|
ApiDownload::new(
|
||||||
.invoke(&client, bar)?;
|
&file,
|
||||||
|
target,
|
||||||
|
self.cmd.password(),
|
||||||
|
).invoke(&client, bar)?;
|
||||||
|
|
||||||
// TODO: open the file, or it's location
|
// TODO: open the file, or it's location
|
||||||
// TODO: copy the file location
|
// TODO: copy the file location
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use ffsend_api::url::{ParseError, Url};
|
use ffsend_api::url::{ParseError, Url};
|
||||||
|
|
||||||
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
use super::clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
@ -23,6 +25,11 @@ impl<'a: 'b, 'b> CmdDownload<'a> {
|
||||||
.help("The share URL")
|
.help("The share URL")
|
||||||
.required(true)
|
.required(true)
|
||||||
.multiple(false))
|
.multiple(false))
|
||||||
|
.arg(Arg::with_name("file")
|
||||||
|
.long("file")
|
||||||
|
.short("f")
|
||||||
|
.value_name("PATH")
|
||||||
|
.help("The output file or directory"))
|
||||||
.arg(Arg::with_name("password")
|
.arg(Arg::with_name("password")
|
||||||
.long("password")
|
.long("password")
|
||||||
.short("p")
|
.short("p")
|
||||||
|
@ -71,6 +78,15 @@ impl<'a: 'b, 'b> CmdDownload<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The target file or directory to download the file to.
|
||||||
|
/// If a directory is given, the file name of the original uploaded file
|
||||||
|
/// will be used.
|
||||||
|
pub fn file(&'a self) -> PathBuf {
|
||||||
|
self.matches.value_of("file")
|
||||||
|
.map(|path| PathBuf::from(path))
|
||||||
|
.unwrap_or(PathBuf::from("./"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the password.
|
/// Get the password.
|
||||||
/// `None` is returned if no password was specified.
|
/// `None` is returned if no password was specified.
|
||||||
pub fn password(&'a self) -> Option<String> {
|
pub fn password(&'a self) -> Option<String> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue