---
src/dir.rs | 37 ++++++++++++++++++++++------------
src/errors.rs | 37 ++++++++++++++++++++++++++++++++--
src/ops/empty.rs | 30 +++++++++++++++++++++++-----
src/ops/list.rs | 5 +++--
src/ops/put.rs | 50 ++++++++++++++++++++++++++++++++++++----------
src/ops/restore.rs | 5 +++--
6 files changed, 129 insertions(+), 35 deletions(-)
diff --git a/src/dir.rs b/src/dir.rs
index b4efc1f..d06cb07 100644
--- a/src/dir.rs
+++ b/src/dir.rs
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use walkdir::{DirEntry, WalkDir};
-use crate::Error;
+use crate::errors::{Context, Error};
use crate::TrashInfo;
use crate::XDG;
@@ -44,7 +44,9 @@ impl TrashDir {
pub fn create(&self) -> Result<(), Error> {
let path = &self.0;
if !path.exists() {
- fs::create_dir_all(&path)?;
+ fs::create_dir_all(&path).with_context(|| {
+ format!("Failed to create {}", path.display())
+ })?;
}
Ok(())
}
@@ -58,7 +60,9 @@ impl TrashDir {
pub fn files_dir(&self) -> Result<PathBuf, Error> {
let target = self.0.join("files");
if !target.exists() {
- fs::create_dir_all(&target)?;
+ fs::create_dir_all(&target).with_context(|| {
+ format!("Failed to create {}", target.display())
+ })?;
}
Ok(target)
}
@@ -67,7 +71,9 @@ impl TrashDir {
pub fn info_dir(&self) -> Result<PathBuf, Error> {
let target = self.0.join("info");
if !target.exists() {
- fs::create_dir_all(&target)?;
+ fs::create_dir_all(&target).with_context(|| {
+ format!("Failed to create {}", target.display())
+ })?;
}
Ok(target)
}
@@ -84,13 +90,15 @@ impl TrashDir {
/// Iterate over trash infos within this trash directory
pub fn iter(&self) -> Result<TrashDirIter, Error> {
- let iter = WalkDir::new(&self.info_dir()?)
- .contents_first(true)
- .into_iter()
- .filter_entry(|entry| match entry.path().extension() {
- Some(x) => x == "trashinfo",
- _ => false,
- });
+ let iter = WalkDir::new(
+ &self.info_dir().context("Failed to open trash directory")?,
+ )
+ .contents_first(true)
+ .into_iter()
+ .filter_entry(|entry| match entry.path().extension() {
+ Some(x) => x == "trashinfo",
+ _ => false,
+ });
Ok(TrashDirIter(self.0.clone(), Box::new(iter)))
}
}
@@ -133,8 +141,11 @@ impl Iterator for TrashDirIter {
};
Some(
- TrashInfo::from_files(entry.path(), deleted_path)
- .map_err(Error::from),
+ TrashInfo::from_files(entry.path(), &deleted_path)
+ .map_err(Error::from)
+ .with_context(|| {
+ format!("Failed to load trashinfo file {} (for deleted file {})", entry.path().display(), deleted_path.display())
+ }),
)
}
}
diff --git a/src/errors.rs b/src/errors.rs
index 9db81ae..93e5525 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -6,6 +6,30 @@ use std::{
/// Result convenience type for the Error type
pub type Result<T, E = Error> = std::result::Result<T, E>;
+/// An extension trait to add the `with_context` method.
+pub trait Context: Sized {
+ type Output;
+
+ /// Adds an entry of context to the error.
+ fn context(self, msg: &str) -> Self::Output {
+ self.with_context(|| msg.to_string())
+ }
+
+ /// Adds an entry of context computed by a function to the error.
+ fn with_context<F: FnOnce() -> String>(self, msg: F) -> Self::Output;
+}
+
+impl<T, E> Context for Result<T, E>
+where
+ Error: From<E>,
+{
+ type Output = Result<T, Error>;
+
+ fn with_context<F: FnOnce() -> String>(self, msg: F) -> Self::Output {
+ self.map_err(|e| Error::from(e).with_context(msg))
+ }
+}
+
/// Errors plus their surrounding context.
#[derive(Debug, Error)]
pub struct Error {
@@ -19,9 +43,9 @@ pub struct Error {
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> FmtResult {
- writeln!(fmt, "{}", self.kind)?;
+ write!(fmt, "{}", self.kind)?;
for entry in self.context.iter().rev() {
- writeln!(fmt, "caused by {}", entry)?;
+ write!(fmt, "\ncaused by: {}", entry)?;
}
Ok(())
}
@@ -36,6 +60,15 @@ impl<T: Into<ErrorKind>> From<T> for Error {
}
}
+impl Context for Error {
+ type Output = Self;
+
+ fn with_context<F: FnOnce() -> String>(mut self, msg: F) -> Self {
+ self.context.push(msg());
+ self
+ }
+}
+
/// All errors that could happen
#[derive(Debug, Error)]
#[allow(missing_docs)]
diff --git a/src/ops/empty.rs b/src/ops/empty.rs
index aca485b..cc95fd7 100644
--- a/src/ops/empty.rs
+++ b/src/ops/empty.rs
@@ -4,7 +4,7 @@ use std::path::PathBuf;
use chrono::{Duration, Local};
-use crate::errors::Result;
+use crate::errors::{Context, Result};
use crate::TrashDir;
/// Options to pass to empty
@@ -42,7 +42,8 @@ pub fn empty(options: EmptyOptions) -> Result<()> {
Local::now()
};
- let current_dir = env::current_dir()?;
+ let current_dir =
+ env::current_dir().context("Failed to get current directory")?;
trash_dir
.iter()?
.collect::<Result<Vec<_>>>()?
@@ -54,13 +55,32 @@ pub fn empty(options: EmptyOptions) -> Result<()> {
if options.dry {
println!("deleting {:?}", info.path);
} else {
- fs::remove_file(info.info_path)?;
+ fs::remove_file(&info.info_path).with_context(|| {
+ format!(
+ "Failed to delete info file {}",
+ info.info_path.display()
+ )
+ })?;
if info.deleted_path.exists() {
if info.deleted_path.is_dir() {
- fs::remove_dir_all(info.deleted_path)?;
+ fs::remove_dir_all(&info.deleted_path).with_context(
+ || {
+ format!(
+ "Failed to delete trashed directory {}",
+ info.deleted_path.display()
+ )
+ },
+ )?;
} else {
- fs::remove_file(info.deleted_path)?;
+ fs::remove_file(&info.deleted_path).with_context(
+ || {
+ format!(
+ "Failed to delete trashed file {}",
+ info.deleted_path.display()
+ )
+ },
+ )?;
}
}
}
diff --git a/src/ops/list.rs b/src/ops/list.rs
index 70627ec..a501fff 100644
--- a/src/ops/list.rs
+++ b/src/ops/list.rs
@@ -2,7 +2,7 @@ use std::env;
use std::path::PathBuf;
use crate::dir::TrashDir;
-use crate::errors::Result;
+use crate::errors::{Context, Result};
use crate::list;
/// Options to pass to list
@@ -23,7 +23,8 @@ pub struct ListOptions {
pub fn list(options: ListOptions) -> Result<()> {
let trash_dir = TrashDir::from_opt(options.trash_dir.as_ref());
- let current_dir = env::current_dir()?;
+ let current_dir =
+ env::current_dir().context("Failed to get current directory")?;
let mut files = trash_dir
.iter()?
diff --git a/src/ops/put.rs b/src/ops/put.rs
index 50bc2e6..b18f395 100644
--- a/src/ops/put.rs
+++ b/src/ops/put.rs
@@ -6,8 +6,8 @@ use std::path::{Path, PathBuf};
use chrono::Local;
-use crate::errors::{ErrorKind, Result};
-use crate::utils::{self};
+use crate::errors::{Context, ErrorKind, Result};
+use crate::utils;
use crate::{TrashDir, TrashInfo};
use crate::{HOME_MOUNT, MOUNTS};
@@ -66,10 +66,13 @@ pub struct PutOptions {
/// Throw some files into the trash.
pub fn put(options: PutOptions) -> Result<()> {
for path in options.paths.iter() {
- let abs_path = utils::into_absolute(&path)?;
+ let abs_path = utils::into_absolute(&path).with_context(|| {
+ format!("Couldn't convert {} to an absolute path", path.display())
+ })?;
// don't allow deleting '.' or '..'
- let current_dir = env::current_dir()?;
+ let current_dir =
+ env::current_dir().context("Couldn't get current directory")?;
let parent = current_dir.parent();
info!("Checking if {:?} is . or ..", abs_path);
trace!("curr = {:?}", current_dir);
@@ -134,7 +137,9 @@ impl DeletionStrategy {
.join(".Trash")
.join(utils::get_uid().to_string());
let trash_dir = TrashDir::from(topdir_trash_dir);
- trash_dir.create()?;
+ trash_dir
+ .create()
+ .context("Failed to create trash directory")?;
return Ok(DeletionStrategy::MoveTo(trash_dir));
}
@@ -143,7 +148,9 @@ impl DeletionStrategy {
let topdir_trash_uid =
target_mount.join(format!(".Trash-{}", utils::get_uid()));
let trash_dir = TrashDir::from(topdir_trash_uid);
- trash_dir.create()?;
+ trash_dir
+ .create()
+ .context("Failed to create trash directory")?;
return Ok(DeletionStrategy::MoveTo(trash_dir));
}
@@ -187,12 +194,14 @@ impl DeletionStrategy {
eprint!("Remove file '{}'? [Y/n] ", target.to_str().unwrap());
}
// TODO: actually handle prompting instead of manually flushing
- io::stderr().flush()?;
+ io::stderr().flush().context("Failed to flush stderr")?;
let should_continue = loop {
let stdin = io::stdin();
let mut s = String::new();
- stdin.read_line(&mut s).unwrap();
+ stdin
+ .read_line(&mut s)
+ .context("Failed to read from stdin")?;
match s.trim().to_lowercase().as_str() {
"yes" | "y" => break true,
"no" | "n" => break false,
@@ -246,10 +255,29 @@ impl DeletionStrategy {
// copy the file over
if requires_copy {
- utils::recursive_copy(&target, &trash_file_path)?;
- fs::remove_dir_all(&target)?;
+ utils::recursive_copy(&target, &trash_file_path).with_context(
+ || {
+ format!(
+ "Failed to copy file {} to trash ({})",
+ target.display(),
+ trash_file_path.display()
+ )
+ },
+ )?;
+ fs::remove_dir_all(&target).with_context(|| {
+ format!(
+ "Failed to delete original file {} after copying",
+ target.display()
+ )
+ })?;
} else {
- fs::rename(&target, &trash_file_path)?;
+ fs::rename(&target, &trash_file_path).with_context(|| {
+ format!(
+ "Failed to move file {} to trash ({})",
+ target.display(),
+ trash_file_path.display()
+ )
+ })?;
}
Ok(())
diff --git a/src/ops/restore.rs b/src/ops/restore.rs
index c0d363e..4a179fe 100644
--- a/src/ops/restore.rs
+++ b/src/ops/restore.rs
@@ -3,7 +3,7 @@ use std::fs;
use std::io;
use std::path::PathBuf;
-use crate::errors::{ErrorKind, Result};
+use crate::errors::{Context, ErrorKind, Result};
use crate::list;
use crate::TrashDir;
@@ -34,7 +34,8 @@ pub fn restore(options: RestoreOptions) -> Result<()> {
// get list of files sorted by deletion date
// TODO: possible to get this to be streaming?
- let current_dir = env::current_dir()?;
+ let current_dir =
+ env::current_dir().context("Couldn't get current directory")?;
let files = {
let mut files = trash_dir
.iter()?
--
2.28.0