Browse Source

Add --clear/--rm flag/option to clear history/remove specific item

timvisee 6 years ago
parent
commit
98fe01c218
6 changed files with 116 additions and 11 deletions
  1. 49 4
      src/action/history.rs
  2. 30 0
      src/cmd/matcher/history.rs
  3. 1 0
      src/cmd/subcmd/delete.rs
  4. 16 1
      src/cmd/subcmd/history.rs
  5. 19 5
      src/history.rs
  6. 1 1
      src/history_tool.rs

+ 49 - 4
src/action/history.rs

@@ -1,10 +1,11 @@
 use clap::ArgMatches;
+use failure::Fail;
 use prettytable::{format::FormatBuilder, Cell, Row, Table};
 
-use crate::cmd::matcher::{main::MainMatcher, Matcher};
+use crate::cmd::matcher::{history::HistoryMatcher, main::MainMatcher, Matcher};
 use crate::error::ActionError;
 use crate::history::{History as HistoryManager, LoadError as HistoryLoadError};
-use crate::util::format_duration;
+use crate::util::{format_duration, quit_error, quit_error_msg, ErrorHintsBuilder};
 
 /// A history action.
 pub struct History<'a> {
@@ -22,6 +23,7 @@ impl<'a> History<'a> {
     pub fn invoke(&self) -> Result<(), ActionError> {
         // Create the command matchers
         let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
+        let matcher_history = HistoryMatcher::with(self.cmd_matches).unwrap();
 
         // Get the history path, make sure it exists
         let history_path = matcher_main.history();
@@ -33,7 +35,7 @@ impl<'a> History<'a> {
         }
 
         // History
-        let history = HistoryManager::load(history_path)?;
+        let mut history = HistoryManager::load(history_path)?;
 
         // Do not report any files if there aren't any
         if history.files().is_empty() {
@@ -43,6 +45,49 @@ impl<'a> History<'a> {
             return Ok(());
         }
 
+        // Clear all history
+        if matcher_history.clear() {
+            history.clear();
+
+            // Save history
+            if let Err(err) = history.save() {
+                quit_error(
+                    err,
+                    ErrorHintsBuilder::default().verbose(true).build().unwrap(),
+                );
+            }
+
+            eprintln!("History cleared");
+            return Ok(());
+        }
+
+        // Remove history item
+        if let Some(url) = matcher_history.rm() {
+            // Remove item, print error if no item with URL was foudn
+            match history.remove_url(url) {
+                Ok(removed) if !removed => quit_error_msg(
+                    "could not remove item from history, no item matches given URL",
+                    ErrorHintsBuilder::default().verbose(true).build().unwrap(),
+                ),
+                Err(err) => quit_error(
+                    err.context("could not remove item from history"),
+                    ErrorHintsBuilder::default().verbose(true).build().unwrap(),
+                ),
+                _ => {}
+            }
+
+            // Save history
+            if let Err(err) = history.save() {
+                quit_error(
+                    err,
+                    ErrorHintsBuilder::default().verbose(true).build().unwrap(),
+                );
+            }
+
+            eprintln!("Item removed from history");
+            return Ok(());
+        }
+
         // Get the list of files, and sort the first expiring files to be last
         let mut files = history.files().clone();
         files.sort_by(|a, b| b.expire_at().cmp(&a.expire_at()));
@@ -50,7 +95,7 @@ impl<'a> History<'a> {
         // Log a history table, or just the URLs in quiet mode
         if !matcher_main.quiet() {
             // Build the list of column names
-            let mut columns = vec!["#", "LINK", "EXPIRY"];
+            let mut columns = vec!["#", "LINK", "EXPIRE"];
             if matcher_main.verbose() {
                 columns.push("OWNER TOKEN");
             }

+ 30 - 0
src/cmd/matcher/history.rs

@@ -1,6 +1,10 @@
 use clap::ArgMatches;
+use failure::Fail;
+use ffsend_api::url::Url;
 
 use super::Matcher;
+use crate::host::parse_host;
+use crate::util::{quit_error, ErrorHints};
 
 /// The history command matcher.
 pub struct HistoryMatcher<'a> {
@@ -8,6 +12,32 @@ pub struct HistoryMatcher<'a> {
     matches: &'a ArgMatches<'a>,
 }
 
+impl<'a> HistoryMatcher<'a> {
+    /// Check whether to clear all history.
+    pub fn clear(&self) -> bool {
+        self.matches.is_present("clear")
+    }
+
+    /// Check whether to remove a given entry from the history.
+    ///
+    /// This method parses the URL into an `Url`.
+    /// If the given URL is invalid,
+    /// the program will quit with an error message.
+    pub fn rm(&'a self) -> Option<Url> {
+        // Get the URL
+        let url = self.matches.value_of("rm")?;
+
+        // Parse the URL
+        match parse_host(&url) {
+            Ok(url) => Some(url),
+            Err(err) => quit_error(
+                err.context("failed to parse the given share URL"),
+                ErrorHints::default(),
+            ),
+        }
+    }
+}
+
 impl<'a> Matcher<'a> for HistoryMatcher<'a> {
     fn with(matches: &'a ArgMatches) -> Option<Self> {
         matches

+ 1 - 0
src/cmd/subcmd/delete.rs

@@ -10,6 +10,7 @@ impl CmdDelete {
         SubCommand::with_name("delete")
             .about("Delete a shared file")
             .visible_alias("del")
+            .visible_alias("rm")
             .arg(ArgUrl::build())
             .arg(ArgOwner::build())
     }

+ 16 - 1
src/cmd/subcmd/history.rs

@@ -1,4 +1,4 @@
-use clap::{App, SubCommand};
+use clap::{App, Arg, SubCommand};
 
 /// The history command definition.
 pub struct CmdHistory;
@@ -9,5 +9,20 @@ impl CmdHistory {
             .about("View file history")
             .visible_alias("h")
             .alias("ls")
+            .arg(
+                Arg::with_name("rm")
+                    .long("rm")
+                    .short("R")
+                    .alias("remove")
+                    .value_name("URL")
+                    .help("Remove history entry"),
+            )
+            .arg(
+                Arg::with_name("clear")
+                    .long("clear")
+                    .short("C")
+                    .alias("flush")
+                    .help("Clear all history"),
+            )
     }
 }

+ 19 - 5
src/history.rs

@@ -9,7 +9,8 @@ use self::toml::de::Error as DeError;
 use self::toml::ser::Error as SerError;
 use self::version_compare::{CompOp, VersionCompare};
 use failure::Fail;
-use ffsend_api::file::remote_file::RemoteFile;
+use ffsend_api::file::remote_file::{FileParseError, RemoteFile};
+use ffsend_api::url::Url;
 
 use crate::util::{print_error, print_warning};
 
@@ -167,16 +168,16 @@ impl History {
         self.changed = true;
     }
 
-    /// Remove the given remote file, matched by it's file ID.
+    /// Remove a file, matched by it's file ID.
     ///
     /// If any file was removed, true is returned.
-    pub fn remove(&mut self, file: &RemoteFile) -> bool {
+    pub fn remove(&mut self, id: &str) -> bool {
         // Get the indices of files that have expired
         let expired_indices: Vec<usize> = self
             .files
             .iter()
             .enumerate()
-            .filter(|&(_, f)| f.id() == file.id())
+            .filter(|&(_, f)| f.id() == id)
             .map(|(i, _)| i)
             .collect();
 
@@ -192,6 +193,13 @@ impl History {
         !expired_indices.is_empty()
     }
 
+    /// Remove a file by the given URL.
+    ///
+    /// If any file was removed, true is returned.
+    pub fn remove_url(&mut self, url: Url) -> Result<bool, FileParseError> {
+        Ok(self.remove(RemoteFile::parse_url(url, None)?.id()))
+    }
+
     /// Get all files.
     pub fn files(&self) -> &Vec<RemoteFile> {
         &self.files
@@ -207,6 +215,12 @@ impl History {
             .find(|f| f.id() == file.id() && f.host() == file.host())
     }
 
+    /// Clear all history.
+    pub fn clear(&mut self) {
+        self.changed = !self.files.is_empty();
+        self.files.clear();
+    }
+
     /// Garbage collect (remove) all files that have been expired,
     /// as defined by their `expire_at` property.
     ///
@@ -224,7 +238,7 @@ impl History {
 
         // Remove the files
         for f in &expired {
-            self.remove(f);
+            self.remove(f.id());
         }
 
         // Set the changed flag

+ 1 - 1
src/history_tool.rs

@@ -55,7 +55,7 @@ fn remove_error(matcher_main: &MainMatcher, file: &RemoteFile) -> Result<bool, H
 
     // Load the history, remove the file, and save
     let mut history = History::load_or_new(matcher_main.history())?;
-    let removed = history.remove(file);
+    let removed = history.remove(file.id());
     history.save()?;
     Ok(removed)
 }