Greatly improve logic for selecting smart target file
This commit is contained in:
parent
ff8c644194
commit
149e98a887
2 changed files with 166 additions and 2 deletions
|
@ -1,6 +1,10 @@
|
|||
use std::env::current_dir;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::{self, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use failure::{err_msg, Fail};
|
||||
use ffsend_api::action::download::{
|
||||
Download as ApiDownload,
|
||||
Error as DownloadError,
|
||||
|
@ -9,6 +13,10 @@ use ffsend_api::action::exists::{
|
|||
Error as ExistsError,
|
||||
Exists as ApiExists,
|
||||
};
|
||||
use ffsend_api::action::metadata::{
|
||||
Error as MetadataError,
|
||||
Metadata as ApiMetadata,
|
||||
};
|
||||
use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
|
||||
use ffsend_api::reqwest::Client;
|
||||
|
||||
|
@ -18,7 +26,7 @@ use cmd::matcher::{
|
|||
main::MainMatcher,
|
||||
};
|
||||
use progress::ProgressBar;
|
||||
use util::ensure_password;
|
||||
use util::{ensure_password, prompt_yes, quit, quit_error};
|
||||
|
||||
/// A file download action.
|
||||
pub struct Download<'a> {
|
||||
|
@ -47,7 +55,6 @@ impl<'a> Download<'a> {
|
|||
let client = Client::new();
|
||||
|
||||
// Parse the remote file based on the share URL
|
||||
// TODO: handle error here
|
||||
let file = RemoteFile::parse_url(url, None)?;
|
||||
|
||||
// Get the target file or directory, and the password
|
||||
|
@ -63,6 +70,20 @@ impl<'a> Download<'a> {
|
|||
// Ensure a password is set when required
|
||||
ensure_password(&mut password, exists.has_password(), &matcher_main);
|
||||
|
||||
// Fetch the file metadata
|
||||
let metadata = ApiMetadata::new(
|
||||
&file,
|
||||
password.clone(),
|
||||
false,
|
||||
).invoke(&client)?;
|
||||
|
||||
// Prepare the output path to use
|
||||
let target = Self::prepare_path(
|
||||
target,
|
||||
metadata.metadata().name(),
|
||||
&matcher_main,
|
||||
);
|
||||
|
||||
// Create a progress bar reporter
|
||||
let bar = Arc::new(Mutex::new(ProgressBar::new_download()));
|
||||
|
||||
|
@ -79,6 +100,134 @@ impl<'a> Download<'a> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This methods prepares a full file path to use for the file to
|
||||
/// download, based on the current directory, the original file name,
|
||||
/// and the user input.
|
||||
///
|
||||
/// If no file name was given, the original file name is used.
|
||||
///
|
||||
/// The full path including the name is returned.
|
||||
///
|
||||
/// This method will check whether a file is overwitten, and whether
|
||||
/// parent directories must be created.
|
||||
///
|
||||
/// The program will quit with an error message if a problem occurs.
|
||||
fn prepare_path(
|
||||
target: PathBuf,
|
||||
name_hint: &str,
|
||||
main_matcher: &MainMatcher,
|
||||
) -> PathBuf {
|
||||
// Select the path to use
|
||||
let target = Self::select_path(target, name_hint);
|
||||
|
||||
// Ask to overwrite
|
||||
if target.exists() && !main_matcher.force() {
|
||||
eprintln!(
|
||||
"The path '{}' already exists",
|
||||
target.to_str().unwrap_or("?"),
|
||||
);
|
||||
if !prompt_yes("Overwrite?", None, main_matcher) {
|
||||
println!("Download cancelled");
|
||||
quit();
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the parent directory exists
|
||||
match target.parent() {
|
||||
Some(parent) => if !parent.is_dir() {
|
||||
// Prompt to create them if not forced
|
||||
if !main_matcher.force() {
|
||||
eprintln!(
|
||||
"The directory '{}' doesn't exists",
|
||||
parent.to_str().unwrap_or("?"),
|
||||
);
|
||||
if !prompt_yes("Create it?", Some(true), main_matcher) {
|
||||
println!("Download cancelled");
|
||||
quit();
|
||||
}
|
||||
}
|
||||
|
||||
// Create the parent directories
|
||||
if let Err(err) = create_dir_all(parent) {
|
||||
quit_error(err.context(
|
||||
"Failed to create parent directories for output file",
|
||||
));
|
||||
}
|
||||
},
|
||||
None => quit_error(err_msg("Invalid output file path").compat()),
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/// This methods prepares a full file path to use for the file to
|
||||
/// download, based on the current directory, the original file name,
|
||||
/// and the user input.
|
||||
///
|
||||
/// If no file name was given, the original file name is used.
|
||||
///
|
||||
/// The full path including the file name will be returned.
|
||||
fn select_path(target: PathBuf, name_hint: &str) -> PathBuf {
|
||||
// If we're already working with a file, canonicalize and return
|
||||
if target.is_file() {
|
||||
match target.canonicalize() {
|
||||
Ok(target) => return target,
|
||||
Err(err) => quit_error(
|
||||
err.context("Failed to canonicalize target path")
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Append the name hint if this is a directory, canonicalize and return
|
||||
if target.is_dir() {
|
||||
match target.canonicalize() {
|
||||
Ok(target) => return target.join(name_hint),
|
||||
Err(err) => quit_error(
|
||||
err.context("Failed to canonicalize target path")
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: canonicalize parent if it exists
|
||||
|
||||
// Get the path string
|
||||
let path = target.to_str();
|
||||
|
||||
// If the path is emtpy, use the working directory with the name hint
|
||||
let use_workdir = path
|
||||
.map(|path| path.trim().is_empty())
|
||||
.unwrap_or(true);
|
||||
if use_workdir {
|
||||
match current_dir() {
|
||||
Ok(target) => return target.join(name_hint),
|
||||
Err(err) => quit_error(err.context(
|
||||
"Failed to determine working directory to use for the output file"
|
||||
)),
|
||||
}
|
||||
}
|
||||
let path = path.unwrap();
|
||||
|
||||
// Make the target mutable
|
||||
let mut target = target.clone();
|
||||
|
||||
// If the path ends with a separator, append the name hint
|
||||
if path.trim().ends_with(path::is_separator) {
|
||||
target = target.join(name_hint);
|
||||
}
|
||||
|
||||
// If relative, use the working directory as base
|
||||
if target.is_relative() {
|
||||
match current_dir() {
|
||||
Ok(workdir) => target = workdir.join(target),
|
||||
Err(err) => quit_error(err.context(
|
||||
"Failed to determine working directory to use for the output file"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
|
@ -92,6 +241,10 @@ pub enum Error {
|
|||
#[fail(display = "Failed to check whether the file exists")]
|
||||
Exists(#[cause] ExistsError),
|
||||
|
||||
/// An error occurred while fetching metadata.
|
||||
#[fail(display = "Failed to fetch file metadata")]
|
||||
Metadata(#[cause] MetadataError),
|
||||
|
||||
/// An error occurred while downloading the file.
|
||||
#[fail(display = "")]
|
||||
Download(#[cause] DownloadError),
|
||||
|
@ -113,6 +266,12 @@ impl From<ExistsError> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<MetadataError> for Error {
|
||||
fn from(err: MetadataError) -> Error {
|
||||
Error::Metadata(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DownloadError> for Error {
|
||||
fn from(err: DownloadError) -> Error {
|
||||
Error::Download(err)
|
||||
|
|
|
@ -48,6 +48,11 @@ pub fn print_error<E: Fail>(err: E) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Quit the application regularly.
|
||||
pub fn quit() -> ! {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/// Quit the application with an error code,
|
||||
/// and print the given error.
|
||||
pub fn quit_error<E: Fail>(err: E) -> ! {
|
||||
|
|
Loading…
Add table
Reference in a new issue