From 8161a1c59bc9fb65813b666e4d47ea7f988b0601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Sat, 26 Sep 2020 16:52:13 +0200 Subject: [PATCH] Big code refactoring --- czkawka_cli/src/main.rs | 17 +- czkawka_core/src/big_file.rs | 479 +++++--------------- czkawka_core/src/common.rs | 48 -- czkawka_core/src/common_directory.rs | 252 +++++++++++ czkawka_core/src/common_extensions.rs | 54 +++ czkawka_core/src/common_items.rs | 52 +++ czkawka_core/src/common_messages.rs | 47 ++ czkawka_core/src/common_traits.rs | 9 + czkawka_core/src/duplicate.rs | 613 +++++++------------------- czkawka_core/src/empty_folder.rs | 226 +++------- czkawka_core/src/lib.rs | 5 + czkawka_gui/src/main.rs | 8 +- czkawka_gui_orbtk/src/main.rs | 4 +- 13 files changed, 764 insertions(+), 1050 deletions(-) create mode 100644 czkawka_core/src/common_directory.rs create mode 100644 czkawka_core/src/common_extensions.rs create mode 100644 czkawka_core/src/common_items.rs create mode 100644 czkawka_core/src/common_messages.rs create mode 100644 czkawka_core/src/common_traits.rs diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index 2a37dca..68cf9f6 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -1,3 +1,4 @@ +use czkawka_core::common_traits::*; use czkawka_core::*; use std::{env, process}; @@ -64,13 +65,13 @@ fn main() { let mut df = duplicate::DuplicateFinder::new(); if ArgumentsPair::has_command(&arguments, "-i") { - df.set_include_directory(ArgumentsPair::get_argument(&arguments, "-i", false)); + df.set_included_directory(ArgumentsPair::get_argument(&arguments, "-i", false)); } else { println!("FATAL ERROR: Parameter -i with set of included files is required."); process::exit(1); } if ArgumentsPair::has_command(&arguments, "-e") { - df.set_exclude_directory(ArgumentsPair::get_argument(&arguments, "-e", false)); + df.set_excluded_directory(ArgumentsPair::get_argument(&arguments, "-e", false)); } if ArgumentsPair::has_command(&arguments, "-s") { @@ -148,7 +149,7 @@ fn main() { } #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - df.print_duplicated_entries(); + df.print_results(); df.get_text_messages().print_messages(); } @@ -159,7 +160,7 @@ fn main() { let mut ef = empty_folder::EmptyFolder::new(); if ArgumentsPair::has_command(&arguments, "-i") { - ef.set_include_directory(ArgumentsPair::get_argument(&arguments, "-i", false)); + ef.set_included_directory(ArgumentsPair::get_argument(&arguments, "-i", false)); } else { println!("FATAL ERROR: Parameter -i with set of included files is required."); process::exit(1); @@ -180,19 +181,19 @@ fn main() { } #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - ef.print_empty_folders(); + ef.print_results(); } "--b" => { let mut bf = big_file::BigFile::new(); if ArgumentsPair::has_command(&arguments, "-i") { - bf.set_include_directory(ArgumentsPair::get_argument(&arguments, "-i", false)); + bf.set_included_directory(ArgumentsPair::get_argument(&arguments, "-i", false)); } else { println!("FATAL ERROR: Parameter -i with set of included files is required."); process::exit(1); } if ArgumentsPair::has_command(&arguments, "-e") { - bf.set_exclude_directory(ArgumentsPair::get_argument(&arguments, "-e", false)); + bf.set_excluded_directory(ArgumentsPair::get_argument(&arguments, "-e", false)); } if ArgumentsPair::has_command(&arguments, "-s") { @@ -235,7 +236,7 @@ fn main() { } #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - bf.print_duplicated_entries(); + bf.print_results(); bf.get_text_messages().print_messages(); } diff --git a/czkawka_core/src/big_file.rs b/czkawka_core/src/big_file.rs index 4cc0428..fe9212b 100644 --- a/czkawka_core/src/big_file.rs +++ b/czkawka_core/src/big_file.rs @@ -1,10 +1,14 @@ -use crate::common::{Common, Messages}; +use crate::common::Common; +use crate::common_directory::Directories; +use crate::common_extensions::Extensions; +use crate::common_items::ExcludedItems; +use crate::common_messages::Messages; +use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; use humansize::{file_size_opts as options, FileSize}; use std::collections::BTreeMap; use std::fs; use std::fs::{File, Metadata}; use std::io::Write; -use std::path::Path; use std::time::SystemTime; #[derive(Clone)] @@ -48,10 +52,9 @@ pub struct BigFile { text_messages: Messages, information: Info, big_files: BTreeMap>, - excluded_items: Vec, - excluded_directories: Vec, - included_directories: Vec, - allowed_extensions: Vec, + excluded_items: ExcludedItems, + directories: Directories, + allowed_extensions: Extensions, recursive_search: bool, number_of_files_to_check: usize, } @@ -62,10 +65,9 @@ impl BigFile { text_messages: Default::default(), information: Info::new(), big_files: Default::default(), - excluded_items: vec![], - excluded_directories: vec![], - included_directories: vec![], - allowed_extensions: vec![], + excluded_items: ExcludedItems::new(), + directories: Directories::new(), + allowed_extensions: Extensions::new(), recursive_search: true, number_of_files_to_check: 50, } @@ -88,86 +90,9 @@ impl BigFile { self.recursive_search = recursive_search; } - /// Saving results to provided file - pub fn save_results_to_file(&mut self, file_name: &str) -> bool { - let start_time: SystemTime = SystemTime::now(); - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let mut file = match File::create(&file_name) { - Ok(t) => t, - Err(_) => { - self.text_messages.errors.push("Failed to create file ".to_string() + file_name.as_str()); - return false; - } - }; - - match file.write_all( - format!( - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}\n", - self.included_directories, self.excluded_directories, self.excluded_items - ) - .as_bytes(), - ) { - Ok(_) => (), - Err(_) => { - self.text_messages.errors.push("Failed to save results to file ".to_string() + file_name.as_str()); - return false; - } - } - - if self.information.number_of_real_files != 0 { - file.write_all(format!("{} the biggest files.\n\n", self.information.number_of_real_files).as_bytes()).unwrap(); - - for (size, files) in self.big_files.iter().rev() { - for file_entry in files { - file.write_all(format!("{} ({}) - {}\n", size.file_size(options::BINARY).unwrap(), size, file_entry.path.clone()).as_bytes()).unwrap(); - } - } - } else { - file.write_all(b"Not found any empty folders.").unwrap(); - } - Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); - true - } - /// List of allowed extensions, only files with this extensions will be checking if are duplicates - pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String) { - let start_time: SystemTime = SystemTime::now(); - if allowed_extensions.is_empty() { - return; - } - allowed_extensions = allowed_extensions.replace("IMAGE", "jpg,kra,gif,png,bmp,tiff,webp,hdr,svg"); - allowed_extensions = allowed_extensions.replace("VIDEO", "mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp"); - allowed_extensions = allowed_extensions.replace("MUSIC", "mp3,flac,ogg,tta,wma,webm"); - allowed_extensions = allowed_extensions.replace("TEXT", "txt,doc,docx,odt,rtf"); - - let extensions: Vec = allowed_extensions.split(',').map(String::from).collect(); - for mut extension in extensions { - if extension == "" || extension.replace('.', "").trim() == "" { - continue; - } - - if extension.starts_with('.') { - extension = extension[1..].to_string(); - } - - if extension[1..].contains('.') { - self.text_messages.warnings.push(".".to_string() + extension.as_str() + " is not valid extension(valid extension doesn't have dot inside)"); - continue; - } - - if !self.allowed_extensions.contains(&extension.trim().to_string()) { - self.allowed_extensions.push(extension.trim().to_string()); - } - } - - if self.allowed_extensions.is_empty() { - self.text_messages.messages.push("No valid extensions were provided, so allowing all extensions by default.".to_string()); - } - Common::print_time(start_time, SystemTime::now(), "set_allowed_extensions".to_string()); + pub fn set_allowed_extensions(&mut self, allowed_extensions: String) { + self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages); } fn look_for_big_files(&mut self) { @@ -175,7 +100,7 @@ impl BigFile { let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector // Add root folders for finding - for id in &self.included_directories { + for id in &self.directories.included_directories { folders_to_check.push(id.to_string()); } self.information.number_of_checked_folders += folders_to_check.len(); @@ -221,7 +146,7 @@ impl BigFile { let mut is_excluded_dir = false; next_folder = "".to_owned() + ¤t_folder + &entry_data.file_name().into_string().unwrap() + "/"; - for ed in &self.excluded_directories { + for ed in &self.directories.excluded_directories { if next_folder == *ed { is_excluded_dir = true; break; @@ -229,7 +154,7 @@ impl BigFile { } if !is_excluded_dir { let mut found_expression: bool = false; - for expression in &self.excluded_items { + for expression in &self.excluded_items.items { if Common::regex_check(expression, &next_folder) { found_expression = true; break; @@ -245,9 +170,9 @@ impl BigFile { let file_name_lowercase: String = entry_data.file_name().into_string().unwrap().to_lowercase(); // Checking allowed extensions - if !self.allowed_extensions.is_empty() { + if !self.allowed_extensions.file_extensions.is_empty() { have_valid_extension = false; - for i in &self.allowed_extensions { + for i in &self.allowed_extensions.file_extensions { if file_name_lowercase.ends_with((".".to_string() + i.to_lowercase().as_str()).as_str()) { have_valid_extension = true; break; @@ -263,7 +188,7 @@ impl BigFile { // Checking expressions let mut found_expression: bool = false; - for expression in &self.excluded_items { + for expression in &self.excluded_items.items { if Common::regex_check(expression, ¤t_file_name) { found_expression = true; break; @@ -307,7 +232,6 @@ impl BigFile { } } - // let mut new_map: BTreeMap> = Default::default(); for (size, vector) in self.big_files.iter().rev() { @@ -331,6 +255,39 @@ impl BigFile { Common::print_time(start_time, SystemTime::now(), "look_for_big_files".to_string()); } + pub fn set_number_of_files_to_check(&mut self, number_of_files_to_check: usize) { + self.number_of_files_to_check = number_of_files_to_check; + } + + /// Setting excluded items which needs to contains * wildcrard + /// Are a lot of slower than absolute path, so it should be used to heavy + pub fn set_excluded_items(&mut self, excluded_items: String) { + self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); + } + + /// Remove unused entries when included or excluded overlaps with each other or are duplicated etc. + fn optimize_directories(&mut self) { + self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); + } + + /// Setting include directories, at least one must be provided + pub fn set_included_directory(&mut self, included_directory: String) { + self.directories.set_included_directory(included_directory, &mut self.text_messages); + } + + /// Setting absolute path to exclude + pub fn set_excluded_directory(&mut self, excluded_directory: String) { + self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); + } +} + +impl Default for BigFile { + fn default() -> Self { + Self::new() + } +} + +impl DebugPrint for BigFile { #[allow(dead_code)] #[allow(unreachable_code)] /// Debugging printing - only available on debug build @@ -352,22 +309,63 @@ impl BigFile { println!("### Other"); println!("Big files size {} in {} groups", self.information.number_of_real_files, self.big_files.len()); - println!("Allowed extensions - {:?}", self.allowed_extensions); - println!("Excluded items - {:?}", self.excluded_items); - println!("Included directories - {:?}", self.included_directories); - println!("Excluded directories - {:?}", self.excluded_directories); + println!("Allowed extensions - {:?}", self.allowed_extensions.file_extensions); + println!("Excluded items - {:?}", self.excluded_items.items); + println!("Included directories - {:?}", self.directories.included_directories); + println!("Excluded directories - {:?}", self.directories.excluded_directories); println!("Recursive search - {}", self.recursive_search.to_string()); println!("Number of files to check - {:?}", self.number_of_files_to_check); println!("-----------------------------------------"); } +} +impl SaveResults for BigFile { + /// Saving results to provided file + fn save_results_to_file(&mut self, file_name: &str) -> bool { + let start_time: SystemTime = SystemTime::now(); + let file_name: String = match file_name { + "" => "results.txt".to_string(), + k => k.to_string(), + }; - pub fn set_number_of_files_to_check(&mut self, number_of_files_to_check: usize) { - self.number_of_files_to_check = number_of_files_to_check; + let mut file = match File::create(&file_name) { + Ok(t) => t, + Err(_) => { + self.text_messages.errors.push("Failed to create file ".to_string() + file_name.as_str()); + return false; + } + }; + + match file.write_all( + format!( + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}\n", + self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items + ) + .as_bytes(), + ) { + Ok(_) => (), + Err(_) => { + self.text_messages.errors.push("Failed to save results to file ".to_string() + file_name.as_str()); + return false; + } + } + + if self.information.number_of_real_files != 0 { + file.write_all(format!("{} the biggest files.\n\n", self.information.number_of_real_files).as_bytes()).unwrap(); + + for (size, files) in self.big_files.iter().rev() { + for file_entry in files { + file.write_all(format!("{} ({}) - {}\n", size.file_size(options::BINARY).unwrap(), size, file_entry.path.clone()).as_bytes()).unwrap(); + } + } + } else { + file.write_all(b"Not found any empty folders.").unwrap(); + } + Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); + true } - - /// Print information's about duplicated entries - /// Only needed for CLI - pub fn print_duplicated_entries(&self) { +} +impl PrintResults for BigFile { + fn print_results(&self) { let start_time: SystemTime = SystemTime::now(); println!("Found {} files which take {}:", self.information.number_of_real_files, self.information.taken_space.file_size(options::BINARY).unwrap()); for (size, vector) in self.big_files.iter().rev() { @@ -378,273 +376,4 @@ impl BigFile { } Common::print_time(start_time, SystemTime::now(), "print_duplicated_entries".to_string()); } - - /// Setting excluded items which needs to contains * wildcrard - /// Are a lot of slower than absolute path, so it should be used to heavy - pub fn set_excluded_items(&mut self, mut excluded_items: String) { - let start_time: SystemTime = SystemTime::now(); - - if excluded_items.is_empty() { - return; - } - - excluded_items = excluded_items.replace("\"", ""); - let expressions: Vec = excluded_items.split(',').map(String::from).collect(); - let mut checked_expressions: Vec = Vec::new(); - - for expression in expressions { - let expression: String = expression.trim().to_string(); - - if expression == "" { - continue; - } - if expression == "DEFAULT" { - // TODO add more files by default - checked_expressions.push("*/.git/*".to_string()); - continue; - } - if !expression.contains('*') { - self.text_messages.warnings.push("Excluded Items Warning: Wildcard * is required in expression, ignoring ".to_string() + expression.as_str()); - continue; - } - - checked_expressions.push(expression); - } - self.excluded_items = checked_expressions; - Common::print_time(start_time, SystemTime::now(), "set_excluded_items".to_string()); - } - - /// Remove unused entries when included or excluded overlaps with each other or are duplicated etc. - fn optimize_directories(&mut self) -> bool { - let start_time: SystemTime = SystemTime::now(); - - let mut optimized_included: Vec = Vec::::new(); - let mut optimized_excluded: Vec = Vec::::new(); - - // Remove duplicated entries like: "/", "/" - - self.excluded_directories.sort(); - self.included_directories.sort(); - - self.excluded_directories.dedup(); - self.included_directories.dedup(); - - // Optimize for duplicated included directories - "/", "/home". "/home/Pulpit" to "/" - if self.recursive_search { - // This is only point which can't be done when recursive search is disabled. - let mut is_inside: bool; - for ed_checked in &self.excluded_directories { - is_inside = false; - for ed_help in &self.excluded_directories { - if ed_checked == ed_help { - // We checking same element - continue; - } - if ed_checked.starts_with(ed_help) { - is_inside = true; - break; - } - } - if !is_inside { - optimized_excluded.push(ed_checked.to_string()); - } - } - - for id_checked in &self.included_directories { - is_inside = false; - for id_help in &self.included_directories { - if id_checked == id_help { - // We checking same element - continue; - } - if id_checked.starts_with(id_help) { - is_inside = true; - break; - } - } - if !is_inside { - optimized_included.push(id_checked.to_string()); - } - } - - self.included_directories = optimized_included; - optimized_included = Vec::::new(); - self.excluded_directories = optimized_excluded; - optimized_excluded = Vec::::new(); - } - - // Remove include directories which are inside any exclude directory - for id in &self.included_directories { - let mut is_inside: bool = false; - for ed in &self.excluded_directories { - if id.starts_with(ed) { - is_inside = true; - break; - } - } - if !is_inside { - optimized_included.push(id.to_string()); - } - } - self.included_directories = optimized_included; - optimized_included = Vec::::new(); - - // Remove non existed directories - for id in &self.included_directories { - let path = Path::new(id); - if path.exists() { - optimized_included.push(id.to_string()); - } - } - - for ed in &self.excluded_directories { - let path = Path::new(ed); - if path.exists() { - optimized_excluded.push(ed.to_string()); - } - } - - self.included_directories = optimized_included; - // optimized_included = Vec::::new(); - self.excluded_directories = optimized_excluded; - optimized_excluded = Vec::::new(); - - // Excluded paths must are inside include path, because - for ed in &self.excluded_directories { - let mut is_inside: bool = false; - for id in &self.included_directories { - if ed.starts_with(id) { - is_inside = true; - break; - } - } - if is_inside { - optimized_excluded.push(ed.to_string()); - } - } - - self.excluded_directories = optimized_excluded; - // optimized_excluded = Vec::::new(); - - if self.included_directories.is_empty() { - self.text_messages.errors.push("Optimize Directories ERROR: Excluded directories overlaps all included directories.".to_string()); - return false; - } - - // Not needed, but better is to have sorted everything - self.excluded_directories.sort(); - self.included_directories.sort(); - Common::print_time(start_time, SystemTime::now(), "optimize_directories".to_string()); - true - } - - /// Setting include directories, at least one must be provided - pub fn set_include_directory(&mut self, mut include_directory: String) -> bool { - let start_time: SystemTime = SystemTime::now(); - - if include_directory.is_empty() { - self.text_messages.errors.push("At least one directory must be provided".to_string()); - return false; - } - - include_directory = include_directory.replace("\"", ""); - let directories: Vec = include_directory.split(',').map(String::from).collect(); - let mut checked_directories: Vec = Vec::new(); - - for directory in directories { - let directory: String = directory.trim().to_string(); - - if directory == "" { - continue; - } - if directory.contains('*') { - self.text_messages.warnings.push("Include Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !directory.starts_with('/') { - self.text_messages.warnings.push("Include Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).exists() { - self.text_messages.warnings.push("Include Directory Warning: Provided folder path must exits, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).is_dir() { - self.text_messages.warnings.push("Include Directory Warning: Provided path must point at the directory, ignoring ".to_string() + directory.as_str()); - continue; - } - - // directory must end with /, due to possibility of incorrect assumption, that e.g. /home/rafal is top folder to /home/rafalinho - if !directory.ends_with('/') { - checked_directories.push(directory + "/"); - } else { - checked_directories.push(directory); - } - } - - if checked_directories.is_empty() { - self.text_messages.errors.push("Include Directory ERROR: Not found even one correct path to include which is required.".to_string()); - return false; - } - - self.included_directories = checked_directories; - - Common::print_time(start_time, SystemTime::now(), "set_include_directory".to_string()); - true - } - - /// Setting absolute path to exclude - pub fn set_exclude_directory(&mut self, mut exclude_directory: String) { - let start_time: SystemTime = SystemTime::now(); - if exclude_directory.is_empty() { - return; - } - - exclude_directory = exclude_directory.replace("\"", ""); - let directories: Vec = exclude_directory.split(',').map(String::from).collect(); - let mut checked_directories: Vec = Vec::new(); - - for directory in directories { - let directory: String = directory.trim().to_string(); - - if directory == "" { - continue; - } - if directory == "/" { - self.text_messages.errors.push("Exclude Directory ERROR: Excluding / is pointless, because it means that no files will be scanned.".to_string()); - break; - } - if directory.contains('*') { - self.text_messages.warnings.push("Exclude Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !directory.starts_with('/') { - self.text_messages.warnings.push("Exclude Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).exists() { - self.text_messages.warnings.push("Exclude Directory Warning: Provided folder path must exits, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).is_dir() { - self.text_messages.warnings.push("Exclude Directory Warning: Provided path must point at the directory, ignoring ".to_string() + directory.as_str()); - continue; - } - - // directory must end with /, due to possibility of incorrect assumption, that e.g. /home/rafal is top folder to /home/rafalinho - if !directory.ends_with('/') { - checked_directories.push(directory.trim().to_string() + "/"); - } else { - checked_directories.push(directory.trim().to_string()); - } - } - self.excluded_directories = checked_directories; - - Common::print_time(start_time, SystemTime::now(), "set_exclude_directory".to_string()); - } -} -impl Default for BigFile { - fn default() -> Self { - Self::new() - } } diff --git a/czkawka_core/src/common.rs b/czkawka_core/src/common.rs index 8ceef05..ba52c85 100644 --- a/czkawka_core/src/common.rs +++ b/czkawka_core/src/common.rs @@ -104,54 +104,6 @@ impl Common { } } -pub struct Messages { - pub messages: Vec, - pub warnings: Vec, - pub errors: Vec, -} - -impl Messages { - pub fn new() -> Messages { - Messages { - messages: vec![], - warnings: vec![], - errors: vec![], - } - } - pub fn print_messages(&self) { - if !self.messages.is_empty() { - println!("-------------------------------MESSAGES--------------------------------"); - for i in &self.messages { - println!("{}", i); - } - println!("---------------------------END OF MESSAGES-----------------------------"); - } - - if !self.warnings.is_empty() { - println!("-------------------------------WARNINGS--------------------------------"); - - for i in &self.warnings { - println!("{}", i); - } - println!("---------------------------END OF WARNINGS-----------------------------"); - } - - if !self.errors.is_empty() { - println!("--------------------------------ERRORS---------------------------------"); - - for i in &self.errors { - println!("{}", i); - } - println!("----------------------------END OF ERRORS------------------------------"); - } - } -} -impl Default for Messages { - fn default() -> Self { - Self::new() - } -} - #[cfg(test)] mod test { use crate::common::Common; diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs new file mode 100644 index 0000000..a0d5460 --- /dev/null +++ b/czkawka_core/src/common_directory.rs @@ -0,0 +1,252 @@ +use crate::common::Common; +use crate::common_messages::Messages; +use std::path::Path; +use std::time::SystemTime; + +pub struct Directories { + pub excluded_directories: Vec, + pub included_directories: Vec, +} +impl Directories { + pub fn new() -> Directories { + Directories { + excluded_directories: vec![], + included_directories: vec![], + } + } + + /// Setting include directories, at least one must be provided + pub fn set_included_directory(&mut self, mut included_directory: String, text_messages: &mut Messages) -> bool { + let start_time: SystemTime = SystemTime::now(); + + if included_directory.is_empty() { + text_messages.errors.push("At least one directory must be provided".to_string()); + return false; + } + + included_directory = included_directory.replace("\"", ""); + let directories: Vec = included_directory.split(',').map(String::from).collect(); + let mut checked_directories: Vec = Vec::new(); + + for directory in directories { + let directory: String = directory.trim().to_string(); + + if directory == "" { + continue; + } + if directory.contains('*') { + text_messages.warnings.push("Include Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); + continue; + } + if !directory.starts_with('/') { + text_messages.warnings.push("Include Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); + continue; + } + if !Path::new(&directory).exists() { + text_messages.warnings.push("Include Directory Warning: Provided folder path must exits, ignoring ".to_string() + directory.as_str()); + continue; + } + if !Path::new(&directory).is_dir() { + text_messages.warnings.push("Include Directory Warning: Provided path must point at the directory, ignoring ".to_string() + directory.as_str()); + continue; + } + + // directory must end with /, due to possibility of incorrect assumption, that e.g. /home/rafal is top folder to /home/rafalinho + if !directory.ends_with('/') { + checked_directories.push(directory + "/"); + } else { + checked_directories.push(directory); + } + } + + if checked_directories.is_empty() { + text_messages.errors.push("Include Directory ERROR: Not found even one correct path to include which is required.".to_string()); + return false; + } + + self.included_directories = checked_directories; + + Common::print_time(start_time, SystemTime::now(), "set_included_directory".to_string()); + true + } + + /// Setting absolute path to exclude + pub fn set_excluded_directory(&mut self, mut excluded_directory: String, text_messages: &mut Messages) { + let start_time: SystemTime = SystemTime::now(); + if excluded_directory.is_empty() { + return; + } + + excluded_directory = excluded_directory.replace("\"", ""); + let directories: Vec = excluded_directory.split(',').map(String::from).collect(); + let mut checked_directories: Vec = Vec::new(); + + for directory in directories { + let directory: String = directory.trim().to_string(); + + if directory == "" { + continue; + } + if directory == "/" { + text_messages.errors.push("Exclude Directory ERROR: Excluding / is pointless, because it means that no files will be scanned.".to_string()); + break; + } + if directory.contains('*') { + text_messages.warnings.push("Exclude Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); + continue; + } + if !directory.starts_with('/') { + text_messages.warnings.push("Exclude Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); + continue; + } + if !Path::new(&directory).exists() { + text_messages.warnings.push("Exclude Directory Warning: Provided folder path must exits, ignoring ".to_string() + directory.as_str()); + continue; + } + if !Path::new(&directory).is_dir() { + text_messages.warnings.push("Exclude Directory Warning: Provided path must point at the directory, ignoring ".to_string() + directory.as_str()); + continue; + } + + // directory must end with /, due to possibility of incorrect assumption, that e.g. /home/rafal is top folder to /home/rafalinho + if !directory.ends_with('/') { + checked_directories.push(directory.trim().to_string() + "/"); + } else { + checked_directories.push(directory.trim().to_string()); + } + } + self.excluded_directories = checked_directories; + + Common::print_time(start_time, SystemTime::now(), "set_excluded_directory".to_string()); + } + + /// Remove unused entries when included or excluded overlaps with each other or are duplicated etc. + pub fn optimize_directories(&mut self, recursive_search: bool, text_messages: &mut Messages) -> bool { + let start_time: SystemTime = SystemTime::now(); + + let mut optimized_included: Vec = Vec::::new(); + let mut optimized_excluded: Vec = Vec::::new(); + + // Remove duplicated entries like: "/", "/" + + self.excluded_directories.sort(); + self.included_directories.sort(); + + self.excluded_directories.dedup(); + self.included_directories.dedup(); + + // Optimize for duplicated included directories - "/", "/home". "/home/Pulpit" to "/" + if recursive_search { + // This is only point which can't be done when recursive search is disabled. + let mut is_inside: bool; + for ed_checked in &self.excluded_directories { + is_inside = false; + for ed_help in &self.excluded_directories { + if ed_checked == ed_help { + // We checking same element + continue; + } + if ed_checked.starts_with(ed_help) { + is_inside = true; + break; + } + } + if !is_inside { + optimized_excluded.push(ed_checked.to_string()); + } + } + + for id_checked in &self.included_directories { + is_inside = false; + for id_help in &self.included_directories { + if id_checked == id_help { + // We checking same element + continue; + } + if id_checked.starts_with(id_help) { + is_inside = true; + break; + } + } + if !is_inside { + optimized_included.push(id_checked.to_string()); + } + } + + self.included_directories = optimized_included; + optimized_included = Vec::::new(); + self.excluded_directories = optimized_excluded; + optimized_excluded = Vec::::new(); + } + + // Remove include directories which are inside any exclude directory + for id in &self.included_directories { + let mut is_inside: bool = false; + for ed in &self.excluded_directories { + if id.starts_with(ed) { + is_inside = true; + break; + } + } + if !is_inside { + optimized_included.push(id.to_string()); + } + } + self.included_directories = optimized_included; + optimized_included = Vec::::new(); + + // Remove non existed directories + for id in &self.included_directories { + let path = Path::new(id); + if path.exists() { + optimized_included.push(id.to_string()); + } + } + + for ed in &self.excluded_directories { + let path = Path::new(ed); + if path.exists() { + optimized_excluded.push(ed.to_string()); + } + } + + self.included_directories = optimized_included; + // optimized_included = Vec::::new(); + self.excluded_directories = optimized_excluded; + optimized_excluded = Vec::::new(); + + // Excluded paths must are inside include path, because + for ed in &self.excluded_directories { + let mut is_inside: bool = false; + for id in &self.included_directories { + if ed.starts_with(id) { + is_inside = true; + break; + } + } + if is_inside { + optimized_excluded.push(ed.to_string()); + } + } + + self.excluded_directories = optimized_excluded; + // optimized_excluded = Vec::::new(); + + if self.included_directories.is_empty() { + text_messages.errors.push("Optimize Directories ERROR: Excluded directories overlaps all included directories.".to_string()); + return false; + } + + // Not needed, but better is to have sorted everything + self.excluded_directories.sort(); + self.included_directories.sort(); + Common::print_time(start_time, SystemTime::now(), "optimize_directories".to_string()); + true + } +} + +impl Default for Directories { + fn default() -> Self { + Self::new() + } +} diff --git a/czkawka_core/src/common_extensions.rs b/czkawka_core/src/common_extensions.rs new file mode 100644 index 0000000..c0348f1 --- /dev/null +++ b/czkawka_core/src/common_extensions.rs @@ -0,0 +1,54 @@ +use crate::common::Common; +use crate::common_messages::Messages; +use std::time::SystemTime; + +pub struct Extensions { + pub file_extensions: Vec, +} + +impl Extensions { + pub fn new() -> Extensions { + Extensions { file_extensions: vec![] } + } + /// List of allowed extensions, only files with this extensions will be checking if are duplicates + pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String, text_messages: &mut Messages) { + let start_time: SystemTime = SystemTime::now(); + if allowed_extensions.is_empty() { + return; + } + allowed_extensions = allowed_extensions.replace("IMAGE", "jpg,kra,gif,png,bmp,tiff,webp,hdr,svg"); + allowed_extensions = allowed_extensions.replace("VIDEO", "mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp"); + allowed_extensions = allowed_extensions.replace("MUSIC", "mp3,flac,ogg,tta,wma,webm"); + allowed_extensions = allowed_extensions.replace("TEXT", "txt,doc,docx,odt,rtf"); + + let extensions: Vec = allowed_extensions.split(',').map(String::from).collect(); + for mut extension in extensions { + if extension == "" || extension.replace('.', "").trim() == "" { + continue; + } + + if extension.starts_with('.') { + extension = extension[1..].to_string(); + } + + if extension[1..].contains('.') { + text_messages.warnings.push(".".to_string() + extension.as_str() + " is not valid extension(valid extension doesn't have dot inside)"); + continue; + } + + if !self.file_extensions.contains(&extension.trim().to_string()) { + self.file_extensions.push(extension.trim().to_string()); + } + } + + if self.file_extensions.is_empty() { + text_messages.messages.push("No valid extensions were provided, so allowing all extensions by default.".to_string()); + } + Common::print_time(start_time, SystemTime::now(), "set_allowed_extensions".to_string()); + } +} +impl Default for Extensions { + fn default() -> Self { + Self::new() + } +} diff --git a/czkawka_core/src/common_items.rs b/czkawka_core/src/common_items.rs new file mode 100644 index 0000000..4142294 --- /dev/null +++ b/czkawka_core/src/common_items.rs @@ -0,0 +1,52 @@ +use crate::common::Common; +use crate::common_messages::Messages; +use std::time::SystemTime; + +pub struct ExcludedItems { + pub items: Vec, +} + +impl ExcludedItems { + pub fn new() -> ExcludedItems { + ExcludedItems { items: vec![] } + } + /// Setting excluded items which needs to contains * wildcrard + /// Are a lot of slower than absolute path, so it should be used to heavy + pub fn set_excluded_items(&mut self, mut excluded_items: String, text_messages: &mut Messages) { + let start_time: SystemTime = SystemTime::now(); + + if excluded_items.is_empty() { + return; + } + + excluded_items = excluded_items.replace("\"", ""); + let expressions: Vec = excluded_items.split(',').map(String::from).collect(); + let mut checked_expressions: Vec = Vec::new(); + + for expression in expressions { + let expression: String = expression.trim().to_string(); + + if expression == "" { + continue; + } + if expression == "DEFAULT" { + // TODO add more files by default + checked_expressions.push("*/.git/*".to_string()); + continue; + } + if !expression.contains('*') { + text_messages.warnings.push("Excluded Items Warning: Wildcard * is required in expression, ignoring ".to_string() + expression.as_str()); + continue; + } + + checked_expressions.push(expression); + } + self.items = checked_expressions; + Common::print_time(start_time, SystemTime::now(), "set_excluded_items".to_string()); + } +} +impl Default for ExcludedItems { + fn default() -> Self { + Self::new() + } +} diff --git a/czkawka_core/src/common_messages.rs b/czkawka_core/src/common_messages.rs new file mode 100644 index 0000000..1645610 --- /dev/null +++ b/czkawka_core/src/common_messages.rs @@ -0,0 +1,47 @@ +pub struct Messages { + pub messages: Vec, + pub warnings: Vec, + pub errors: Vec, +} + +impl Messages { + pub fn new() -> Messages { + Messages { + messages: vec![], + warnings: vec![], + errors: vec![], + } + } + pub fn print_messages(&self) { + if !self.messages.is_empty() { + println!("-------------------------------MESSAGES--------------------------------"); + for i in &self.messages { + println!("{}", i); + } + println!("---------------------------END OF MESSAGES-----------------------------"); + } + + if !self.warnings.is_empty() { + println!("-------------------------------WARNINGS--------------------------------"); + + for i in &self.warnings { + println!("{}", i); + } + println!("---------------------------END OF WARNINGS-----------------------------"); + } + + if !self.errors.is_empty() { + println!("--------------------------------ERRORS---------------------------------"); + + for i in &self.errors { + println!("{}", i); + } + println!("----------------------------END OF ERRORS------------------------------"); + } + } +} +impl Default for Messages { + fn default() -> Self { + Self::new() + } +} diff --git a/czkawka_core/src/common_traits.rs b/czkawka_core/src/common_traits.rs new file mode 100644 index 0000000..0f98fe2 --- /dev/null +++ b/czkawka_core/src/common_traits.rs @@ -0,0 +1,9 @@ +pub trait DebugPrint { + fn debug_print(&self); +} +pub trait SaveResults { + fn save_results_to_file(&mut self, file_name: &str) -> bool; +} +pub trait PrintResults { + fn print_results(&self); +} diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index e05cbb7..423bfe0 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -3,10 +3,14 @@ use std::collections::{BTreeMap, HashMap}; use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; -use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::common::{Common, Messages}; +use crate::common::Common; +use crate::common_directory::Directories; +use crate::common_extensions::Extensions; +use crate::common_items::ExcludedItems; +use crate::common_messages::Messages; +use crate::common_traits::*; #[derive(PartialEq, Eq, Clone, Debug)] pub enum CheckingMethod { @@ -82,10 +86,9 @@ pub struct DuplicateFinder { information: Info, files_with_identical_size: BTreeMap>, files_with_identical_hashes: BTreeMap>>, - allowed_extensions: Vec, - excluded_items: Vec, - excluded_directories: Vec, - included_directories: Vec, + directories: Directories, + allowed_extensions: Extensions, + excluded_items: ExcludedItems, recursive_search: bool, min_file_size: u64, check_method: CheckingMethod, @@ -95,21 +98,31 @@ pub struct DuplicateFinder { impl DuplicateFinder { pub fn new() -> DuplicateFinder { DuplicateFinder { - text_messages: Default::default(), + text_messages: Messages::new(), information: Info::new(), files_with_identical_size: Default::default(), files_with_identical_hashes: Default::default(), - excluded_items: vec![], - excluded_directories: vec![], - included_directories: vec![], recursive_search: true, - allowed_extensions: vec![], + allowed_extensions: Extensions::new(), check_method: CheckingMethod::None, delete_method: DeleteMethod::None, min_file_size: 1024, + directories: Directories::new(), + excluded_items: ExcludedItems::new(), } } + /// Finding duplicates, save results to internal struct variables + pub fn find_duplicates(&mut self) { + self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); + self.check_files_size(); + if self.check_method == CheckingMethod::Hash { + self.check_files_hash(); + } + self.delete_files(); + self.debug_print(); + } + pub fn get_files_sorted_by_size(&self) -> &BTreeMap> { &self.files_with_identical_size } @@ -142,192 +155,19 @@ impl DuplicateFinder { self.recursive_search = recursive_search; } - /// Finding duplicates, save results to internal struct variables - pub fn find_duplicates(&mut self) { - self.optimize_directories(); - self.check_files_size(); - if self.check_method == CheckingMethod::Hash { - self.check_files_hash(); - } - self.delete_files(); - self.debug_print(); + pub fn set_included_directory(&mut self, included_directory: String) -> bool { + self.directories.set_included_directory(included_directory, &mut self.text_messages) } - /// Setting excluded items which needs to contains * wildcrard - /// Are a lot of slower than absolute path, so it should be used to heavy - pub fn set_excluded_items(&mut self, mut excluded_items: String) { - let start_time: SystemTime = SystemTime::now(); - - if excluded_items.is_empty() { - return; - } - - excluded_items = excluded_items.replace("\"", ""); - let expressions: Vec = excluded_items.split(',').map(String::from).collect(); - let mut checked_expressions: Vec = Vec::new(); - - for expression in expressions { - let expression: String = expression.trim().to_string(); - - if expression == "" { - continue; - } - if expression == "DEFAULT" { - // TODO add more files by default - checked_expressions.push("*/.git/*".to_string()); - continue; - } - if !expression.contains('*') { - self.text_messages.warnings.push("Excluded Items Warning: Wildcard * is required in expression, ignoring ".to_string() + expression.as_str()); - continue; - } - - checked_expressions.push(expression); - } - self.excluded_items = checked_expressions; - Common::print_time(start_time, SystemTime::now(), "set_excluded_items".to_string()); + pub fn set_excluded_directory(&mut self, excluded_directory: String) { + self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); + } + pub fn set_allowed_extensions(&mut self, allowed_extensions: String) { + self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages); } - /// List of allowed extensions, only files with this extensions will be checking if are duplicates - pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String) { - let start_time: SystemTime = SystemTime::now(); - if allowed_extensions.is_empty() { - return; - } - allowed_extensions = allowed_extensions.replace("IMAGE", "jpg,kra,gif,png,bmp,tiff,webp,hdr,svg"); - allowed_extensions = allowed_extensions.replace("VIDEO", "mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp"); - allowed_extensions = allowed_extensions.replace("MUSIC", "mp3,flac,ogg,tta,wma,webm"); - allowed_extensions = allowed_extensions.replace("TEXT", "txt,doc,docx,odt,rtf"); - - let extensions: Vec = allowed_extensions.split(',').map(String::from).collect(); - for mut extension in extensions { - if extension == "" || extension.replace('.', "").trim() == "" { - continue; - } - - if extension.starts_with('.') { - extension = extension[1..].to_string(); - } - - if extension[1..].contains('.') { - self.text_messages.warnings.push(".".to_string() + extension.as_str() + " is not valid extension(valid extension doesn't have dot inside)"); - continue; - } - - if !self.allowed_extensions.contains(&extension.trim().to_string()) { - self.allowed_extensions.push(extension.trim().to_string()); - } - } - - if self.allowed_extensions.is_empty() { - self.text_messages.messages.push("No valid extensions were provided, so allowing all extensions by default.".to_string()); - } - Common::print_time(start_time, SystemTime::now(), "set_allowed_extensions".to_string()); - } - - /// Setting include directories, at least one must be provided - pub fn set_include_directory(&mut self, mut include_directory: String) -> bool { - let start_time: SystemTime = SystemTime::now(); - - if include_directory.is_empty() { - self.text_messages.errors.push("At least one directory must be provided".to_string()); - return false; - } - - include_directory = include_directory.replace("\"", ""); - let directories: Vec = include_directory.split(',').map(String::from).collect(); - let mut checked_directories: Vec = Vec::new(); - - for directory in directories { - let directory: String = directory.trim().to_string(); - - if directory == "" { - continue; - } - if directory.contains('*') { - self.text_messages.warnings.push("Include Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !directory.starts_with('/') { - self.text_messages.warnings.push("Include Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).exists() { - self.text_messages.warnings.push("Include Directory Warning: Provided folder path must exits, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).is_dir() { - self.text_messages.warnings.push("Include Directory Warning: Provided path must point at the directory, ignoring ".to_string() + directory.as_str()); - continue; - } - - // directory must end with /, due to possibility of incorrect assumption, that e.g. /home/rafal is top folder to /home/rafalinho - if !directory.ends_with('/') { - checked_directories.push(directory + "/"); - } else { - checked_directories.push(directory); - } - } - - if checked_directories.is_empty() { - self.text_messages.errors.push("Include Directory ERROR: Not found even one correct path to include which is required.".to_string()); - return false; - } - - self.included_directories = checked_directories; - - Common::print_time(start_time, SystemTime::now(), "set_include_directory".to_string()); - true - } - - /// Setting absolute path to exclude - pub fn set_exclude_directory(&mut self, mut exclude_directory: String) { - let start_time: SystemTime = SystemTime::now(); - if exclude_directory.is_empty() { - return; - } - - exclude_directory = exclude_directory.replace("\"", ""); - let directories: Vec = exclude_directory.split(',').map(String::from).collect(); - let mut checked_directories: Vec = Vec::new(); - - for directory in directories { - let directory: String = directory.trim().to_string(); - - if directory == "" { - continue; - } - if directory == "/" { - self.text_messages.errors.push("Exclude Directory ERROR: Excluding / is pointless, because it means that no files will be scanned.".to_string()); - break; - } - if directory.contains('*') { - self.text_messages.warnings.push("Exclude Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !directory.starts_with('/') { - self.text_messages.warnings.push("Exclude Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).exists() { - self.text_messages.warnings.push("Exclude Directory Warning: Provided folder path must exits, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).is_dir() { - self.text_messages.warnings.push("Exclude Directory Warning: Provided path must point at the directory, ignoring ".to_string() + directory.as_str()); - continue; - } - - // directory must end with /, due to possibility of incorrect assumption, that e.g. /home/rafal is top folder to /home/rafalinho - if !directory.ends_with('/') { - checked_directories.push(directory.trim().to_string() + "/"); - } else { - checked_directories.push(directory.trim().to_string()); - } - } - self.excluded_directories = checked_directories; - - Common::print_time(start_time, SystemTime::now(), "set_exclude_directory".to_string()); + pub fn set_excluded_items(&mut self, excluded_items: String) { + self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); } /// Read file length and puts it to different boxes(each for different lengths) @@ -338,7 +178,7 @@ impl DuplicateFinder { let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector // Add root folders for finding - for id in &self.included_directories { + for id in &self.directories.included_directories { folders_to_check.push(id.to_string()); } self.information.number_of_checked_folders += folders_to_check.len(); @@ -384,7 +224,7 @@ impl DuplicateFinder { let mut is_excluded_dir = false; next_folder = "".to_owned() + ¤t_folder + &entry_data.file_name().into_string().unwrap() + "/"; - for ed in &self.excluded_directories { + for ed in &self.directories.excluded_directories { if next_folder == *ed { is_excluded_dir = true; break; @@ -392,7 +232,7 @@ impl DuplicateFinder { } if !is_excluded_dir { let mut found_expression: bool = false; - for expression in &self.excluded_items { + for expression in &self.excluded_items.items { if Common::regex_check(expression, &next_folder) { found_expression = true; break; @@ -408,9 +248,9 @@ impl DuplicateFinder { let file_name_lowercase: String = entry_data.file_name().into_string().unwrap().to_lowercase(); // Checking allowed extensions - if !self.allowed_extensions.is_empty() { + if !self.allowed_extensions.file_extensions.is_empty() { have_valid_extension = false; - for i in &self.allowed_extensions { + for i in &self.allowed_extensions.file_extensions { if file_name_lowercase.ends_with((".".to_string() + i.to_lowercase().as_str()).as_str()) { have_valid_extension = true; break; @@ -426,7 +266,7 @@ impl DuplicateFinder { // Checking expressions let mut found_expression: bool = false; - for expression in &self.excluded_items { + for expression in &self.excluded_items.items { if Common::regex_check(expression, ¤t_file_name) { found_expression = true; break; @@ -488,87 +328,6 @@ impl DuplicateFinder { Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string()); } - /// Saving results to provided file - pub fn save_results_to_file(&mut self, file_name: &str) -> bool { - let start_time: SystemTime = SystemTime::now(); - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let mut file = match File::create(&file_name) { - Ok(t) => t, - Err(_) => { - self.text_messages.errors.push("Failed to create file ".to_string() + file_name.as_str()); - return false; - } - }; - - match file.write_all(format!("Results of searching in {:?}\n", self.included_directories).as_bytes()) { - Ok(_) => (), - Err(_) => { - self.text_messages.errors.push("Failed to save results to file ".to_string() + file_name.as_str()); - return false; - } - } - - if !self.files_with_identical_size.is_empty() { - file.write_all(b"-------------------------------------------------Files with same size-------------------------------------------------\n").unwrap(); - file.write_all( - ("Found ".to_string() - + self.information.number_of_duplicated_files_by_size.to_string().as_str() - + " duplicated files which in " - + self.information.number_of_groups_by_size.to_string().as_str() - + " groups which takes " - + self.information.lost_space_by_size.file_size(options::BINARY).unwrap().as_str() - + ".\n") - .as_bytes(), - ) - .unwrap(); - for (size, files) in self.files_with_identical_size.iter().rev() { - file.write_all(b"\n---- Size ").unwrap(); - file.write_all(size.file_size(options::BINARY).unwrap().as_bytes()).unwrap(); - file.write_all((" (".to_string() + size.to_string().as_str() + ")").as_bytes()).unwrap(); - file.write_all((" - ".to_string() + files.len().to_string().as_str() + " files").as_bytes()).unwrap(); - file.write_all(b"\n").unwrap(); - for file_entry in files { - file.write_all((file_entry.path.clone() + "\n").as_bytes()).unwrap(); - } - } - - if !self.files_with_identical_hashes.is_empty() { - file.write_all(b"-------------------------------------------------Files with same hashes-------------------------------------------------\n").unwrap(); - file.write_all( - ("Found ".to_string() - + self.information.number_of_duplicated_files_by_hash.to_string().as_str() - + " duplicated files which in " - + self.information.number_of_groups_by_hash.to_string().as_str() - + " groups which takes " - + self.information.lost_space_by_hash.file_size(options::BINARY).unwrap().as_str() - + ".\n") - .as_bytes(), - ) - .unwrap(); - for (size, files) in self.files_with_identical_hashes.iter().rev() { - for vector in files { - file.write_all(b"\n---- Size ").unwrap(); - file.write_all(size.file_size(options::BINARY).unwrap().as_bytes()).unwrap(); - file.write_all((" (".to_string() + size.to_string().as_str() + ")").as_bytes()).unwrap(); - file.write_all((" - ".to_string() + vector.len().to_string().as_str() + " files").as_bytes()).unwrap(); - file.write_all(b"\n").unwrap(); - for file_entry in vector { - file.write_all((file_entry.path.clone() + "\n").as_bytes()).unwrap(); - } - } - } - } - } else { - file.write_all(b"Not found any empty folders.").unwrap(); - } - Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); - true - } - /// The slowest checking type, which must be applied after checking for size fn check_files_hash(&mut self) { let start_time: SystemTime = SystemTime::now(); @@ -631,6 +390,46 @@ impl DuplicateFinder { Common::print_time(start_time, SystemTime::now(), "check_files_hash".to_string()); } + /// Function to delete files, from filed before BTreeMap + /// Using another function to delete files to avoid duplicates data + fn delete_files(&mut self) { + let start_time: SystemTime = SystemTime::now(); + + match self.check_method { + CheckingMethod::Hash => { + for entry in &self.files_with_identical_hashes { + for vector in entry.1 { + let tuple: (u64, usize, usize) = delete_files(&vector, &self.delete_method, &mut self.text_messages.warnings); + self.information.gained_space += tuple.0; + self.information.number_of_removed_files += tuple.1; + self.information.number_of_failed_to_remove_files += tuple.2; + } + } + } + CheckingMethod::Size => { + for entry in &self.files_with_identical_size { + let tuple: (u64, usize, usize) = delete_files(&entry.1, &self.delete_method, &mut self.text_messages.warnings); + self.information.gained_space += tuple.0; + self.information.number_of_removed_files += tuple.1; + self.information.number_of_failed_to_remove_files += tuple.2; + } + } + CheckingMethod::None => { + //Just do nothing + panic!("Checking method should never be none."); + } + } + + Common::print_time(start_time, SystemTime::now(), "delete_files".to_string()); + } +} +impl Default for DuplicateFinder { + fn default() -> Self { + Self::new() + } +} + +impl DebugPrint for DuplicateFinder { #[allow(dead_code)] #[allow(unreachable_code)] /// Debugging printing - only available on debug build @@ -676,20 +475,102 @@ impl DuplicateFinder { println!("Files list size - {}", self.files_with_identical_size.len()); println!("Hashed Files list size - {}", self.files_with_identical_hashes.len()); - println!("Allowed extensions - {:?}", self.allowed_extensions); - println!("Excluded items - {:?}", self.excluded_items); - println!("Included directories - {:?}", self.included_directories); - println!("Excluded directories - {:?}", self.excluded_directories); + println!("Allowed extensions - {:?}", self.allowed_extensions.file_extensions); + println!("Excluded items - {:?}", self.excluded_items.items); + println!("Included directories - {:?}", self.directories.included_directories); + println!("Excluded directories - {:?}", self.directories.excluded_directories); println!("Recursive search - {}", self.recursive_search.to_string()); println!("Minimum file size - {:?}", self.min_file_size); println!("Checking Method - {:?}", self.check_method); println!("Delete Method - {:?}", self.delete_method); println!("-----------------------------------------"); } +} +impl SaveResults for DuplicateFinder { + fn save_results_to_file(&mut self, file_name: &str) -> bool { + let start_time: SystemTime = SystemTime::now(); + let file_name: String = match file_name { + "" => "results.txt".to_string(), + k => k.to_string(), + }; + let mut file = match File::create(&file_name) { + Ok(t) => t, + Err(_) => { + self.text_messages.errors.push("Failed to create file ".to_string() + file_name.as_str()); + return false; + } + }; + + match file.write_all(format!("Results of searching in {:?}\n", self.directories.included_directories).as_bytes()) { + Ok(_) => (), + Err(_) => { + self.text_messages.errors.push("Failed to save results to file ".to_string() + file_name.as_str()); + return false; + } + } + + if !self.files_with_identical_size.is_empty() { + file.write_all(b"-------------------------------------------------Files with same size-------------------------------------------------\n").unwrap(); + file.write_all( + ("Found ".to_string() + + self.information.number_of_duplicated_files_by_size.to_string().as_str() + + " duplicated files which in " + + self.information.number_of_groups_by_size.to_string().as_str() + + " groups which takes " + + self.information.lost_space_by_size.file_size(options::BINARY).unwrap().as_str() + + ".\n") + .as_bytes(), + ) + .unwrap(); + for (size, files) in self.files_with_identical_size.iter().rev() { + file.write_all(b"\n---- Size ").unwrap(); + file.write_all(size.file_size(options::BINARY).unwrap().as_bytes()).unwrap(); + file.write_all((" (".to_string() + size.to_string().as_str() + ")").as_bytes()).unwrap(); + file.write_all((" - ".to_string() + files.len().to_string().as_str() + " files").as_bytes()).unwrap(); + file.write_all(b"\n").unwrap(); + for file_entry in files { + file.write_all((file_entry.path.clone() + "\n").as_bytes()).unwrap(); + } + } + + if !self.files_with_identical_hashes.is_empty() { + file.write_all(b"-------------------------------------------------Files with same hashes-------------------------------------------------\n").unwrap(); + file.write_all( + ("Found ".to_string() + + self.information.number_of_duplicated_files_by_hash.to_string().as_str() + + " duplicated files which in " + + self.information.number_of_groups_by_hash.to_string().as_str() + + " groups which takes " + + self.information.lost_space_by_hash.file_size(options::BINARY).unwrap().as_str() + + ".\n") + .as_bytes(), + ) + .unwrap(); + for (size, files) in self.files_with_identical_hashes.iter().rev() { + for vector in files { + file.write_all(b"\n---- Size ").unwrap(); + file.write_all(size.file_size(options::BINARY).unwrap().as_bytes()).unwrap(); + file.write_all((" (".to_string() + size.to_string().as_str() + ")").as_bytes()).unwrap(); + file.write_all((" - ".to_string() + vector.len().to_string().as_str() + " files").as_bytes()).unwrap(); + file.write_all(b"\n").unwrap(); + for file_entry in vector { + file.write_all((file_entry.path.clone() + "\n").as_bytes()).unwrap(); + } + } + } + } + } else { + file.write_all(b"Not found any empty folders.").unwrap(); + } + Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); + true + } +} +impl PrintResults for DuplicateFinder { /// Print information's about duplicated entries /// Only needed for CLI - pub fn print_duplicated_entries(&self) { + fn print_results(&self) { let start_time: SystemTime = SystemTime::now(); let mut number_of_files: u64 = 0; let mut number_of_groups: u64 = 0; @@ -744,168 +625,6 @@ impl DuplicateFinder { } Common::print_time(start_time, SystemTime::now(), "print_duplicated_entries".to_string()); } - - /// Remove unused entries when included or excluded overlaps with each other or are duplicated etc. - fn optimize_directories(&mut self) -> bool { - let start_time: SystemTime = SystemTime::now(); - - let mut optimized_included: Vec = Vec::::new(); - let mut optimized_excluded: Vec = Vec::::new(); - - // Remove duplicated entries like: "/", "/" - - self.excluded_directories.sort(); - self.included_directories.sort(); - - self.excluded_directories.dedup(); - self.included_directories.dedup(); - - // Optimize for duplicated included directories - "/", "/home". "/home/Pulpit" to "/" - if self.recursive_search { - // This is only point which can't be done when recursive search is disabled. - let mut is_inside: bool; - for ed_checked in &self.excluded_directories { - is_inside = false; - for ed_help in &self.excluded_directories { - if ed_checked == ed_help { - // We checking same element - continue; - } - if ed_checked.starts_with(ed_help) { - is_inside = true; - break; - } - } - if !is_inside { - optimized_excluded.push(ed_checked.to_string()); - } - } - - for id_checked in &self.included_directories { - is_inside = false; - for id_help in &self.included_directories { - if id_checked == id_help { - // We checking same element - continue; - } - if id_checked.starts_with(id_help) { - is_inside = true; - break; - } - } - if !is_inside { - optimized_included.push(id_checked.to_string()); - } - } - - self.included_directories = optimized_included; - optimized_included = Vec::::new(); - self.excluded_directories = optimized_excluded; - optimized_excluded = Vec::::new(); - } - - // Remove include directories which are inside any exclude directory - for id in &self.included_directories { - let mut is_inside: bool = false; - for ed in &self.excluded_directories { - if id.starts_with(ed) { - is_inside = true; - break; - } - } - if !is_inside { - optimized_included.push(id.to_string()); - } - } - self.included_directories = optimized_included; - optimized_included = Vec::::new(); - - // Remove non existed directories - for id in &self.included_directories { - let path = Path::new(id); - if path.exists() { - optimized_included.push(id.to_string()); - } - } - - for ed in &self.excluded_directories { - let path = Path::new(ed); - if path.exists() { - optimized_excluded.push(ed.to_string()); - } - } - - self.included_directories = optimized_included; - // optimized_included = Vec::::new(); - self.excluded_directories = optimized_excluded; - optimized_excluded = Vec::::new(); - - // Excluded paths must are inside include path, because - for ed in &self.excluded_directories { - let mut is_inside: bool = false; - for id in &self.included_directories { - if ed.starts_with(id) { - is_inside = true; - break; - } - } - if is_inside { - optimized_excluded.push(ed.to_string()); - } - } - - self.excluded_directories = optimized_excluded; - // optimized_excluded = Vec::::new(); - - if self.included_directories.is_empty() { - self.text_messages.errors.push("Optimize Directories ERROR: Excluded directories overlaps all included directories.".to_string()); - return false; - } - - // Not needed, but better is to have sorted everything - self.excluded_directories.sort(); - self.included_directories.sort(); - Common::print_time(start_time, SystemTime::now(), "optimize_directories".to_string()); - true - } - - /// Function to delete files, from filed before BTreeMap - /// Using another function to delete files to avoid duplicates data - fn delete_files(&mut self) { - let start_time: SystemTime = SystemTime::now(); - - match self.check_method { - CheckingMethod::Hash => { - for entry in &self.files_with_identical_hashes { - for vector in entry.1 { - let tuple: (u64, usize, usize) = delete_files(&vector, &self.delete_method, &mut self.text_messages.warnings); - self.information.gained_space += tuple.0; - self.information.number_of_removed_files += tuple.1; - self.information.number_of_failed_to_remove_files += tuple.2; - } - } - } - CheckingMethod::Size => { - for entry in &self.files_with_identical_size { - let tuple: (u64, usize, usize) = delete_files(&entry.1, &self.delete_method, &mut self.text_messages.warnings); - self.information.gained_space += tuple.0; - self.information.number_of_removed_files += tuple.1; - self.information.number_of_failed_to_remove_files += tuple.2; - } - } - CheckingMethod::None => { - //Just do nothing - panic!("Checking method should never be none."); - } - } - - Common::print_time(start_time, SystemTime::now(), "delete_files".to_string()); - } -} -impl Default for DuplicateFinder { - fn default() -> Self { - Self::new() - } } /// Functions to remove slice(vector) of files with provided method diff --git a/czkawka_core/src/empty_folder.rs b/czkawka_core/src/empty_folder.rs index e4c28a3..6c3b906 100644 --- a/czkawka_core/src/empty_folder.rs +++ b/czkawka_core/src/empty_folder.rs @@ -1,10 +1,12 @@ -use crate::common::{Common, Messages}; +use crate::common::Common; +use crate::common_directory::Directories; +use crate::common_messages::Messages; +use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; use std::collections::BTreeMap; +use std::fs; use std::fs::{File, Metadata}; use std::io::Write; -use std::path::Path; use std::time::SystemTime; -use std::{fs, process}; /// Enum with values which show if folder is empty. /// In function "optimize_folders" automatically "Maybe" is changed to "Yes", so it is not necessery to put it here @@ -28,7 +30,7 @@ pub struct EmptyFolder { delete_folders: bool, text_messages: Messages, empty_folder_list: BTreeMap, // Path, FolderEntry - included_directories: Vec, + directories: Directories, } /// Info struck with helpful information's about results @@ -57,9 +59,9 @@ impl EmptyFolder { EmptyFolder { information: Default::default(), delete_folders: false, - text_messages: Default::default(), + text_messages: Messages::new(), empty_folder_list: Default::default(), - included_directories: vec![], + directories: Directories::new(), } } @@ -76,9 +78,9 @@ impl EmptyFolder { /// Public function used by CLI to search for empty folders pub fn find_empty_folders(&mut self) { - self.optimize_directories(); + self.directories.optimize_directories(true, &mut self.text_messages); self.check_for_empty_folders(true); - self.check_for_empty_folders(false); // Not needed for CLI, but it is better to check this + self.check_for_empty_folders(false); // Not needed for CLI, but it is better to check this again, because maybe empty folder stops to be empty self.optimize_folders(); if self.delete_folders { self.delete_empty_folders(); @@ -90,43 +92,6 @@ impl EmptyFolder { self.delete_folders = delete_folder; } - pub fn save_results_to_file(&mut self, file_name: &str) -> bool { - let start_time: SystemTime = SystemTime::now(); - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let mut file = match File::create(&file_name) { - Ok(t) => t, - Err(_) => { - self.text_messages.errors.push("Failed to create file ".to_string() + file_name.as_str()); - return false; - } - }; - - match file.write_all(format!("Results of searching in {:?}\n", self.included_directories).as_bytes()) { - Ok(_) => (), - Err(_) => { - self.text_messages.errors.push("Failed to save results to file ".to_string() + file_name.as_str()); - return false; - } - } - - if !self.empty_folder_list.is_empty() { - file.write_all(b"-------------------------------------------------Empty folder list-------------------------------------------------\n").unwrap(); - file.write_all(("Found ".to_string() + self.information.number_of_empty_folders.to_string().as_str() + " empty folders which in " + ".\n").as_bytes()) - .unwrap(); - for name in self.empty_folder_list.keys() { - file.write_all((name.clone() + "\n").as_bytes()).unwrap(); - } - } else { - file.write_all(b"Not found any empty folders.").unwrap(); - } - Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); - true - } - /// Clean directory tree /// If directory contains only 2 empty folders, then this directory should be removed instead two empty folders inside because it will produce another empty folder. fn optimize_folders(&mut self) { @@ -157,7 +122,7 @@ impl EmptyFolder { if initial_checking { // Add root folders for finding - for id in &self.included_directories { + for id in &self.directories.included_directories { folders_checked.insert( id.clone(), FolderEntry { @@ -285,17 +250,18 @@ impl EmptyFolder { Common::print_time(start_time, SystemTime::now(), "delete_files".to_string()); } - /// Prints basic info about empty folders // TODO print better - pub fn print_empty_folders(&self) { - if !self.empty_folder_list.is_empty() { - println!("Found {} empty folders", self.empty_folder_list.len()); - } - for i in &self.empty_folder_list { - println!("{}", i.0); - } + /// Set include dir which needs to be relative, exists etc. + pub fn set_included_directory(&mut self, included_directory: String) { + self.directories.set_included_directory(included_directory, &mut self.text_messages); } +} +impl Default for EmptyFolder { + fn default() -> Self { + Self::new() + } +} - /// Debug print +impl DebugPrint for EmptyFolder { #[allow(dead_code)] #[allow(unreachable_code)] fn debug_print(&self) { @@ -307,128 +273,56 @@ impl EmptyFolder { println!("---------------DEBUG PRINT---------------"); println!("Number of all checked folders - {}", self.information.number_of_checked_folders); println!("Number of empty folders - {}", self.information.number_of_empty_folders); - println!("Included directories - {:?}", self.included_directories); + println!("Included directories - {:?}", self.directories.included_directories); println!("-----------------------------------------"); } - - // TODO maybe move this and one from duplicated finder to one common class to avoid duplicating code - /// Optimize include and exclude directories by removing duplicates etc. - fn optimize_directories(&mut self) { +} +impl SaveResults for EmptyFolder { + fn save_results_to_file(&mut self, file_name: &str) -> bool { let start_time: SystemTime = SystemTime::now(); + let file_name: String = match file_name { + "" => "results.txt".to_string(), + k => k.to_string(), + }; - let mut optimized_included: Vec = Vec::::new(); - // Remove duplicated entries like: "/", "/" - - self.included_directories.sort(); - - self.included_directories.dedup(); - - // Optimize for duplicated included directories - "/", "/home". "/home/Pulpit" to "/" - - let mut is_inside: bool; - for id_checked in &self.included_directories { - is_inside = false; - for id_help in &self.included_directories { - if id_checked == id_help { - // We checking same element - continue; - } - if id_checked.starts_with(id_help) { - is_inside = true; - break; - } + let mut file = match File::create(&file_name) { + Ok(t) => t, + Err(_) => { + self.text_messages.errors.push("Failed to create file ".to_string() + file_name.as_str()); + return false; } - if !is_inside { - optimized_included.push(id_checked.to_string()); + }; + + match file.write_all(format!("Results of searching in {:?}\n", self.directories.included_directories).as_bytes()) { + Ok(_) => (), + Err(_) => { + self.text_messages.errors.push("Failed to save results to file ".to_string() + file_name.as_str()); + return false; } } - self.included_directories = optimized_included; - optimized_included = Vec::::new(); - - // Remove non existed directories - for id in &self.included_directories { - let path = Path::new(id); - if path.exists() { - optimized_included.push(id.to_string()); + if !self.empty_folder_list.is_empty() { + file.write_all(b"-------------------------------------------------Empty folder list-------------------------------------------------\n").unwrap(); + file.write_all(("Found ".to_string() + self.information.number_of_empty_folders.to_string().as_str() + " empty folders which in " + ".\n").as_bytes()) + .unwrap(); + for name in self.empty_folder_list.keys() { + file.write_all((name.clone() + "\n").as_bytes()).unwrap(); } + } else { + file.write_all(b"Not found any empty folders.").unwrap(); } - - self.included_directories = optimized_included; - //optimized_included = Vec::::new(); - - if self.included_directories.is_empty() { - println!("Optimize Directories ERROR: Excluded directories overlaps all included directories."); - process::exit(1); - } - - // Not needed, but better is to have sorted everything - self.included_directories.sort(); - Common::print_time(start_time, SystemTime::now(), "optimize_directories".to_string()); - } - - /// Set include dir which needs to be relative, exists etc. - pub fn set_include_directory(&mut self, mut include_directory: String) { - let start_time: SystemTime = SystemTime::now(); - - if include_directory.is_empty() { - println!("At least one directory must be provided"); - } - - include_directory = include_directory.replace("\"", ""); - let directories: Vec = include_directory.split(',').map(String::from).collect(); - let mut checked_directories: Vec = Vec::new(); - - for directory in directories { - let directory: String = directory.trim().to_string(); - - if directory == "" { - continue; - } - if directory == "/" { - println!("Using / is probably not good idea, you may go out of ram."); - } - if directory.contains('*') { - println!("Include Directory ERROR: Wildcards are not supported, ignoring path {}.", directory); - continue; - } - if directory.starts_with('~') { - println!("Include Directory ERROR: ~ in path isn't supported, ignoring path {}.", directory); - continue; - } - if !directory.starts_with('/') { - println!("Include Directory ERROR: Relative path are not supported, ignoring path {}.", directory); - continue; - } - if !Path::new(&directory).exists() { - println!("Include Directory ERROR: Path {} doesn't exists.", directory); - continue; - } - if !Path::new(&directory).is_dir() { - println!("Include Directory ERROR: {} isn't folder.", directory); - continue; - } - - // directory must end with /, due to possiblity of incorrect assumption, that e.g. /home/rafal is top folder to /home/rafalinho - if !directory.ends_with('/') { - checked_directories.push(directory + "/"); - } else { - checked_directories.push(directory); - } - } - - if checked_directories.is_empty() { - println!("Not found even one correct path to include."); - process::exit(1); - } - - self.included_directories = checked_directories; - - Common::print_time(start_time, SystemTime::now(), "set_include_directory".to_string()); + Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); + true } } -impl Default for EmptyFolder { - fn default() -> Self { - Self::new() +impl PrintResults for EmptyFolder { + /// Prints basic info about empty folders // TODO print better + fn print_results(&self) { + if !self.empty_folder_list.is_empty() { + println!("Found {} empty folders", self.empty_folder_list.len()); + } + for i in &self.empty_folder_list { + println!("{}", i.0); + } } } diff --git a/czkawka_core/src/lib.rs b/czkawka_core/src/lib.rs index cac7b4f..ddf7afd 100644 --- a/czkawka_core/src/lib.rs +++ b/czkawka_core/src/lib.rs @@ -3,5 +3,10 @@ pub mod duplicate; pub mod empty_folder; pub mod common; +pub mod common_directory; +pub mod common_extensions; +pub mod common_items; +pub mod common_messages; +pub mod common_traits; pub const CZKAWKA_VERSION: &str = "0.1.3"; diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index 6171cea..4110cf8 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -3,7 +3,7 @@ use humansize::{file_size_opts as options, FileSize}; extern crate gtk; use chrono::NaiveDateTime; -use czkawka_core::common::Messages; +use czkawka_core::common_messages::Messages; use czkawka_core::duplicate::CheckingMethod; use czkawka_core::empty_folder::EmptyFolder; use duplicate::DuplicateFinder; @@ -259,8 +259,8 @@ fn main() { let mut df = DuplicateFinder::new(); let check_method = duplicate::CheckingMethod::Hash; // TODO { - df.set_include_directory(get_string_from_list_store(&scrolled_window_included_directories)); - df.set_exclude_directory(get_string_from_list_store(&scrolled_window_excluded_directories)); + df.set_included_directory(get_string_from_list_store(&scrolled_window_included_directories)); + df.set_excluded_directory(get_string_from_list_store(&scrolled_window_excluded_directories)); df.set_excluded_items(entry_excluded_items.get_text().as_str().to_string()); df.set_allowed_extensions(entry_allowed_extensions.get_text().as_str().to_string()); df.set_min_file_size(match entry_duplicate_minimal_size.get_text().as_str().parse::() { @@ -401,7 +401,7 @@ fn main() { // TODO Change to proper value let mut ef = EmptyFolder::new(); - ef.set_include_directory("/home/rafal/Pulpit".to_string()); + ef.set_included_directory("/home/rafal/Pulpit".to_string()); ef.set_delete_folder(false); ef.find_empty_folders(); diff --git a/czkawka_gui_orbtk/src/main.rs b/czkawka_gui_orbtk/src/main.rs index e2ec8ab..db18c3b 100644 --- a/czkawka_gui_orbtk/src/main.rs +++ b/czkawka_gui_orbtk/src/main.rs @@ -19,7 +19,7 @@ fn main() { .child(TextBlock::new().text("Include Directory:").v_align("center").h_align("start").attach(Grid::column(0)).attach(Grid::row(0)).build(ctx)) .child( TextBox::new() - .id("include_directory") + .id("included_directory") .water_mark("Include Directory") .v_align("center") .h_align("start") @@ -31,7 +31,7 @@ fn main() { .child(TextBlock::new().text("Exclude Directory:").v_align("center").h_align("start").attach(Grid::column(0)).attach(Grid::row(1)).build(ctx)) .child( TextBox::new() - .id("exclude_directory") + .id("excluded_directory") .water_mark("Exclude Directory") .v_align("center") .h_align("start")