diff --git a/Cargo.lock b/Cargo.lock index cfd54de..8c960ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,12 +9,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "anyhow" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" - [[package]] name = "arrayref" version = "0.3.6" @@ -194,7 +188,6 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" name = "garbage" version = "0.2.2" dependencies = [ - "anyhow", "chrono", "chrono-humanize", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index d4b129b..705493e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ name = "garbage" path = "src/main.rs" [dependencies] -anyhow = "1.0" chrono = "0.4" chrono-humanize = "0.1.1" lazy_static = "1.4" diff --git a/src/dir.rs b/src/dir.rs index 0892ada..b4efc1f 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -131,6 +131,7 @@ impl Iterator for TrashDirIter { &name.as_bytes()[..name.len() - b".trashinfo".len()], )) }; + Some( TrashInfo::from_files(entry.path(), deleted_path) .map_err(Error::from), diff --git a/src/errors.rs b/src/errors.rs index d007b49..6346f97 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,15 +1,38 @@ +use std::path::PathBuf; + +/// Result convenience type for the Error type +pub type Result = std::result::Result; + /// All errors that could happen #[derive(Debug, Error)] #[allow(missing_docs)] pub enum Error { #[error("IO error: {0}")] Io(#[from] std::io::Error), + #[error("Walkdir error: {0}")] WalkDir(#[from] walkdir::Error), - #[error("Bad .trashinfo file: {0}")] - BadTrashInfo(#[from] TrashInfoError), + + #[error("Mount error: {0}")] + Mount(#[from] libmount::mountinfo::ParseError), + + #[error("Strip prefix error: {0}")] + StripPrefix(#[from] std::path::StripPrefixError), + + #[error("Bad .trashinfo file at {0:?}: {1}")] + BadTrashInfo(PathBuf, TrashInfoError), + #[error("Date parsing error: {0}")] ParseDate(#[from] chrono::format::ParseError), + + #[error("No trash directory exists at: {0:?}")] + TrashDirDoesntExist(PathBuf), + + #[error("No files in the trash directory: {0:?}")] + NoFilesInThisDirectory(PathBuf), + + #[error("Put error: {0}")] + Put(#[from] crate::ops::put::PutError), } /// Errors related to .trashinfo files @@ -18,8 +41,10 @@ pub enum Error { pub enum TrashInfoError { #[error("Missing [TrashInfo] header")] MissingHeader, + #[error("Missing path attribute")] MissingPath, + #[error("Missing date attribute")] MissingDate, } diff --git a/src/info.rs b/src/info.rs index 1a46d21..a52ae26 100644 --- a/src/info.rs +++ b/src/info.rs @@ -64,6 +64,7 @@ impl TrashInfo { if i == 0 { if line != "[Trash Info]" { return Err(Error::BadTrashInfo( + info_path, TrashInfoError::MissingHeader, )); } else { @@ -94,14 +95,20 @@ impl TrashInfo { let path = match path { Some(path) => path, None => { - return Err(Error::BadTrashInfo(TrashInfoError::MissingPath)) + return Err(Error::BadTrashInfo( + info_path, + TrashInfoError::MissingPath, + )) } }; let deletion_date = match deletion_date { Some(deletion_date) => deletion_date, None => { - return Err(Error::BadTrashInfo(TrashInfoError::MissingDate)) + return Err(Error::BadTrashInfo( + info_path, + TrashInfoError::MissingDate, + )) } }; diff --git a/src/lib.rs b/src/lib.rs index 2d057b1..c696cc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,6 @@ extern crate lazy_static; #[macro_use] extern crate structopt; #[macro_use] -extern crate anyhow; -#[macro_use] extern crate thiserror; #[macro_use] @@ -26,7 +24,7 @@ use std::path::PathBuf; use xdg::BaseDirectories; pub use crate::dir::TrashDir; -pub use crate::errors::Error; +pub use crate::errors::{Error, Result}; pub use crate::info::TrashInfo; use crate::mounts::Mounts; diff --git a/src/main.rs b/src/main.rs index e1edb9c..c644648 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ #![deny(warnings)] -extern crate anyhow; - -use anyhow::Result; -use garbage::ops::{ - self, EmptyOptions, ListOptions, PutOptions, RestoreOptions, +use garbage::{ + ops::{self, EmptyOptions, ListOptions, PutOptions, RestoreOptions}, + Result, }; use structopt::StructOpt; @@ -46,7 +44,7 @@ fn main() { match run() { Ok(_) => (), Err(err) => { - eprintln!("Error: {:?}", err); + eprintln!("Error: {}", err); // for cause in err.chain() { // eprintln!("- {:?}", cause); // } diff --git a/src/mounts.rs b/src/mounts.rs index 091b6d8..cfeec61 100644 --- a/src/mounts.rs +++ b/src/mounts.rs @@ -4,9 +4,9 @@ use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; -use anyhow::Error; use libmount::mountinfo::Parser; +use crate::errors::Error; use crate::utils; #[derive(Debug)] diff --git a/src/ops/empty.rs b/src/ops/empty.rs index 9b9be03..aca485b 100644 --- a/src/ops/empty.rs +++ b/src/ops/empty.rs @@ -1,9 +1,10 @@ +use std::env; use std::fs; use std::path::PathBuf; -use anyhow::Result; use chrono::{Duration, Local}; +use crate::errors::Result; use crate::TrashDir; /// Options to pass to empty @@ -23,11 +24,16 @@ pub struct EmptyOptions { /// By default, this is your home directory's trash ($XDG_DATA_HOME/Trash) #[structopt(long = "trash-dir", parse(from_os_str))] trash_dir: Option, + + /// Delete all files in the trash (by default, only files in the current + /// directory are listed) + #[structopt(short = "a", long = "all")] + all: bool, } /// Actually delete files in the trash. pub fn empty(options: EmptyOptions) -> Result<()> { - let trash_dir = TrashDir::from_opt(options.trash_dir); + let trash_dir = TrashDir::from_opt(options.trash_dir.as_ref()); // cutoff date let cutoff = if let Some(days) = options.days { @@ -36,28 +42,32 @@ pub fn empty(options: EmptyOptions) -> Result<()> { Local::now() }; - for file in trash_dir.iter()? { - let file = file?; - + let current_dir = env::current_dir()?; + trash_dir + .iter()? + .collect::>>()? + .into_iter() // ignore files that were deleted after the cutoff (younger) - let ignore = file.deletion_date > cutoff; - - if !ignore { + .filter(|info| info.deletion_date <= cutoff) + .filter(|info| options.all || info.path.starts_with(¤t_dir)) + .map(|info| -> Result<_> { if options.dry { - println!("{:?}", file.path); + println!("deleting {:?}", info.path); } else { - fs::remove_file(file.info_path)?; + fs::remove_file(info.info_path)?; - if file.deleted_path.exists() { - if file.deleted_path.is_dir() { - fs::remove_dir_all(file.deleted_path)?; + if info.deleted_path.exists() { + if info.deleted_path.is_dir() { + fs::remove_dir_all(info.deleted_path)?; } else { - fs::remove_file(file.deleted_path)?; + fs::remove_file(info.deleted_path)?; } } } - } - } + + Ok(()) + }) + .collect::>()?; Ok(()) } diff --git a/src/ops/list.rs b/src/ops/list.rs index 40a4bf5..70627ec 100644 --- a/src/ops/list.rs +++ b/src/ops/list.rs @@ -1,10 +1,9 @@ -use std::path::PathBuf; use std::env; +use std::path::PathBuf; -use anyhow::Result; - +use crate::dir::TrashDir; +use crate::errors::Result; use crate::list; -use crate::TrashDir; /// Options to pass to list #[derive(StructOpt)] @@ -25,19 +24,16 @@ pub fn list(options: ListOptions) -> Result<()> { let trash_dir = TrashDir::from_opt(options.trash_dir.as_ref()); let current_dir = env::current_dir()?; + let mut files = trash_dir - .iter() - .unwrap() - .filter_map(|entry| match entry { - Ok(info) => { - if !options.all && !info.path.starts_with(¤t_dir) { - return None; - } - Some(info) - } - Err(err) => { - eprintln!("failed to get file info: {:?}", err); + .iter()? + .collect::>>()? + .into_iter() + .filter_map(|info| { + if !options.all && !info.path.starts_with(¤t_dir) { None + } else { + Some(info) } }) .collect::>(); diff --git a/src/ops/mod.rs b/src/ops/mod.rs index df2191f..b4a0e20 100644 --- a/src/ops/mod.rs +++ b/src/ops/mod.rs @@ -1,9 +1,9 @@ //! Operations that garbage can do. -mod empty; -mod list; -mod put; -mod restore; +pub(crate) mod empty; +pub(crate) mod list; +pub(crate) mod put; +pub(crate) mod restore; pub use self::empty::{empty, EmptyOptions}; pub use self::list::{list, ListOptions}; diff --git a/src/ops/put.rs b/src/ops/put.rs index 49032a7..844fd88 100644 --- a/src/ops/put.rs +++ b/src/ops/put.rs @@ -4,15 +4,15 @@ use std::io::{self, Write}; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; -use anyhow::Result; use chrono::Local; +use crate::errors::{Error, Result}; use crate::utils::{self}; use crate::{TrashDir, TrashInfo}; use crate::{HOME_MOUNT, MOUNTS}; #[derive(Debug, Error)] -pub enum Error { +pub enum PutError { // #[error("Refusing to remove directory {0} without '-r' option")] // MissingRecursiveOption(PathBuf), #[error("Refusing to remove '.' or '..', skipping...")] @@ -23,6 +23,9 @@ pub enum Error { #[error("Invalid filename.")] InvalidFilename, + + #[error("Couldn't find mount point.")] + CouldntFindMountPoint, } /// Options to pass to put @@ -65,13 +68,14 @@ pub fn put(options: PutOptions) -> Result<()> { for path in options.paths.iter() { // don't allow deleting '.' or '..' let current_dir = env::current_dir()?; - ensure!( - !(utils::into_absolute(&path)? == current_dir.as_path() - || (current_dir.parent().is_some() - && utils::into_absolute(&path)? - == current_dir.parent().unwrap())), - Error::CannotTrashDotDirs - ); + + if !(utils::into_absolute(&path)? == current_dir.as_path() + || (current_dir.parent().is_some() + && utils::into_absolute(&path)? + == current_dir.parent().unwrap())) + { + return Err(Error::Put(PutError::CannotTrashDotDirs)); + } // pick the best strategy for deleting this particular file let strategy = if let Some(ref trash_dir) = options.trash_dir { @@ -112,7 +116,7 @@ impl DeletionStrategy { let target = target.as_ref(); let target_mount = MOUNTS .get_mount_point(target) - .ok_or_else(|| anyhow!("couldn't get mount point"))?; + .ok_or_else(|| PutError::CouldntFindMountPoint)?; // first, are we on the home mount? if target_mount == *HOME_MOUNT { @@ -193,7 +197,7 @@ impl DeletionStrategy { } }; if !should_continue { - bail!(Error::CancelledByUser); + return Err(Error::Put(PutError::CancelledByUser)); } } @@ -204,9 +208,9 @@ impl DeletionStrategy { let elapsed_str = elapsed.to_string(); let target_file = match target.file_name() { Some(file) => file.to_os_string(), - None => bail!(Error::InvalidFilename), + None => return Err(Error::Put(PutError::InvalidFilename)), }; - let file_name = crate::concat_os_str!(elapsed_str, ".", target_file); + let file_name = concat_os_str!(elapsed_str, ".", target_file); // { // let buf = vec![0; elapsed_str.len() + 1 + target_file.len()]; // unimplemented!() @@ -220,7 +224,7 @@ impl DeletionStrategy { let trash_file_path = trash_dir.files_dir()?.join(&file_name); let trash_info_path = trash_dir .info_dir()? - .join(crate::concat_os_str!(file_name, ".trashinfo")); + .join(concat_os_str!(file_name, ".trashinfo")); let trash_info = TrashInfo { path: utils::into_absolute(target)?, diff --git a/src/ops/restore.rs b/src/ops/restore.rs index 4bb21f7..4103e91 100644 --- a/src/ops/restore.rs +++ b/src/ops/restore.rs @@ -3,8 +3,7 @@ use std::fs; use std::io; use std::path::PathBuf; -use anyhow::Result; - +use crate::errors::{Error, Result}; use crate::list; use crate::TrashDir; @@ -27,7 +26,7 @@ pub fn restore(options: RestoreOptions) -> Result<()> { let trash_dir = TrashDir::from_opt(options.trash_dir.as_ref()); if trash_dir.check_info_dir()?.is_none() { - bail!("There's no trash directory here."); + return Err(Error::TrashDirDoesntExist(trash_dir.path().to_path_buf())); } // get list of files sorted by deletion date @@ -55,7 +54,9 @@ pub fn restore(options: RestoreOptions) -> Result<()> { }; if files.len() == 0 { - bail!("No files in this trash directory."); + return Err(Error::NoFilesInThisDirectory( + trash_dir.path().to_path_buf(), + )); } list::print_files_list(files.iter(), true); diff --git a/src/utils.rs b/src/utils.rs index 560d612..57ab530 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,10 +4,11 @@ use std::fs; use std::os::unix::ffi::OsStrExt; use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR}; -use anyhow::Result; use percent_encoding::AsciiSet; use walkdir::WalkDir; +use crate::errors::Result; + const MASK: &AsciiSet = percent_encoding::CONTROLS; pub fn into_absolute(path: impl AsRef) -> Result {