From acfecd7ca3116b8a85a693bed13f0744ecf205bf Mon Sep 17 00:00:00 2001 From: Dariusz Niedoba Date: Wed, 14 Oct 2020 18:41:37 +0200 Subject: [PATCH] Replace String with PathBuf for paths (#59) --- czkawka_core/src/big_file.rs | 104 +++++++------------ czkawka_core/src/common.rs | 35 +++++-- czkawka_core/src/common_directory.rs | 135 +++++++++++-------------- czkawka_core/src/common_items.rs | 14 +++ czkawka_core/src/duplicate.rs | 144 +++++++++++---------------- czkawka_core/src/empty_files.rs | 93 ++++++----------- czkawka_core/src/empty_folder.rs | 59 ++++------- czkawka_core/src/temporary.rs | 99 ++++++------------ czkawka_gui/src/main.rs | 49 ++++----- 9 files changed, 297 insertions(+), 435 deletions(-) diff --git a/czkawka_core/src/big_file.rs b/czkawka_core/src/big_file.rs index b3f5915..5059f2e 100644 --- a/czkawka_core/src/big_file.rs +++ b/czkawka_core/src/big_file.rs @@ -7,14 +7,16 @@ use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; use crossbeam_channel::Receiver; use humansize::{file_size_opts as options, FileSize}; use std::collections::BTreeMap; +use std::ffi::OsStr; use std::fs; use std::fs::{File, Metadata}; use std::io::Write; +use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Clone)] pub struct FileEntry { - pub path: String, + pub path: PathBuf, pub size: u64, pub modified_date: u64, } @@ -99,26 +101,23 @@ impl BigFile { fn look_for_big_files(&mut self, rx: Option<&Receiver<()>>) -> bool { let start_time: SystemTime = SystemTime::now(); - 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 + 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.directories.included_directories { - folders_to_check.push(id.to_string()); + folders_to_check.push(id.clone()); } self.information.number_of_checked_folders += folders_to_check.len(); - let mut current_folder: String; - let mut next_folder: String; while !folders_to_check.is_empty() { if rx.is_some() && rx.unwrap().try_recv().is_ok() { return false; } - current_folder = folders_to_check.pop().unwrap(); - + let current_folder = folders_to_check.pop().unwrap(); let read_dir = match fs::read_dir(¤t_folder) { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot open dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display())); continue; } // Permissions denied }; @@ -126,14 +125,14 @@ impl BigFile { let entry_data = match entry { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read entry in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display())); continue; } //Permissions denied }; let metadata: Metadata = match entry_data.metadata() { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read metadata in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display())); continue; } //Permissions denied }; @@ -144,60 +143,30 @@ impl BigFile { continue; } - next_folder = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - } - + "/"; + let next_folder = current_folder.join(entry_data.file_name()); + if self.directories.is_excluded(&next_folder) || self.excluded_items.is_excluded(&next_folder) { + continue 'dir; + } - for ed in &self.directories.excluded_directories { - if next_folder == *ed { - continue 'dir; - } - } - for expression in &self.excluded_items.items { - if Common::regex_check(expression, &next_folder) { - continue 'dir; - } - } folders_to_check.push(next_folder); } else if metadata.is_file() { - let file_name_lowercase: String = match entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - } - .to_lowercase(); + // Extracting file extension + let file_extension = entry_data.path().extension().and_then(OsStr::to_str).map(str::to_lowercase); // Checking allowed extensions if !self.allowed_extensions.file_extensions.is_empty() { - let allowed = self.allowed_extensions.file_extensions.iter().any(|e| file_name_lowercase.ends_with((".".to_string() + e.to_lowercase().as_str()).as_str())); + let allowed = self.allowed_extensions.file_extensions.iter().map(|e| e.to_lowercase()).any(|e| file_extension == Some(e)); if !allowed { // Not an allowed extension, ignore it. self.information.number_of_ignored_files += 1; continue 'dir; } } - // Checking files - #[allow(unused_mut)] // Used is later by Windows build - let mut current_file_name = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - }; // Checking expressions - for expression in &self.excluded_items.items { - if Common::regex_check(expression, ¤t_file_name) { - continue 'dir; - } - } - - #[cfg(target_family = "windows")] - { - current_file_name = Common::prettier_windows_path(¤t_file_name); + let current_file_name = current_folder.join(entry_data.file_name()); + if self.excluded_items.is_excluded(¤t_file_name) { + continue 'dir; } // Creating new file entry @@ -208,12 +177,12 @@ impl BigFile { Ok(t) => match t.duration_since(UNIX_EPOCH) { Ok(d) => d.as_secs(), Err(_) => { - self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name)); + self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display())); 0 } }, Err(_) => { - self.text_messages.warnings.push("Unable to get modification date from file ".to_string() + current_file_name.as_str()); + self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display())); continue; } // Permissions Denied }, @@ -318,6 +287,7 @@ impl DebugPrint for BigFile { println!("-----------------------------------------"); } } + impl SaveResults for BigFile { /// Saving results to provided file fn save_results_to_file(&mut self, file_name: &str) -> bool { @@ -335,35 +305,33 @@ impl SaveResults for BigFile { } }; - 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 writeln!( + file, + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items + ) + .is_err() + { + self.text_messages.errors.push(format!("Failed to save results to file {}", file_name)); + 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(); + write!(file, "{} the biggest files.\n\n", self.information.number_of_real_files).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(); + writeln!(file, "{} ({}) - {}", size.file_size(options::BINARY).unwrap(), size, file_entry.path.display()).unwrap(); } } } else { - file.write_all(b"Not found any empty folders.").unwrap(); + write!(file, "Not found any empty folders.").unwrap(); } Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); true } } + impl PrintResults for BigFile { fn print_results(&self) { let start_time: SystemTime = SystemTime::now(); @@ -371,7 +339,7 @@ impl PrintResults for BigFile { for (size, vector) in self.big_files.iter().rev() { // TODO Align all to same width for entry in vector { - println!("{} ({} bytes) - {}", size.file_size(options::BINARY).unwrap(), size, entry.path); + println!("{} ({} bytes) - {}", size.file_size(options::BINARY).unwrap(), size, entry.path.display()); } } Common::print_time(start_time, SystemTime::now(), "print_entries".to_string()); diff --git a/czkawka_core/src/common.rs b/czkawka_core/src/common.rs index 275ffa7..fe2469e 100644 --- a/czkawka_core/src/common.rs +++ b/czkawka_core/src/common.rs @@ -1,5 +1,6 @@ +use std::ffi::OsString; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::SystemTime; /// Class for common functions used across other class/functions @@ -50,7 +51,7 @@ impl Common { } /// Function to check if directory match expression - pub fn regex_check(expression: &str, directory: &str) -> bool { + pub fn regex_check(expression: &str, directory: impl AsRef) -> bool { if !expression.contains('*') { #[cfg(debug_assertions)] { @@ -70,6 +71,9 @@ impl Common { return false; } + // Get rid of non unicode characters + let directory = directory.as_ref().to_string_lossy(); + // Early checking if directory contains all parts needed by expression for split in &splits { if !directory.contains(split) { @@ -102,15 +106,30 @@ impl Common { } true } - #[allow(clippy::ptr_arg)] - pub fn prettier_windows_path(path_to_change: &String) -> String { - path_to_change[..1].to_uppercase() + path_to_change[1..].to_lowercase().replace("\\", "/").as_str() + + pub fn normalize_windows_path(path_to_change: impl AsRef) -> PathBuf { + let path = path_to_change.as_ref(); + match path.to_str() { + Some(path) if path.is_char_boundary(1) => { + let replaced = path.replace('\\', "/"); + let mut new_path = OsString::new(); + if replaced[1..].starts_with(':') { + new_path.push(replaced[..1].to_ascii_uppercase()); + new_path.push(replaced[1..].to_ascii_lowercase()); + } else { + new_path.push(replaced.to_ascii_lowercase()); + } + PathBuf::from(new_path) + } + _ => path.to_path_buf(), + } } } #[cfg(test)] mod test { use crate::common::Common; + use std::path::PathBuf; #[test] fn test_regex() { @@ -134,8 +153,8 @@ mod test { } #[test] fn test_windows_path() { - assert_eq!("C:/path.txt", Common::prettier_windows_path(&"c:/PATH.tXt".to_string())); - assert_eq!("H:/reka/weza/roman.txt", Common::prettier_windows_path(&"h:/RekA/Weza\\roMan.Txt".to_string())); - assert_eq!("T:/a", Common::prettier_windows_path(&"T:\\A".to_string())); + assert_eq!(PathBuf::from("C:/path.txt"), Common::normalize_windows_path("c:/PATH.tXt")); + assert_eq!(PathBuf::from("H:/reka/weza/roman.txt"), Common::normalize_windows_path("h:/RekA/Weza\\roMan.Txt")); + assert_eq!(PathBuf::from("T:/a"), Common::normalize_windows_path("T:\\A")); } } diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index 618d88e..56a0e31 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -1,12 +1,12 @@ use crate::common::Common; use crate::common_messages::Messages; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::SystemTime; #[derive(Default)] pub struct Directories { - pub excluded_directories: Vec, - pub included_directories: Vec, + pub excluded_directories: Vec, + pub included_directories: Vec, } impl Directories { pub fn new() -> Self { @@ -14,7 +14,7 @@ impl Directories { } /// Setting included directories, at least one must be provided - pub fn set_included_directory(&mut self, mut included_directory: String, text_messages: &mut Messages) -> bool { + pub fn set_included_directory(&mut self, included_directory: String, text_messages: &mut Messages) -> bool { let start_time: SystemTime = SystemTime::now(); if included_directory.is_empty() { @@ -22,45 +22,28 @@ impl Directories { return false; } - included_directory = included_directory.replace("\"", ""); - let directories: Vec = included_directory.split(',').map(String::from).collect(); - let mut checked_directories: Vec = Vec::new(); + let included_directory = included_directory.replace("\"", ""); + let directories: Vec<_> = included_directory.split(',').map(|dir| dir.trim()).filter(|dir| !dir.is_empty()).map(PathBuf::from).collect(); + let mut checked_directories: Vec = Vec::new(); for directory in directories { - let directory: String = directory.trim().to_string(); - - if directory == "" { + if directory.to_string_lossy().contains('*') { + text_messages.warnings.push(format!("Included Directory Warning: Wildcards in path are not supported, ignoring {}", directory.display())); continue; } - if directory.contains('*') { - text_messages.warnings.push("Included Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); + if directory.is_relative() { + text_messages.warnings.push(format!("Included Directory Warning: Relative path are not supported, ignoring {}", directory.display())); continue; } - #[cfg(target_family = "unix")] - if !directory.starts_with('/') { - text_messages.warnings.push("Included Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); + if !directory.exists() { + text_messages.warnings.push(format!("Included Directory Warning: Provided folder path must exits, ignoring {}", directory.display())); continue; } - #[cfg(target_family = "windows")] - if !(directory[..directory.len()].starts_with(":/") || !directory[..directory.len()].starts_with(":\\")) { - text_messages.warnings.push("Included Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); + if !directory.is_dir() { + text_messages.warnings.push(format!("Included Directory Warning: Provided path must point at the directory, ignoring {}", directory.display())); continue; } - if !Path::new(&directory).exists() { - text_messages.warnings.push("Included Directory Warning: Provided folder path must exits, ignoring ".to_string() + directory.as_str()); - continue; - } - if !Path::new(&directory).is_dir() { - text_messages.warnings.push("Included 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); - } + checked_directories.push(directory); } if checked_directories.is_empty() { @@ -75,51 +58,40 @@ impl Directories { } /// Setting absolute path to exclude - pub fn set_excluded_directory(&mut self, mut excluded_directory: String, text_messages: &mut Messages) { + pub fn set_excluded_directory(&mut self, 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(); + let excluded_directory = excluded_directory.replace("\"", ""); + let directories: Vec = excluded_directory.split(',').map(|dir| dir.trim()).filter(|dir| !dir.is_empty()).map(PathBuf::from).collect(); + + let mut checked_directories: Vec = Vec::new(); for directory in directories { - let directory: String = directory.trim().to_string().replace("\\", "/"); - - if directory == "" { - continue; - } - if directory == "/" { + let directory_as_string = directory.to_string_lossy(); + if directory_as_string == "/" { text_messages.errors.push("Excluded Directory ERROR: Excluding / is pointless, because it means that no files will be scanned.".to_string()); break; } - if directory.contains('*') { - text_messages.warnings.push("Excluded Directory Warning: Wildcards in path are not supported, ignoring ".to_string() + directory.as_str()); + if directory_as_string.contains('*') { + text_messages.warnings.push(format!("Excluded Directory Warning: Wildcards in path are not supported, ignoring {}", directory.display())); continue; } - #[cfg(target_family = "unix")] - if !directory.starts_with('/') { - text_messages.warnings.push("Excluded Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); + if directory.is_relative() { + text_messages.warnings.push(format!("Excluded Directory Warning: Relative path are not supported, ignoring {}", directory.display())); continue; } - #[cfg(target_family = "windows")] - if !(directory[..directory.len()].starts_with(":/") || !directory[..directory.len()].starts_with(":\\")) { - text_messages.warnings.push("Excluded Directory Warning: Relative path are not supported, ignoring ".to_string() + directory.as_str()); + if !directory.exists() { + text_messages.warnings.push(format!("Excluded Directory Warning: Provided folder path must exits, ignoring {}", directory.display())); continue; } - if !Path::new(&directory).is_dir() { - text_messages.warnings.push("Excluded Directory Warning: Provided path must point at the directory, ignoring ".to_string() + directory.as_str()); + if !directory.is_dir() { + text_messages.warnings.push(format!("Excluded Directory Warning: Provided path must point at the directory, ignoring {}", directory.display())); 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()); - } + checked_directories.push(directory); } self.excluded_directories = checked_directories; @@ -130,14 +102,12 @@ impl Directories { 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(); + let mut optimized_included: Vec = Vec::new(); + let mut optimized_excluded: Vec = Vec::new(); - // Windows(or specific EXT4 extension) doesn't recognize size of letters so we must remove one of directory e.g. - C:/h.txt, C:/H.txt - #[cfg(target_family = "windows")] - { - self.included_directories = self.included_directories.iter().map(Common::prettier_windows_path).collect(); - self.excluded_directories = self.excluded_directories.iter().map(Common::prettier_windows_path).collect(); + if cfg!(target_family = "windows") { + self.included_directories = self.included_directories.iter().map(Common::normalize_windows_path).collect(); + self.excluded_directories = self.excluded_directories.iter().map(Common::normalize_windows_path).collect(); } // Remove duplicated entries like: "/", "/" @@ -165,7 +135,7 @@ impl Directories { } } if !is_inside { - optimized_excluded.push(ed_checked.to_string()); + optimized_excluded.push(ed_checked.clone()); } } @@ -182,14 +152,14 @@ impl Directories { } } if !is_inside { - optimized_included.push(id_checked.to_string()); + optimized_included.push(id_checked.clone()); } } self.included_directories = optimized_included; - optimized_included = Vec::::new(); + optimized_included = Vec::new(); self.excluded_directories = optimized_excluded; - optimized_excluded = Vec::::new(); + optimized_excluded = Vec::new(); } // Remove included directories which are inside any excluded directory @@ -202,31 +172,30 @@ impl Directories { } } if !is_inside { - optimized_included.push(id.to_string()); + optimized_included.push(id.clone()); } } self.included_directories = optimized_included; - optimized_included = Vec::::new(); + 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()); + optimized_included.push(id.clone()); } } for ed in &self.excluded_directories { let path = Path::new(ed); if path.exists() { - optimized_excluded.push(ed.to_string()); + optimized_excluded.push(ed.clone()); } } self.included_directories = optimized_included; - // optimized_included = Vec::::new(); self.excluded_directories = optimized_excluded; - optimized_excluded = Vec::::new(); + optimized_excluded = Vec::new(); // Excluded paths must are inside included path, because for ed in &self.excluded_directories { @@ -238,12 +207,11 @@ impl Directories { } } if is_inside { - optimized_excluded.push(ed.to_string()); + optimized_excluded.push(ed.clone()); } } 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()); @@ -256,4 +224,13 @@ impl Directories { Common::print_time(start_time, SystemTime::now(), "optimize_directories".to_string()); true } + + /// Checks whether a specified directory is excluded from searching + pub fn is_excluded(&self, path: impl AsRef) -> bool { + let path = path.as_ref(); + #[cfg(target_family = "windows")] + let path = Common::normalize_windows_path(path); + // We're assuming that `excluded_directories` are already normalized + self.excluded_directories.iter().any(|p| p.as_path() == path) + } } diff --git a/czkawka_core/src/common_items.rs b/czkawka_core/src/common_items.rs index 4c92c1f..1d946f4 100644 --- a/czkawka_core/src/common_items.rs +++ b/czkawka_core/src/common_items.rs @@ -1,5 +1,6 @@ use crate::common::Common; use crate::common_messages::Messages; +use std::path::Path; use std::time::SystemTime; #[derive(Default)] @@ -45,4 +46,17 @@ impl ExcludedItems { self.items = checked_expressions; Common::print_time(start_time, SystemTime::now(), "set_excluded_items".to_string()); } + + /// Checks whether a specified path is excluded from searching + pub fn is_excluded(&self, path: impl AsRef) -> bool { + #[cfg(target_family = "windows")] + let path = Common::normalize_windows_path(path); + + for expression in &self.items { + if Common::regex_check(expression, &path) { + return true; + } + } + false + } } diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index 3c37943..1cc4b39 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -4,6 +4,7 @@ use std::collections::{BTreeMap, HashMap}; use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; +use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; use crate::common::Common; @@ -34,7 +35,7 @@ pub enum DeleteMethod { #[derive(Clone)] pub struct FileEntry { - pub path: String, + pub path: PathBuf, pub size: u64, pub modified_date: u64, } @@ -178,27 +179,25 @@ impl DuplicateFinder { fn check_files_size(&mut self, rx: Option<&Receiver<()>>) -> bool { // TODO maybe add multithreading checking for file hash let start_time: SystemTime = SystemTime::now(); - 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 + 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.directories.included_directories { - folders_to_check.push(id.to_string()); + folders_to_check.push(id.clone()); } self.information.number_of_checked_folders += folders_to_check.len(); - let mut current_folder: String; - let mut next_folder: String; while !folders_to_check.is_empty() { if rx.is_some() && rx.unwrap().try_recv().is_ok() { return false; } - current_folder = folders_to_check.pop().unwrap(); + let current_folder = folders_to_check.pop().unwrap(); // Read current dir, if permission are denied just go to next let read_dir = match fs::read_dir(¤t_folder) { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot open dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display())); continue; } // Permissions denied }; @@ -208,14 +207,14 @@ impl DuplicateFinder { let entry_data = match entry { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read entry in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display())); continue; } //Permissions denied }; let metadata: Metadata = match entry_data.metadata() { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read metadata in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display())); continue; } //Permissions denied }; @@ -226,24 +225,15 @@ impl DuplicateFinder { continue; } - next_folder = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - } - + "/"; + let next_folder = current_folder.join(entry_data.file_name()); + if self.directories.is_excluded(&next_folder) { + continue 'dir; + } - for ed in &self.directories.excluded_directories { - if next_folder == *ed { - continue 'dir; - } - } - for expression in &self.excluded_items.items { - if Common::regex_check(expression, &next_folder) { - continue 'dir; - } + if self.excluded_items.is_excluded(&next_folder) { + continue 'dir; } + folders_to_check.push(next_folder); } else if metadata.is_file() { // let mut have_valid_extension: bool; @@ -264,24 +254,9 @@ impl DuplicateFinder { } // Checking files if metadata.len() >= self.minimal_file_size { - #[allow(unused_mut)] // Used is later by Windows build - let mut current_file_name = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - }; - - // Checking expressions - for expression in &self.excluded_items.items { - if Common::regex_check(expression, ¤t_file_name) { - continue 'dir; - } - } - - #[cfg(target_family = "windows")] - { - current_file_name = Common::prettier_windows_path(¤t_file_name); + let current_file_name = current_folder.join(entry_data.file_name()); + if self.excluded_items.is_excluded(¤t_file_name) { + continue 'dir; } // Creating new file entry @@ -292,12 +267,12 @@ impl DuplicateFinder { Ok(t) => match t.duration_since(UNIX_EPOCH) { Ok(d) => d.as_secs(), Err(_) => { - self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name)); + self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display())); 0 } }, Err(_) => { - self.text_messages.warnings.push("Unable to get modification date from file ".to_string() + current_file_name.as_str()); + self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display())); continue; } // Permissions Denied }, @@ -354,7 +329,7 @@ impl DuplicateFinder { file_handler = match File::open(&file_entry.path) { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Unable to check hash of file ".to_string() + file_entry.path.as_str()); + self.text_messages.warnings.push(format!("Unable to check hash of file {}", file_entry.path.display())); continue; } }; @@ -368,7 +343,7 @@ impl DuplicateFinder { let n = match file_handler.read(&mut buffer) { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Error happened when checking hash of file ".to_string() + file_entry.path.as_str()); + self.text_messages.warnings.push(format!("Error happened when checking hash of file {}", file_entry.path.display())); error_reading_file = true; break; } @@ -523,62 +498,55 @@ impl SaveResults for DuplicateFinder { } }; - 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(format!("Failed to save results to file {}", file_name)); - return false; - } + if writeln!( + file, + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items + ) + .is_err() + { + self.text_messages.errors.push(format!("Failed to save results to file {}", file_name)); + return false; } if !self.files_with_identical_size.is_empty() { - file.write_all(b"-------------------------------------------------Files with same size-------------------------------------------------\n").unwrap(); - file.write_all( - format!( - "Found {} duplicated files which in {} groups which takes {}.\n", - self.information.number_of_duplicated_files_by_size, - self.information.number_of_groups_by_size, - self.information.lost_space_by_size.file_size(options::BINARY).unwrap() - ) - .as_bytes(), + writeln!(file, "-------------------------------------------------Files with same size-------------------------------------------------").unwrap(); + writeln!( + file, + "Found {} duplicated files which in {} groups which takes {}.", + self.information.number_of_duplicated_files_by_size, + self.information.number_of_groups_by_size, + self.information.lost_space_by_size.file_size(options::BINARY).unwrap() ) .unwrap(); for (size, vector) in self.files_with_identical_size.iter().rev() { - file.write_all(format!("\n---- Size {} ({}) - {} files \n", size.file_size(options::BINARY).unwrap(), size, vector.len()).as_bytes()).unwrap(); + write!(file, "\n---- Size {} ({}) - {} files \n", size.file_size(options::BINARY).unwrap(), size, vector.len()).unwrap(); for file_entry in vector { - file.write_all(format!("{} \n", file_entry.path).as_bytes()).unwrap(); + writeln!(file, "{}", file_entry.path.display()).unwrap(); } } if !self.files_with_identical_hashes.is_empty() { - file.write_all(b"-------------------------------------------------Files with same hashes-------------------------------------------------\n").unwrap(); - file.write_all( - format!( - "Found {} duplicated files which in {} groups which takes {}.\n", - self.information.number_of_duplicated_files_by_hash, - self.information.number_of_groups_by_hash, - self.information.lost_space_by_hash.file_size(options::BINARY).unwrap() - ) - .as_bytes(), + writeln!(file, "-------------------------------------------------Files with same hashes-------------------------------------------------").unwrap(); + writeln!( + file, + "Found {} duplicated files which in {} groups which takes {}.", + self.information.number_of_duplicated_files_by_hash, + self.information.number_of_groups_by_hash, + self.information.lost_space_by_hash.file_size(options::BINARY).unwrap() ) .unwrap(); for (size, vectors_vector) in self.files_with_identical_hashes.iter().rev() { for vector in vectors_vector { - file.write_all(format!("\n---- Size {} ({}) - {} files \n", size.file_size(options::BINARY).unwrap(), size, vector.len()).as_bytes()).unwrap(); + writeln!(file, "\n---- Size {} ({}) - {} files", size.file_size(options::BINARY).unwrap(), size, vector.len()).unwrap(); for file_entry in vector { - file.write_all(format!("{} \n", file_entry.path).as_bytes()).unwrap(); + writeln!(file, "{}", file_entry.path.display()).unwrap(); } } } } } else { - file.write_all(b"Not found any duplicates.").unwrap(); + write!(file, "Not found any duplicates.").unwrap(); } Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); true @@ -610,7 +578,7 @@ impl PrintResults for DuplicateFinder { for j in vector { println!("Size - {} ({}) - {} files ", size.file_size(options::BINARY).unwrap(), size, j.len()); for k in j { - println!("{}", k.path); + println!("{}", k.path.display()); } println!("----"); } @@ -631,7 +599,7 @@ impl PrintResults for DuplicateFinder { for (size, vector) in &self.files_with_identical_size { println!("Size - {} ({}) - {} files ", size.file_size(options::BINARY).unwrap(), size, vector.len()); for j in vector { - println!("{}", j.path); + println!("{}", j.path.display()); } println!(); } @@ -670,7 +638,7 @@ fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, warnings: &m } Err(_) => { failed_to_remove_files += 1; - warnings.push("Failed to delete".to_string() + vector[q_index].path.as_str()); + warnings.push(format!("Failed to delete {}", vector[q_index].path.display())); } }; } @@ -688,7 +656,7 @@ fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, warnings: &m } Err(_) => { failed_to_remove_files += 1; - warnings.push("Failed to delete".to_string() + vector[q_index].path.as_str()); + warnings.push(format!("Failed to delete {}", vector[q_index].path.display())); } }; } @@ -708,7 +676,7 @@ fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, warnings: &m } Err(_) => { failed_to_remove_files += 1; - warnings.push("Failed to delete".to_string() + file.path.as_str()); + warnings.push(format!("Failed to delete {}", file.path.display())); } }; } @@ -730,7 +698,7 @@ fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, warnings: &m } Err(_) => { failed_to_remove_files += 1; - warnings.push("Failed to delete".to_string() + file.path.as_str()); + warnings.push(format!("Failed to delete {}", file.path.display())); } }; } diff --git a/czkawka_core/src/empty_files.rs b/czkawka_core/src/empty_files.rs index fa6c678..3103cf7 100644 --- a/czkawka_core/src/empty_files.rs +++ b/czkawka_core/src/empty_files.rs @@ -1,6 +1,7 @@ use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; +use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; use crate::common::Common; @@ -19,7 +20,7 @@ pub enum DeleteMethod { #[derive(Clone)] pub struct FileEntry { - pub path: String, + pub path: PathBuf, pub modified_date: u64, } @@ -121,27 +122,25 @@ impl EmptyFiles { /// Check files for any with size == 0 fn check_files(&mut self, rx: Option<&Receiver<()>>) -> bool { let start_time: SystemTime = SystemTime::now(); - 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 + 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.directories.included_directories { - folders_to_check.push(id.to_string()); + folders_to_check.push(id.clone()); } self.information.number_of_checked_folders += folders_to_check.len(); - let mut current_folder: String; - let mut next_folder: String; while !folders_to_check.is_empty() { if rx.is_some() && rx.unwrap().try_recv().is_ok() { return false; } - current_folder = folders_to_check.pop().unwrap(); + let current_folder = folders_to_check.pop().unwrap(); // Read current dir, if permission are denied just go to next let read_dir = match fs::read_dir(¤t_folder) { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot open dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display())); continue; } // Permissions denied }; @@ -151,14 +150,14 @@ impl EmptyFiles { let entry_data = match entry { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read entry in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display())); continue; } //Permissions denied }; let metadata: Metadata = match entry_data.metadata() { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read metadata in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display())); continue; } //Permissions denied }; @@ -169,24 +168,11 @@ impl EmptyFiles { continue; } - next_folder = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - } - + "/"; + let next_folder = current_folder.join(entry_data.file_name()); + if self.directories.is_excluded(&next_folder) || self.excluded_items.is_excluded(&next_folder) { + continue 'dir; + } - for ed in &self.directories.excluded_directories { - if next_folder == *ed { - continue 'dir; - } - } - for expression in &self.excluded_items.items { - if Common::regex_check(expression, &next_folder) { - continue 'dir; - } - } folders_to_check.push(next_folder); } else if metadata.is_file() { let file_name_lowercase: String = match entry_data.file_name().into_string() { @@ -206,23 +192,9 @@ impl EmptyFiles { } // Checking files if metadata.len() == 0 { - #[allow(unused_mut)] // Used is later by Windows build - let mut current_file_name = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - }; - - // Checking expressions - for expression in &self.excluded_items.items { - if Common::regex_check(expression, ¤t_file_name) { - continue 'dir; - } - } - #[cfg(target_family = "windows")] - { - current_file_name = Common::prettier_windows_path(¤t_file_name); + let current_file_name = current_folder.join(entry_data.file_name()); + if self.excluded_items.is_excluded(¤t_file_name) { + continue 'dir; } // Creating new file entry @@ -232,12 +204,12 @@ impl EmptyFiles { Ok(t) => match t.duration_since(UNIX_EPOCH) { Ok(d) => d.as_secs(), Err(_) => { - self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name)); + self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display())); 0 } }, Err(_) => { - self.text_messages.warnings.push("Unable to get modification date from file ".to_string() + current_file_name.as_str()); + self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display())); continue; } // Permissions Denied }, @@ -270,7 +242,7 @@ impl EmptyFiles { DeleteMethod::Delete => { for file_entry in &self.empty_files { if fs::remove_file(file_entry.path.clone()).is_err() { - self.text_messages.warnings.push(file_entry.path.clone()); + self.text_messages.warnings.push(file_entry.path.display().to_string()); } } } @@ -338,27 +310,24 @@ impl SaveResults for EmptyFiles { } }; - 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(format!("Failed to save results to file {}", file_name)); - return false; - } + if writeln!( + file, + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items + ) + .is_err() + { + self.text_messages.errors.push(format!("Failed to save results to file {}", file_name)); + return false; } if !self.empty_files.is_empty() { - file.write_all(format!("Found {} empty files.\n", self.information.number_of_empty_files).as_bytes()).unwrap(); + writeln!(file, "Found {} empty files.", self.information.number_of_empty_files).unwrap(); for file_entry in self.empty_files.iter() { - file.write_all(format!("{} \n", file_entry.path).as_bytes()).unwrap(); + writeln!(file, "{}", file_entry.path.display()).unwrap(); } } else { - file.write_all(b"Not found any empty files.").unwrap(); + write!(file, "Not found any empty files.").unwrap(); } Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); true @@ -371,7 +340,7 @@ impl PrintResults for EmptyFiles { let start_time: SystemTime = SystemTime::now(); println!("Found {} empty files.\n", self.information.number_of_empty_files); for file_entry in self.empty_files.iter() { - println!("{}", file_entry.path); + println!("{}", file_entry.path.display()); } Common::print_time(start_time, SystemTime::now(), "print_entries".to_string()); diff --git a/czkawka_core/src/empty_folder.rs b/czkawka_core/src/empty_folder.rs index e303253..61c7d5f 100644 --- a/czkawka_core/src/empty_folder.rs +++ b/czkawka_core/src/empty_folder.rs @@ -7,6 +7,7 @@ use std::collections::BTreeMap; use std::fs; use std::fs::{File, Metadata}; use std::io::Write; +use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; /// Enum with values which show if folder is empty. @@ -20,7 +21,7 @@ enum FolderEmptiness { /// Struct assigned to each checked folder with parent path(used to ignore parent if children are not empty) and flag which shows if folder is empty #[derive(Clone)] pub struct FolderEntry { - parent_path: Option, // Usable only when finding + parent_path: Option, // Usable only when finding is_empty: FolderEmptiness, pub modified_date: u64, } @@ -30,7 +31,7 @@ pub struct EmptyFolder { information: Info, delete_folders: bool, text_messages: Messages, - empty_folder_list: BTreeMap, // Path, FolderEntry + empty_folder_list: BTreeMap, // Path, FolderEntry directories: Directories, stopped_search: bool, } @@ -65,7 +66,7 @@ impl EmptyFolder { self.stopped_search } - pub const fn get_empty_folder_list(&self) -> &BTreeMap { + pub const fn get_empty_folder_list(&self) -> &BTreeMap { &self.empty_folder_list } @@ -97,7 +98,7 @@ impl EmptyFolder { /// 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) { - let mut new_directory_folders: BTreeMap = Default::default(); + let mut new_directory_folders: BTreeMap = Default::default(); for (name, folder_entry) in &self.empty_folder_list { match &folder_entry.parent_path { @@ -119,8 +120,8 @@ impl EmptyFolder { /// Parameter initial_checking for second check before deleting to be sure that checked folder is still empty fn check_for_empty_folders(&mut self, rx: Option<&Receiver<()>>) -> bool { let start_time: SystemTime = SystemTime::now(); - 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 - let mut folders_checked: BTreeMap = Default::default(); + 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 + let mut folders_checked: BTreeMap = Default::default(); // Add root folders for finding for id in &self.directories.included_directories { @@ -135,14 +136,12 @@ impl EmptyFolder { folders_to_check.push(id.clone()); } - let mut current_folder: String; - let mut next_folder: String; while !folders_to_check.is_empty() { if rx.is_some() && rx.unwrap().try_recv().is_ok() { return false; } self.information.number_of_checked_folders += 1; - current_folder = folders_to_check.pop().unwrap(); + let current_folder = folders_to_check.pop().unwrap(); // Checked folder may be deleted or we may not have permissions to open it so we assume that this folder is not be empty let read_dir = match fs::read_dir(¤t_folder) { Ok(t) => t, @@ -163,15 +162,8 @@ impl EmptyFolder { }; // If child is dir, still folder may be considered as empty if all children are only directories. if metadata.is_dir() { - next_folder = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - } - + "/"; + let next_folder = current_folder.join(entry_data.file_name()); folders_to_check.push(next_folder.clone()); - folders_checked.insert( next_folder.clone(), FolderEntry { @@ -181,12 +173,12 @@ impl EmptyFolder { Ok(t) => match t.duration_since(UNIX_EPOCH) { Ok(d) => d.as_secs(), Err(_) => { - self.text_messages.warnings.push(format!("Folder {} seems to be modified before Unix Epoch.", current_folder)); + self.text_messages.warnings.push(format!("Folder {} seems to be modified before Unix Epoch.", current_folder.display())); 0 } }, Err(_) => { - self.text_messages.warnings.push(format!("Failed to read modification date of folder {}", current_folder)); + self.text_messages.warnings.push(format!("Failed to read modification date of folder {}", current_folder.display())); continue; } }, @@ -196,12 +188,11 @@ impl EmptyFolder { // 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(); - let mut cf: String; // Loop to recursively set as non empty this and all his parent folders loop { d.is_empty = FolderEmptiness::No; if d.parent_path != None { - cf = d.parent_path.clone().unwrap(); + let cf = d.parent_path.clone().unwrap(); d = folders_checked.get_mut(&cf).unwrap(); } else { break; @@ -215,11 +206,6 @@ impl EmptyFolder { #[allow(unused_mut)] // Used is later by Windows build for (mut name, folder_entry) in folders_checked { if folder_entry.is_empty != FolderEmptiness::No { - #[cfg(target_family = "windows")] - { - name = Common::prettier_windows_path(&name); - } - self.empty_folder_list.insert(name, folder_entry); } } @@ -235,7 +221,7 @@ impl EmptyFolder { for name in self.empty_folder_list.keys() { match fs::remove_dir_all(name) { Ok(_) => (), - Err(_) => self.text_messages.warnings.push(format!("Failed to remove folder {}", name)), + Err(_) => self.text_messages.warnings.push(format!("Failed to remove folder {}", name.display())), }; } @@ -285,22 +271,19 @@ impl SaveResults for EmptyFolder { } }; - match file.write_all(format!("Results of searching {:?} with excluded directories {:?}\n", self.directories.included_directories, self.directories.excluded_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 writeln!(file, "Results of searching {:?} with excluded directories {:?}", self.directories.included_directories, self.directories.excluded_directories).is_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\n").as_bytes()).unwrap(); + writeln!(file, "-------------------------------------------------Empty folder list-------------------------------------------------").unwrap(); + writeln!(file, "Found {} empty folders", self.information.number_of_empty_folders).unwrap(); for name in self.empty_folder_list.keys() { - file.write_all((name.clone() + "\n").as_bytes()).unwrap(); + writeln!(file, "{}", name.display()).unwrap(); } } else { - file.write_all(b"Not found any empty folders.").unwrap(); + write!(file, "Not found any empty folders.").unwrap(); } Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); true @@ -313,7 +296,7 @@ impl PrintResults for EmptyFolder { println!("Found {} empty folders", self.empty_folder_list.len()); } for name in self.empty_folder_list.keys() { - println!("{}", name); + println!("{}", name.display()); } } } diff --git a/czkawka_core/src/temporary.rs b/czkawka_core/src/temporary.rs index 0385425..84cefc6 100644 --- a/czkawka_core/src/temporary.rs +++ b/czkawka_core/src/temporary.rs @@ -1,6 +1,7 @@ use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; +use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; use crate::common::Common; @@ -18,7 +19,7 @@ pub enum DeleteMethod { #[derive(Clone)] pub struct FileEntry { - pub path: String, + pub path: PathBuf, pub modified_date: u64, } @@ -112,31 +113,25 @@ impl Temporary { fn check_files(&mut self, rx: Option<&Receiver<()>>) -> bool { let start_time: SystemTime = SystemTime::now(); - 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 + 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.directories.included_directories { - folders_to_check.push(id.to_string()); + folders_to_check.push(id.clone()); } self.information.number_of_checked_folders += folders_to_check.len(); - let mut current_folder: String; - let mut next_folder: String; while !folders_to_check.is_empty() { if rx.is_some() && rx.unwrap().try_recv().is_ok() { return false; } - current_folder = folders_to_check.pop().unwrap(); - #[cfg(target_family = "windows")] - { - current_folder = Common::prettier_windows_path(¤t_folder); - } + let current_folder = folders_to_check.pop().unwrap(); // Read current dir, if permission are denied just go to next let read_dir = match fs::read_dir(¤t_folder) { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot open dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display())); continue; } // Permissions denied }; @@ -146,14 +141,14 @@ impl Temporary { let entry_data = match entry { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read entry in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display())); continue; } //Permissions denied }; let metadata: Metadata = match entry_data.metadata() { Ok(t) => t, Err(_) => { - self.text_messages.warnings.push("Cannot read metadata in dir ".to_string() + current_folder.as_str()); + self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display())); continue; } //Permissions denied }; @@ -164,24 +159,11 @@ impl Temporary { continue; } - next_folder = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - } - + "/"; + let next_folder = current_folder.join(entry_data.file_name()); + if self.directories.is_excluded(&next_folder) || self.excluded_items.is_excluded(&next_folder) { + continue 'dir; + } - for ed in &self.directories.excluded_directories { - if next_folder == *ed { - continue 'dir; - } - } - for expression in &self.excluded_items.items { - if Common::regex_check(expression, &next_folder) { - continue 'dir; - } - } folders_to_check.push(next_folder); } else if metadata.is_file() { let file_name_lowercase: String = match entry_data.file_name().into_string() { @@ -197,26 +179,10 @@ impl Temporary { self.information.number_of_ignored_files += 1; continue 'dir; } - // Checking files - #[allow(unused_mut)] // Used is later by Windows build - let mut current_file_name = "".to_owned() - + ¤t_folder - + match &entry_data.file_name().into_string() { - Ok(t) => t, - Err(_) => continue, - }; - - // Checking expressions - for expression in &self.excluded_items.items { - if Common::regex_check(expression, ¤t_file_name) { - continue 'dir; - } - } - - #[cfg(target_family = "windows")] - { - current_file_name = Common::prettier_windows_path(¤t_file_name); + let current_file_name = current_folder.join(entry_data.file_name()); + if self.excluded_items.is_excluded(¤t_file_name) { + continue 'dir; } // Creating new file entry @@ -226,12 +192,12 @@ impl Temporary { Ok(t) => match t.duration_since(UNIX_EPOCH) { Ok(d) => d.as_secs(), Err(_) => { - self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name)); + self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display())); 0 } }, Err(_) => { - self.text_messages.warnings.push("Unable to get modification date from file ".to_string() + current_file_name.as_str()); + self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display())); continue; } // Permissions Denied }, @@ -261,7 +227,7 @@ impl Temporary { DeleteMethod::Delete => { for file_entry in &self.temporary_files { if fs::remove_file(file_entry.path.clone()).is_err() { - self.text_messages.warnings.push(file_entry.path.clone()); + self.text_messages.warnings.push(file_entry.path.display().to_string()); } } } @@ -327,27 +293,24 @@ impl SaveResults for Temporary { } }; - 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(format!("Failed to save results to file {}", file_name)); - return false; - } + if writeln!( + file, + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items + ) + .is_err() + { + self.text_messages.errors.push(format!("Failed to save results to file {}", file_name)); + return false; } if !self.temporary_files.is_empty() { - file.write_all(format!("Found {} temporary files.\n", self.information.number_of_temporary_files).as_bytes()).unwrap(); + writeln!(file, "Found {} temporary files.", self.information.number_of_temporary_files).unwrap(); for file_entry in self.temporary_files.iter() { - file.write_all(format!("{} \n", file_entry.path).as_bytes()).unwrap(); + writeln!(file, "{}", file_entry.path.display()).unwrap(); } } else { - file.write_all(b"Not found any temporary files.").unwrap(); + write!(file, "Not found any temporary files.").unwrap(); } Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); true @@ -358,7 +321,7 @@ impl PrintResults for Temporary { let start_time: SystemTime = SystemTime::now(); println!("Found {} temporary files.\n", self.information.number_of_temporary_files); for file_entry in self.temporary_files.iter() { - println!("{}", file_entry.path); + println!("{}", file_entry.path.display()); } Common::print_time(start_time, SystemTime::now(), "print_entries".to_string()); diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index faeef26..a756b88 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -19,9 +19,18 @@ use gtk::{Builder, SelectionMode, TreeIter, TreeView}; use std::cell::RefCell; use std::collections::HashMap; use std::fs::Metadata; +use std::path::Path; use std::rc::Rc; use std::{env, fs, process, thread}; +fn split_path(path: &Path) -> (String, String) { + match (path.parent(), path.file_name()) { + (Some(dir), Some(file)) => (dir.display().to_string(), file.to_string_lossy().into_owned()), + (Some(dir), None) => (dir.display().to_string(), String::new()), + (None, _) => (String::new(), String::new()), + } +} + fn main() { let mut exit_program_after_initialization: bool = false; // Printing version @@ -1395,12 +1404,10 @@ fn main() { ]; list_store.set(&list_store.append(), &col_indices, &values); for entry in vector { - let path = &entry.path; - let index = path.rfind('/').unwrap(); - + let (directory, file) = split_path(&entry.path); let values: [&dyn ToValue; 6] = [ - &(path[index + 1..].to_string()), - &(path[..index].to_string()), + &file, + &directory, &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), &(entry.modified_date), &(MAIN_ROW_COLOR.to_string()), @@ -1425,12 +1432,10 @@ fn main() { ]; list_store.set(&list_store.append(), &col_indices, &values); for entry in vector { - let path = &entry.path; - let index = path.rfind('/').unwrap(); - + let (directory, file) = split_path(&entry.path); let values: [&dyn ToValue; 6] = [ - &(path[index + 1..].to_string()), - &(path[..index].to_string()), + &file, + &directory, &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), &(entry.modified_date), &(MAIN_ROW_COLOR.to_string()), @@ -1509,10 +1514,9 @@ fn main() { let hashmap = ef.get_empty_folder_list(); - for (name, entry) in hashmap { - let name: String = name[..(name.len() - 1)].to_string(); - let index = name.rfind('/').unwrap(); - let values: [&dyn ToValue; 3] = [&(name[index + 1..].to_string()), &(name[..index].to_string()), &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string())]; + for (path, entry) in hashmap { + let (directory, file) = split_path(path); + let values: [&dyn ToValue; 3] = [&file, &directory, &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string())]; list_store.set(&list_store.append(), &col_indices, &values); } print_text_messages_to_text_view(text_messages, &text_view_errors); @@ -1578,9 +1582,8 @@ fn main() { let vector = vf.get_empty_files(); for file_entry in vector { - let name: String = file_entry.path.to_string(); - let index = name.rfind('/').unwrap(); - let values: [&dyn ToValue; 3] = [&(name[index + 1..].to_string()), &(name[..index].to_string()), &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string())]; + let (directory, file) = split_path(&file_entry.path); + let values: [&dyn ToValue; 3] = [&file, &directory, &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string())]; list_store.set(&list_store.append(), &col_indices, &values); } print_text_messages_to_text_view(text_messages, &text_view_errors); @@ -1647,12 +1650,11 @@ fn main() { for (size, vector) in btreemap.iter().rev() { for file_entry in vector { - let name: String = file_entry.path.to_string(); - let index = name.rfind('/').unwrap(); + let (directory, file) = split_path(&file_entry.path); let values: [&dyn ToValue; 4] = [ &(format!("{} ({} bytes)", size.file_size(options::BINARY).unwrap(), size)), - &(name[index + 1..].to_string()), - &(name[..index].to_string()), + &file, + &directory, &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), ]; list_store.set(&list_store.append(), &col_indices, &values); @@ -1721,9 +1723,8 @@ fn main() { let vector = tf.get_temporary_files(); for file_entry in vector { - let name: String = file_entry.path.to_string(); - let index = name.rfind('/').unwrap(); - let values: [&dyn ToValue; 3] = [&(name[index + 1..].to_string()), &(name[..index].to_string()), &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string())]; + let (directory, file) = split_path(&file_entry.path); + let values: [&dyn ToValue; 3] = [&file, &directory, &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string())]; list_store.set(&list_store.append(), &col_indices, &values); } print_text_messages_to_text_view(text_messages, &text_view_errors);