diff --git a/czkawka_cli/src/commands.rs b/czkawka_cli/src/commands.rs index b2b2171..ee185fd 100644 --- a/czkawka_cli/src/commands.rs +++ b/czkawka_cli/src/commands.rs @@ -29,6 +29,10 @@ pub enum Commands { EmptyFolders { #[structopt(flatten)] directories: Directories, + #[structopt(flatten)] + excluded_directories: ExcludedDirectories, + #[structopt(flatten)] + excluded_items: ExcludedItems, #[structopt(short = "D", long, help = "Delete found folders")] delete_folders: bool, #[structopt(flatten)] @@ -46,6 +50,8 @@ pub enum Commands { allowed_extensions: AllowedExtensions, #[structopt(short, long, default_value = "50", help = "Number of files to be shown")] number_of_files: usize, + #[structopt(short = "D", long, help = "Delete found files")] + delete_files: bool, #[structopt(flatten)] file_to_save: FileToSave, #[structopt(flatten)] diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index 725dc39..d99cb1d 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -6,7 +6,7 @@ use commands::Commands; use czkawka_core::common_traits::*; use czkawka_core::{ - big_file::BigFile, + big_file::{self, BigFile}, duplicate::DuplicateFinder, empty_files::{self, EmptyFiles}, empty_folder::EmptyFolder, @@ -64,10 +64,18 @@ fn main() { df.print_results(); df.get_text_messages().print_messages(); } - Commands::EmptyFolders { directories, delete_folders, file_to_save } => { + Commands::EmptyFolders { + directories, + delete_folders, + file_to_save, + excluded_directories, + excluded_items, + } => { let mut ef = EmptyFolder::new(); ef.set_included_directory(path_list_to_str(directories.directories)); + ef.set_excluded_directory(path_list_to_str(excluded_directories.excluded_directories)); + ef.set_excluded_items(path_list_to_str(excluded_items.excluded_items)); ef.set_delete_folder(delete_folders); ef.find_empty_folders(None); @@ -91,6 +99,7 @@ fn main() { number_of_files, file_to_save, not_recursive, + delete_files, } => { let mut bf = BigFile::new(); @@ -100,6 +109,9 @@ fn main() { bf.set_allowed_extensions(allowed_extensions.allowed_extensions.join(",")); bf.set_number_of_files_to_check(number_of_files); bf.set_recursive_search(!not_recursive.not_recursive); + if delete_files { + bf.set_delete_method(big_file::DeleteMethod::Delete); + } bf.find_big_files(None); diff --git a/czkawka_core/src/big_file.rs b/czkawka_core/src/big_file.rs index 174ce60..b1ba28f 100644 --- a/czkawka_core/src/big_file.rs +++ b/czkawka_core/src/big_file.rs @@ -21,6 +21,11 @@ pub struct FileEntry { pub modified_date: u64, } +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum DeleteMethod { + None, + Delete, +} /// Info struck with helpful information's about results #[derive(Default)] pub struct Info { @@ -48,6 +53,7 @@ pub struct BigFile { allowed_extensions: Extensions, recursive_search: bool, number_of_files_to_check: usize, + delete_method: DeleteMethod, stopped_search: bool, } @@ -62,6 +68,7 @@ impl BigFile { allowed_extensions: Extensions::new(), recursive_search: true, number_of_files_to_check: 50, + delete_method: DeleteMethod::None, stopped_search: false, } } @@ -72,6 +79,7 @@ impl BigFile { self.stopped_search = true; return; } + self.delete_files(); self.debug_print(); } pub fn get_stopped_search(&self) -> bool { @@ -90,6 +98,10 @@ impl BigFile { &self.information } + pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { + self.delete_method = delete_method; + } + pub fn set_recursive_search(&mut self, recursive_search: bool) { self.recursive_search = recursive_search; } @@ -248,6 +260,28 @@ impl BigFile { pub fn set_excluded_directory(&mut self, excluded_directory: String) { self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); } + + /// Function to delete files, from filed Vector + fn delete_files(&mut self) { + let start_time: SystemTime = SystemTime::now(); + + match self.delete_method { + DeleteMethod::Delete => { + for vec_file_entry in self.big_files.values() { + for file_entry in vec_file_entry { + if fs::remove_file(file_entry.path.clone()).is_err() { + self.text_messages.warnings.push(file_entry.path.display().to_string()); + } + } + } + } + DeleteMethod::None => { + //Just do nothing + } + } + + Common::print_time(start_time, SystemTime::now(), "delete_files".to_string()); + } } impl Default for BigFile { diff --git a/czkawka_core/src/empty_folder.rs b/czkawka_core/src/empty_folder.rs index 61c7d5f..b2d0f62 100644 --- a/czkawka_core/src/empty_folder.rs +++ b/czkawka_core/src/empty_folder.rs @@ -1,5 +1,6 @@ use crate::common::Common; use crate::common_directory::Directories; +use crate::common_items::ExcludedItems; use crate::common_messages::Messages; use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; use crossbeam_channel::Receiver; @@ -31,6 +32,7 @@ pub struct EmptyFolder { information: Info, delete_folders: bool, text_messages: Messages, + excluded_items: ExcludedItems, empty_folder_list: BTreeMap, // Path, FolderEntry directories: Directories, stopped_search: bool, @@ -56,6 +58,7 @@ impl EmptyFolder { information: Default::default(), delete_folders: false, text_messages: Messages::new(), + excluded_items: Default::default(), empty_folder_list: Default::default(), directories: Directories::new(), stopped_search: false, @@ -77,6 +80,13 @@ impl EmptyFolder { &self.information } + pub fn set_excluded_items(&mut self, excluded_items: String) { + self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); + } + + pub fn set_excluded_directory(&mut self, excluded_directory: String) { + self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); + } /// Public function used by CLI to search for empty folders pub fn find_empty_folders(&mut self, rx: Option<&Receiver<()>>) { self.directories.optimize_directories(true, &mut self.text_messages); @@ -151,18 +161,28 @@ impl EmptyFolder { } }; - for entry in read_dir { + 'dir: for entry in read_dir { let entry_data = match entry { Ok(t) => t, - Err(_) => continue, //Permissions denied + Err(_) => { + set_as_not_empty_folder(&mut folders_checked, ¤t_folder); + continue 'dir; + } //Permissions denied }; let metadata: Metadata = match entry_data.metadata() { Ok(t) => t, - Err(_) => continue, //Permissions denied + Err(_) => { + set_as_not_empty_folder(&mut folders_checked, ¤t_folder); + continue 'dir; + } //Permissions denied }; // If child is dir, still folder may be considered as empty if all children are only directories. if metadata.is_dir() { let next_folder = current_folder.join(entry_data.file_name()); + if self.excluded_items.is_excluded(&next_folder) || self.directories.is_excluded(&next_folder) { + set_as_not_empty_folder(&mut folders_checked, ¤t_folder); + continue 'dir; + } folders_to_check.push(next_folder.clone()); folders_checked.insert( next_folder.clone(), @@ -179,25 +199,15 @@ impl EmptyFolder { }, Err(_) => { self.text_messages.warnings.push(format!("Failed to read modification date of folder {}", current_folder.display())); - continue; + // Can't read data, so assuming that is not empty + set_as_not_empty_folder(&mut folders_checked, ¤t_folder); + continue 'dir; } }, }, ); } else { - // Not folder so it may be a file or symbolic link so it isn't empty - folders_checked.get_mut(¤t_folder).unwrap().is_empty = FolderEmptiness::No; - let mut d = folders_checked.get_mut(¤t_folder).unwrap(); - // Loop to recursively set as non empty this and all his parent folders - loop { - d.is_empty = FolderEmptiness::No; - if d.parent_path != None { - let cf = d.parent_path.clone().unwrap(); - d = folders_checked.get_mut(&cf).unwrap(); - } else { - break; - } - } + set_as_not_empty_folder(&mut folders_checked, ¤t_folder) } } } @@ -233,6 +243,23 @@ impl EmptyFolder { self.directories.set_included_directory(included_directory, &mut self.text_messages); } } + +fn set_as_not_empty_folder(folders_checked: &mut BTreeMap, current_folder: &PathBuf) { + // Not folder so it may be a file or symbolic link so it isn't empty + folders_checked.get_mut(current_folder).unwrap().is_empty = FolderEmptiness::No; + let mut d = folders_checked.get_mut(current_folder).unwrap(); + // Loop to recursively set as non empty this and all his parent folders + loop { + d.is_empty = FolderEmptiness::No; + if d.parent_path != None { + let cf = d.parent_path.clone().unwrap(); + d = folders_checked.get_mut(&cf).unwrap(); + } else { + break; + } + } +} + impl Default for EmptyFolder { fn default() -> Self { Self::new()