diff --git a/src/interactive/app/handlers.rs b/src/interactive/app/handlers.rs index 261866c2..7733075c 100644 --- a/src/interactive/app/handlers.rs +++ b/src/interactive/app/handlers.rs @@ -6,8 +6,8 @@ use crate::interactive::{ }; use dua::traverse::TreeIndex; use itertools::Itertools; -use petgraph::visit::Bfs; -use petgraph::Direction; +use petgraph::{visit::Bfs, Direction}; +use std::{fs, io, path::PathBuf}; use termion::event::Key; use tui::backend::Backend; use tui_react::Terminal; @@ -156,6 +156,8 @@ impl TerminalApp { pub fn delete_entry(&mut self, index: TreeIndex) -> Result<(), usize> { if let Some(_entry) = self.traversal.tree.node_weight(index) { + let path_to_delete = path_of(&self.traversal.tree, index); + delete_directory_recursively(path_to_delete)?; let parent_idx = self .traversal .tree @@ -233,3 +235,59 @@ impl TerminalApp { } } } + +fn into_error_count(res: Result<(), io::Error>) -> usize { + match res.map_err(io_err_to_usize) { + Ok(_) => 0, + Err(c) => c, + } +} + +fn io_err_to_usize(err: io::Error) -> usize { + if err.kind() == io::ErrorKind::NotFound { + 0 + } else { + 1 + } +} + +fn delete_directory_recursively(path: PathBuf) -> Result<(), usize> { + let mut files_or_dirs = vec![path]; + let mut dirs = Vec::new(); + let mut num_errors = 0; + while let Some(path) = files_or_dirs.pop() { + match fs::read_dir(&path) { + Ok(iterator) => { + dirs.push(path); + for entry in iterator { + match entry.map_err(io_err_to_usize) { + Ok(entry) => files_or_dirs.push(entry.path()), + Err(c) => num_errors += c, + } + } + } + Err(ref e) if e.kind() == io::ErrorKind::NotFound => { + continue; + } + Err(ref e) if e.kind() == io::ErrorKind::Other => { + // assume file, save IOps + num_errors += into_error_count(fs::remove_file(path)); + continue; + } + Err(_) => { + num_errors += 1; + continue; + } + }; + } + + for dir in dirs.into_iter().rev() { + num_errors += into_error_count(fs::remove_dir(dir)); + } + + if num_errors == 0 { + Ok(()) + } else { + Err(num_errors) + } +} diff --git a/src/interactive/app_test/journeys_with_writes.rs b/src/interactive/app_test/journeys_with_writes.rs index e8cd6b5f..cbd51cd2 100644 --- a/src/interactive/app_test/journeys_with_writes.rs +++ b/src/interactive/app_test/journeys_with_writes.rs @@ -29,13 +29,8 @@ fn basic_user_journey_with_deletion() -> Result<(), Error> { // When selecting the marker window and pressing the combination to delete entries app.process_events( &mut terminal, - vec![Ok(Key::Char('\t')), Ok(Key::Ctrl('R'))].into_iter(), + vec![Ok(Key::Char('\t')), Ok(Key::Ctrl('r'))].into_iter(), )?; - assert_eq!( - fixture.as_ref().is_dir(), - false, - "the directory should have been deleted" - ); assert_eq!( app.window.mark_pane.is_none(), true, @@ -46,5 +41,10 @@ fn basic_user_journey_with_deletion() -> Result<(), Error> { app.state.root, app.traversal.root_index, "the only root left is the top-level" ); + assert_eq!( + fixture.as_ref().is_dir(), + false, + "the directory should have been deleted", + ); Ok(()) } diff --git a/src/interactive/app_test/utils.rs b/src/interactive/app_test/utils.rs index 144a4cc1..74881433 100644 --- a/src/interactive/app_test/utils.rs +++ b/src/interactive/app_test/utils.rs @@ -65,7 +65,7 @@ pub struct WritableFixture { impl Drop for WritableFixture { fn drop(&mut self) { - delete_recursive(&self.root).unwrap(); + delete_recursive(&self.root).ok(); } } @@ -74,7 +74,7 @@ fn delete_recursive(path: impl AsRef) -> Result<(), Error> { let mut dirs: Vec<_> = Vec::new(); for entry in WalkDir::new(&path).num_threads(1).into_iter() { - let entry: DirEntry = entry.unwrap(); + let entry: DirEntry = entry?; let p = entry.path(); match p.is_dir() { true => dirs.push(p),