Browse Source

Ensure there is enough free disk space, allow info messages with errors

timvisee 7 years ago
parent
commit
f348e603e2
5 changed files with 111 additions and 8 deletions
  1. 11 0
      Cargo.lock
  2. 0 1
      ROADMAP.md
  3. 1 0
      cli/Cargo.toml
  4. 6 0
      cli/src/action/download.rs
  5. 93 7
      cli/src/util.rs

+ 11 - 0
Cargo.lock

@@ -363,6 +363,7 @@ dependencies = [
  "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "ffsend-api 0.0.1",
+ "fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "open 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -412,6 +413,15 @@ name = "foreign-types-shared"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "fuchsia-zircon"
 version = "0.3.3"
@@ -1628,6 +1638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
 "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
 "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+"checksum fs2 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
 "checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"

+ 0 - 1
ROADMAP.md

@@ -1,6 +1,5 @@
 # Release 0.1
 - Panic when secret is missing from URL with info action
-- History compiler flag
 - Lowercase error messages
 - Switch to `directories` instead of `app_dirs2`?
 - Allow file/directory archiving on upload

+ 1 - 0
cli/Cargo.toml

@@ -25,6 +25,7 @@ derive_builder = "0.5"
 failure = "0.1"
 failure_derive = "0.1"
 ffsend-api = { version = "*", path = "../api" }
+fs2 = "0.4"
 lazy_static = "1.0"
 open = "1"
 pbr = "1"

+ 6 - 0
cli/src/action/download.rs

@@ -28,6 +28,7 @@ use cmd::matcher::{
 use history_tool;
 use progress::ProgressBar;
 use util::{
+    ensure_enough_space,
     ensure_password,
     ErrorHints,
     prompt_yes,
@@ -95,6 +96,11 @@ impl<'a> Download<'a> {
             &matcher_main,
         );
 
+        // Ensure there is enough disk space available when not being forced
+        if !matcher_main.force() {
+            ensure_enough_space(target.parent().unwrap(), metadata.size());
+        }
+
         // Create a progress bar reporter
         let bar = Arc::new(Mutex::new(ProgressBar::new_download()));
 

+ 93 - 7
cli/src/util.rs

@@ -1,6 +1,7 @@
 #[cfg(feature = "clipboard")]
 extern crate clipboard;
 extern crate colored;
+extern crate fs2;
 extern crate open;
 
 use std::env::current_exe;
@@ -13,15 +14,17 @@ use std::io::{
     stderr,
     Write,
 };
+use std::path::Path;
 use std::process::{exit, ExitStatus};
 
 use chrono::Duration;
 #[cfg(feature = "clipboard")]
-use self::clipboard::{ClipboardContext, ClipboardProvider};
-use self::colored::*;
 use failure::{err_msg, Fail};
 use ffsend_api::url::Url;
 use rpassword::prompt_password_stderr;
+use self::clipboard::{ClipboardContext, ClipboardProvider};
+use self::colored::*;
+use self::fs2::available_space;
 
 use cmd::matcher::MainMatcher;
 
@@ -38,15 +41,15 @@ pub fn print_error<E: Fail>(err: E) {
         .filter(|err| !err.is_empty())
         .enumerate()
         .map(|(i, err)| if i == 0 {
-            eprintln!("{} {}", "error:".red().bold(), err);
+            eprintln!("{} {}", highlight_error("error:"), err);
         } else {
-            eprintln!("{} {}", "caused by:".red().bold(), err);
+            eprintln!("{} {}", highlight_error("caused by:"), err);
         })
         .count();
 
     // Fall back to a basic message
     if count == 0 {
-        eprintln!("{} {}", "error:".red().bold(), "An undefined error occurred");
+        eprintln!("{} {}", highlight_error("error:"), "An undefined error occurred");
     }
 }
 
@@ -64,7 +67,7 @@ pub fn print_warning<S>(err: S)
     where
         S: AsRef<str> + Display + Debug + Sync + Send + 'static
 {
-    eprintln!("{} {}", "warning:".yellow().bold(), err);
+    eprintln!("{} {}", highlight_warning("warning:"), err);
 }
 
 /// Quit the application regularly.
@@ -95,9 +98,12 @@ pub fn quit_error_msg<S>(err: S, hints: ErrorHints) -> !
 }
 
 /// The error hint configuration.
-#[derive(Copy, Clone, Builder)]
+#[derive(Clone, Builder)]
 #[builder(default)]
 pub struct ErrorHints {
+    /// A list of info messages to print along with the error.
+    info: Vec<String>,
+
     /// Show about the password option.
     password: bool,
 
@@ -130,6 +136,11 @@ impl ErrorHints {
 
     /// Print the error hints.
     pub fn print(&self) {
+        // Print info messages
+        for msg in &self.info {
+            eprintln!("{} {}", highlight_info("info:"), msg);
+        }
+
         // Stop if nothing should be printed
         if !self.any() {
             return;
@@ -165,6 +176,7 @@ impl ErrorHints {
 impl Default for ErrorHints {
     fn default() -> Self {
         ErrorHints {
+            info: Vec::new(),
             password: false,
             owner: false,
             history: false,
@@ -175,11 +187,43 @@ impl Default for ErrorHints {
     }
 }
 
+impl ErrorHintsBuilder {
+    /// Add a single info entry.
+    pub fn add_info(mut self, info: String) -> Self {
+        // Initialize the info list
+        if self.info.is_none() {
+            self.info = Some(Vec::new());
+        }
+
+        // Add the item to the info list
+        if let Some(ref mut list) = self.info {
+            list.push(info);
+        }
+
+        self
+    }
+}
+
 /// Highlight the given text with a color.
 pub fn highlight(msg: &str) -> ColoredString {
     msg.yellow()
 }
 
+/// Highlight the given text with an error color.
+pub fn highlight_error(msg: &str) -> ColoredString {
+    msg.red().bold()
+}
+
+/// Highlight the given text with an warning color.
+pub fn highlight_warning(msg: &str) -> ColoredString {
+    highlight(msg).bold()
+}
+
+/// Highlight the given text with an info color
+pub fn highlight_info(msg: &str) -> ColoredString {
+    msg.cyan()
+}
+
 /// Open the given URL in the users default browser.
 /// The browsers exit statis is returned.
 pub fn open_url(url: Url) -> Result<ExitStatus, IoError> {
@@ -501,3 +545,45 @@ pub fn exe_name() -> String {
         .and_then(|n| n.into_string().ok())
         .unwrap_or(crate_name!().into())
 }
+
+/// Check whether there is enough space avaialble at the given `path` to store a file
+/// with the given `size`.
+///
+/// If an error occurred while querying the file system,
+/// the error is reported to the user, and `true` is returned.
+///
+/// `false` is only returned when sure that there isn't enough space available.
+pub fn ensure_enough_space<P: AsRef<Path>>(path: P, size: u64) {
+    // Get the available space at this path
+    let space = match available_space(path) {
+        Ok(space) => space,
+        Err(err) => {
+            print_error(err.context("Failed to check available space on disk, ignoring"));
+            return;
+        },
+    };
+
+    // Return if enough disk space is avaiable
+    if space >= size {
+        return;
+    }
+
+    // Create an info message giving details about the required space
+    let info = format!(
+        "{} is required, but only {} is available",
+        format_bytes(size),
+        format_bytes(space),
+    );
+
+    // Print an descriptive error and quit
+    quit_error(
+        err_msg("Not enough disk space available in the target directory")
+            .context("Failed to download file"),
+        ErrorHintsBuilder::default()
+            .add_info(info)
+            .force(true)
+            .verbose(false)
+            .build()
+            .unwrap(),
+    );
+}