Big code refactoring
This commit is contained in:
parent
3cff32c031
commit
8161a1c59b
|
@ -1,3 +1,4 @@
|
||||||
|
use czkawka_core::common_traits::*;
|
||||||
use czkawka_core::*;
|
use czkawka_core::*;
|
||||||
use std::{env, process};
|
use std::{env, process};
|
||||||
|
|
||||||
|
@ -64,13 +65,13 @@ fn main() {
|
||||||
let mut df = duplicate::DuplicateFinder::new();
|
let mut df = duplicate::DuplicateFinder::new();
|
||||||
|
|
||||||
if ArgumentsPair::has_command(&arguments, "-i") {
|
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 {
|
} else {
|
||||||
println!("FATAL ERROR: Parameter -i with set of included files is required.");
|
println!("FATAL ERROR: Parameter -i with set of included files is required.");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
if ArgumentsPair::has_command(&arguments, "-e") {
|
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") {
|
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
|
#[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();
|
df.get_text_messages().print_messages();
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,7 @@ fn main() {
|
||||||
let mut ef = empty_folder::EmptyFolder::new();
|
let mut ef = empty_folder::EmptyFolder::new();
|
||||||
|
|
||||||
if ArgumentsPair::has_command(&arguments, "-i") {
|
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 {
|
} else {
|
||||||
println!("FATAL ERROR: Parameter -i with set of included files is required.");
|
println!("FATAL ERROR: Parameter -i with set of included files is required.");
|
||||||
process::exit(1);
|
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
|
#[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" => {
|
"--b" => {
|
||||||
let mut bf = big_file::BigFile::new();
|
let mut bf = big_file::BigFile::new();
|
||||||
|
|
||||||
if ArgumentsPair::has_command(&arguments, "-i") {
|
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 {
|
} else {
|
||||||
println!("FATAL ERROR: Parameter -i with set of included files is required.");
|
println!("FATAL ERROR: Parameter -i with set of included files is required.");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
if ArgumentsPair::has_command(&arguments, "-e") {
|
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") {
|
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
|
#[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();
|
bf.get_text_messages().print_messages();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 humansize::{file_size_opts as options, FileSize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -48,10 +52,9 @@ pub struct BigFile {
|
||||||
text_messages: Messages,
|
text_messages: Messages,
|
||||||
information: Info,
|
information: Info,
|
||||||
big_files: BTreeMap<u64, Vec<FileEntry>>,
|
big_files: BTreeMap<u64, Vec<FileEntry>>,
|
||||||
excluded_items: Vec<String>,
|
excluded_items: ExcludedItems,
|
||||||
excluded_directories: Vec<String>,
|
directories: Directories,
|
||||||
included_directories: Vec<String>,
|
allowed_extensions: Extensions,
|
||||||
allowed_extensions: Vec<String>,
|
|
||||||
recursive_search: bool,
|
recursive_search: bool,
|
||||||
number_of_files_to_check: usize,
|
number_of_files_to_check: usize,
|
||||||
}
|
}
|
||||||
|
@ -62,10 +65,9 @@ impl BigFile {
|
||||||
text_messages: Default::default(),
|
text_messages: Default::default(),
|
||||||
information: Info::new(),
|
information: Info::new(),
|
||||||
big_files: Default::default(),
|
big_files: Default::default(),
|
||||||
excluded_items: vec![],
|
excluded_items: ExcludedItems::new(),
|
||||||
excluded_directories: vec![],
|
directories: Directories::new(),
|
||||||
included_directories: vec![],
|
allowed_extensions: Extensions::new(),
|
||||||
allowed_extensions: vec![],
|
|
||||||
recursive_search: true,
|
recursive_search: true,
|
||||||
number_of_files_to_check: 50,
|
number_of_files_to_check: 50,
|
||||||
}
|
}
|
||||||
|
@ -88,86 +90,9 @@ impl BigFile {
|
||||||
self.recursive_search = recursive_search;
|
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
|
/// 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) {
|
pub fn set_allowed_extensions(&mut self, allowed_extensions: String) {
|
||||||
let start_time: SystemTime = SystemTime::now();
|
self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages);
|
||||||
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<String> = 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn look_for_big_files(&mut self) {
|
fn look_for_big_files(&mut self) {
|
||||||
|
@ -175,7 +100,7 @@ impl BigFile {
|
||||||
let mut folders_to_check: Vec<String> = 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<String> = 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
|
// 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());
|
folders_to_check.push(id.to_string());
|
||||||
}
|
}
|
||||||
self.information.number_of_checked_folders += folders_to_check.len();
|
self.information.number_of_checked_folders += folders_to_check.len();
|
||||||
|
@ -221,7 +146,7 @@ impl BigFile {
|
||||||
let mut is_excluded_dir = false;
|
let mut is_excluded_dir = false;
|
||||||
next_folder = "".to_owned() + ¤t_folder + &entry_data.file_name().into_string().unwrap() + "/";
|
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 {
|
if next_folder == *ed {
|
||||||
is_excluded_dir = true;
|
is_excluded_dir = true;
|
||||||
break;
|
break;
|
||||||
|
@ -229,7 +154,7 @@ impl BigFile {
|
||||||
}
|
}
|
||||||
if !is_excluded_dir {
|
if !is_excluded_dir {
|
||||||
let mut found_expression: bool = false;
|
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) {
|
if Common::regex_check(expression, &next_folder) {
|
||||||
found_expression = true;
|
found_expression = true;
|
||||||
break;
|
break;
|
||||||
|
@ -245,9 +170,9 @@ impl BigFile {
|
||||||
let file_name_lowercase: String = entry_data.file_name().into_string().unwrap().to_lowercase();
|
let file_name_lowercase: String = entry_data.file_name().into_string().unwrap().to_lowercase();
|
||||||
|
|
||||||
// Checking allowed extensions
|
// Checking allowed extensions
|
||||||
if !self.allowed_extensions.is_empty() {
|
if !self.allowed_extensions.file_extensions.is_empty() {
|
||||||
have_valid_extension = false;
|
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()) {
|
if file_name_lowercase.ends_with((".".to_string() + i.to_lowercase().as_str()).as_str()) {
|
||||||
have_valid_extension = true;
|
have_valid_extension = true;
|
||||||
break;
|
break;
|
||||||
|
@ -263,7 +188,7 @@ impl BigFile {
|
||||||
|
|
||||||
// Checking expressions
|
// Checking expressions
|
||||||
let mut found_expression: bool = false;
|
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) {
|
if Common::regex_check(expression, ¤t_file_name) {
|
||||||
found_expression = true;
|
found_expression = true;
|
||||||
break;
|
break;
|
||||||
|
@ -307,7 +232,6 @@ impl BigFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
let mut new_map: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
let mut new_map: BTreeMap<u64, Vec<FileEntry>> = Default::default();
|
||||||
|
|
||||||
for (size, vector) in self.big_files.iter().rev() {
|
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());
|
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(dead_code)]
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
/// Debugging printing - only available on debug build
|
/// Debugging printing - only available on debug build
|
||||||
|
@ -352,22 +309,63 @@ impl BigFile {
|
||||||
|
|
||||||
println!("### Other");
|
println!("### Other");
|
||||||
println!("Big files size {} in {} groups", self.information.number_of_real_files, self.big_files.len());
|
println!("Big files size {} in {} groups", self.information.number_of_real_files, self.big_files.len());
|
||||||
println!("Allowed extensions - {:?}", self.allowed_extensions);
|
println!("Allowed extensions - {:?}", self.allowed_extensions.file_extensions);
|
||||||
println!("Excluded items - {:?}", self.excluded_items);
|
println!("Excluded items - {:?}", self.excluded_items.items);
|
||||||
println!("Included directories - {:?}", self.included_directories);
|
println!("Included directories - {:?}", self.directories.included_directories);
|
||||||
println!("Excluded directories - {:?}", self.excluded_directories);
|
println!("Excluded directories - {:?}", self.directories.excluded_directories);
|
||||||
println!("Recursive search - {}", self.recursive_search.to_string());
|
println!("Recursive search - {}", self.recursive_search.to_string());
|
||||||
println!("Number of files to check - {:?}", self.number_of_files_to_check);
|
println!("Number of files to check - {:?}", self.number_of_files_to_check);
|
||||||
println!("-----------------------------------------");
|
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) {
|
let mut file = match File::create(&file_name) {
|
||||||
self.number_of_files_to_check = number_of_files_to_check;
|
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
|
impl PrintResults for BigFile {
|
||||||
/// Only needed for CLI
|
fn print_results(&self) {
|
||||||
pub fn print_duplicated_entries(&self) {
|
|
||||||
let start_time: SystemTime = SystemTime::now();
|
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());
|
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() {
|
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());
|
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<String> = excluded_items.split(',').map(String::from).collect();
|
|
||||||
let mut checked_expressions: Vec<String> = 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<String> = Vec::<String>::new();
|
|
||||||
let mut optimized_excluded: Vec<String> = Vec::<String>::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::<String>::new();
|
|
||||||
self.excluded_directories = optimized_excluded;
|
|
||||||
optimized_excluded = Vec::<String>::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::<String>::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::<String>::new();
|
|
||||||
self.excluded_directories = optimized_excluded;
|
|
||||||
optimized_excluded = Vec::<String>::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::<String>::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<String> = include_directory.split(',').map(String::from).collect();
|
|
||||||
let mut checked_directories: Vec<String> = 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<String> = exclude_directory.split(',').map(String::from).collect();
|
|
||||||
let mut checked_directories: Vec<String> = 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,54 +104,6 @@ impl Common {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Messages {
|
|
||||||
pub messages: Vec<String>,
|
|
||||||
pub warnings: Vec<String>,
|
|
||||||
pub errors: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::common::Common;
|
use crate::common::Common;
|
||||||
|
|
252
czkawka_core/src/common_directory.rs
Normal file
252
czkawka_core/src/common_directory.rs
Normal file
|
@ -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<String>,
|
||||||
|
pub included_directories: Vec<String>,
|
||||||
|
}
|
||||||
|
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<String> = included_directory.split(',').map(String::from).collect();
|
||||||
|
let mut checked_directories: Vec<String> = 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<String> = excluded_directory.split(',').map(String::from).collect();
|
||||||
|
let mut checked_directories: Vec<String> = 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<String> = Vec::<String>::new();
|
||||||
|
let mut optimized_excluded: Vec<String> = Vec::<String>::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::<String>::new();
|
||||||
|
self.excluded_directories = optimized_excluded;
|
||||||
|
optimized_excluded = Vec::<String>::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::<String>::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::<String>::new();
|
||||||
|
self.excluded_directories = optimized_excluded;
|
||||||
|
optimized_excluded = Vec::<String>::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::<String>::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()
|
||||||
|
}
|
||||||
|
}
|
54
czkawka_core/src/common_extensions.rs
Normal file
54
czkawka_core/src/common_extensions.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use crate::common::Common;
|
||||||
|
use crate::common_messages::Messages;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
pub struct Extensions {
|
||||||
|
pub file_extensions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> = 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()
|
||||||
|
}
|
||||||
|
}
|
52
czkawka_core/src/common_items.rs
Normal file
52
czkawka_core/src/common_items.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::common::Common;
|
||||||
|
use crate::common_messages::Messages;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
pub struct ExcludedItems {
|
||||||
|
pub items: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> = excluded_items.split(',').map(String::from).collect();
|
||||||
|
let mut checked_expressions: Vec<String> = 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()
|
||||||
|
}
|
||||||
|
}
|
47
czkawka_core/src/common_messages.rs
Normal file
47
czkawka_core/src/common_messages.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
pub struct Messages {
|
||||||
|
pub messages: Vec<String>,
|
||||||
|
pub warnings: Vec<String>,
|
||||||
|
pub errors: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
9
czkawka_core/src/common_traits.rs
Normal file
9
czkawka_core/src/common_traits.rs
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -3,10 +3,14 @@ use std::collections::{BTreeMap, HashMap};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::Path;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
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)]
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
pub enum CheckingMethod {
|
pub enum CheckingMethod {
|
||||||
|
@ -82,10 +86,9 @@ pub struct DuplicateFinder {
|
||||||
information: Info,
|
information: Info,
|
||||||
files_with_identical_size: BTreeMap<u64, Vec<FileEntry>>,
|
files_with_identical_size: BTreeMap<u64, Vec<FileEntry>>,
|
||||||
files_with_identical_hashes: BTreeMap<u64, Vec<Vec<FileEntry>>>,
|
files_with_identical_hashes: BTreeMap<u64, Vec<Vec<FileEntry>>>,
|
||||||
allowed_extensions: Vec<String>,
|
directories: Directories,
|
||||||
excluded_items: Vec<String>,
|
allowed_extensions: Extensions,
|
||||||
excluded_directories: Vec<String>,
|
excluded_items: ExcludedItems,
|
||||||
included_directories: Vec<String>,
|
|
||||||
recursive_search: bool,
|
recursive_search: bool,
|
||||||
min_file_size: u64,
|
min_file_size: u64,
|
||||||
check_method: CheckingMethod,
|
check_method: CheckingMethod,
|
||||||
|
@ -95,21 +98,31 @@ pub struct DuplicateFinder {
|
||||||
impl DuplicateFinder {
|
impl DuplicateFinder {
|
||||||
pub fn new() -> DuplicateFinder {
|
pub fn new() -> DuplicateFinder {
|
||||||
DuplicateFinder {
|
DuplicateFinder {
|
||||||
text_messages: Default::default(),
|
text_messages: Messages::new(),
|
||||||
information: Info::new(),
|
information: Info::new(),
|
||||||
files_with_identical_size: Default::default(),
|
files_with_identical_size: Default::default(),
|
||||||
files_with_identical_hashes: Default::default(),
|
files_with_identical_hashes: Default::default(),
|
||||||
excluded_items: vec![],
|
|
||||||
excluded_directories: vec![],
|
|
||||||
included_directories: vec![],
|
|
||||||
recursive_search: true,
|
recursive_search: true,
|
||||||
allowed_extensions: vec![],
|
allowed_extensions: Extensions::new(),
|
||||||
check_method: CheckingMethod::None,
|
check_method: CheckingMethod::None,
|
||||||
delete_method: DeleteMethod::None,
|
delete_method: DeleteMethod::None,
|
||||||
min_file_size: 1024,
|
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<u64, Vec<FileEntry>> {
|
pub fn get_files_sorted_by_size(&self) -> &BTreeMap<u64, Vec<FileEntry>> {
|
||||||
&self.files_with_identical_size
|
&self.files_with_identical_size
|
||||||
}
|
}
|
||||||
|
@ -142,192 +155,19 @@ impl DuplicateFinder {
|
||||||
self.recursive_search = recursive_search;
|
self.recursive_search = recursive_search;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finding duplicates, save results to internal struct variables
|
pub fn set_included_directory(&mut self, included_directory: String) -> bool {
|
||||||
pub fn find_duplicates(&mut self) {
|
self.directories.set_included_directory(included_directory, &mut self.text_messages)
|
||||||
self.optimize_directories();
|
|
||||||
self.check_files_size();
|
|
||||||
if self.check_method == CheckingMethod::Hash {
|
|
||||||
self.check_files_hash();
|
|
||||||
}
|
|
||||||
self.delete_files();
|
|
||||||
self.debug_print();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setting excluded items which needs to contains * wildcrard
|
pub fn set_excluded_directory(&mut self, excluded_directory: String) {
|
||||||
/// Are a lot of slower than absolute path, so it should be used to heavy
|
self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages);
|
||||||
pub fn set_excluded_items(&mut self, mut excluded_items: String) {
|
}
|
||||||
let start_time: SystemTime = SystemTime::now();
|
pub fn set_allowed_extensions(&mut self, allowed_extensions: String) {
|
||||||
|
self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages);
|
||||||
if excluded_items.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
excluded_items = excluded_items.replace("\"", "");
|
|
||||||
let expressions: Vec<String> = excluded_items.split(',').map(String::from).collect();
|
|
||||||
let mut checked_expressions: Vec<String> = 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of allowed extensions, only files with this extensions will be checking if are duplicates
|
pub fn set_excluded_items(&mut self, excluded_items: String) {
|
||||||
pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String) {
|
self.excluded_items.set_excluded_items(excluded_items, &mut self.text_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<String> = 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<String> = include_directory.split(',').map(String::from).collect();
|
|
||||||
let mut checked_directories: Vec<String> = 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<String> = exclude_directory.split(',').map(String::from).collect();
|
|
||||||
let mut checked_directories: Vec<String> = 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read file length and puts it to different boxes(each for different lengths)
|
/// 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<String> = 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<String> = 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
|
// 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());
|
folders_to_check.push(id.to_string());
|
||||||
}
|
}
|
||||||
self.information.number_of_checked_folders += folders_to_check.len();
|
self.information.number_of_checked_folders += folders_to_check.len();
|
||||||
|
@ -384,7 +224,7 @@ impl DuplicateFinder {
|
||||||
let mut is_excluded_dir = false;
|
let mut is_excluded_dir = false;
|
||||||
next_folder = "".to_owned() + ¤t_folder + &entry_data.file_name().into_string().unwrap() + "/";
|
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 {
|
if next_folder == *ed {
|
||||||
is_excluded_dir = true;
|
is_excluded_dir = true;
|
||||||
break;
|
break;
|
||||||
|
@ -392,7 +232,7 @@ impl DuplicateFinder {
|
||||||
}
|
}
|
||||||
if !is_excluded_dir {
|
if !is_excluded_dir {
|
||||||
let mut found_expression: bool = false;
|
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) {
|
if Common::regex_check(expression, &next_folder) {
|
||||||
found_expression = true;
|
found_expression = true;
|
||||||
break;
|
break;
|
||||||
|
@ -408,9 +248,9 @@ impl DuplicateFinder {
|
||||||
let file_name_lowercase: String = entry_data.file_name().into_string().unwrap().to_lowercase();
|
let file_name_lowercase: String = entry_data.file_name().into_string().unwrap().to_lowercase();
|
||||||
|
|
||||||
// Checking allowed extensions
|
// Checking allowed extensions
|
||||||
if !self.allowed_extensions.is_empty() {
|
if !self.allowed_extensions.file_extensions.is_empty() {
|
||||||
have_valid_extension = false;
|
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()) {
|
if file_name_lowercase.ends_with((".".to_string() + i.to_lowercase().as_str()).as_str()) {
|
||||||
have_valid_extension = true;
|
have_valid_extension = true;
|
||||||
break;
|
break;
|
||||||
|
@ -426,7 +266,7 @@ impl DuplicateFinder {
|
||||||
|
|
||||||
// Checking expressions
|
// Checking expressions
|
||||||
let mut found_expression: bool = false;
|
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) {
|
if Common::regex_check(expression, ¤t_file_name) {
|
||||||
found_expression = true;
|
found_expression = true;
|
||||||
break;
|
break;
|
||||||
|
@ -488,87 +328,6 @@ impl DuplicateFinder {
|
||||||
Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string());
|
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
|
/// The slowest checking type, which must be applied after checking for size
|
||||||
fn check_files_hash(&mut self) {
|
fn check_files_hash(&mut self) {
|
||||||
let start_time: SystemTime = SystemTime::now();
|
let start_time: SystemTime = SystemTime::now();
|
||||||
|
@ -631,6 +390,46 @@ impl DuplicateFinder {
|
||||||
Common::print_time(start_time, SystemTime::now(), "check_files_hash".to_string());
|
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(dead_code)]
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
/// Debugging printing - only available on debug build
|
/// Debugging printing - only available on debug build
|
||||||
|
@ -676,20 +475,102 @@ impl DuplicateFinder {
|
||||||
|
|
||||||
println!("Files list size - {}", self.files_with_identical_size.len());
|
println!("Files list size - {}", self.files_with_identical_size.len());
|
||||||
println!("Hashed Files list size - {}", self.files_with_identical_hashes.len());
|
println!("Hashed Files list size - {}", self.files_with_identical_hashes.len());
|
||||||
println!("Allowed extensions - {:?}", self.allowed_extensions);
|
println!("Allowed extensions - {:?}", self.allowed_extensions.file_extensions);
|
||||||
println!("Excluded items - {:?}", self.excluded_items);
|
println!("Excluded items - {:?}", self.excluded_items.items);
|
||||||
println!("Included directories - {:?}", self.included_directories);
|
println!("Included directories - {:?}", self.directories.included_directories);
|
||||||
println!("Excluded directories - {:?}", self.excluded_directories);
|
println!("Excluded directories - {:?}", self.directories.excluded_directories);
|
||||||
println!("Recursive search - {}", self.recursive_search.to_string());
|
println!("Recursive search - {}", self.recursive_search.to_string());
|
||||||
println!("Minimum file size - {:?}", self.min_file_size);
|
println!("Minimum file size - {:?}", self.min_file_size);
|
||||||
println!("Checking Method - {:?}", self.check_method);
|
println!("Checking Method - {:?}", self.check_method);
|
||||||
println!("Delete Method - {:?}", self.delete_method);
|
println!("Delete Method - {:?}", self.delete_method);
|
||||||
println!("-----------------------------------------");
|
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
|
/// Print information's about duplicated entries
|
||||||
/// Only needed for CLI
|
/// Only needed for CLI
|
||||||
pub fn print_duplicated_entries(&self) {
|
fn print_results(&self) {
|
||||||
let start_time: SystemTime = SystemTime::now();
|
let start_time: SystemTime = SystemTime::now();
|
||||||
let mut number_of_files: u64 = 0;
|
let mut number_of_files: u64 = 0;
|
||||||
let mut number_of_groups: 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());
|
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<String> = Vec::<String>::new();
|
|
||||||
let mut optimized_excluded: Vec<String> = Vec::<String>::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::<String>::new();
|
|
||||||
self.excluded_directories = optimized_excluded;
|
|
||||||
optimized_excluded = Vec::<String>::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::<String>::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::<String>::new();
|
|
||||||
self.excluded_directories = optimized_excluded;
|
|
||||||
optimized_excluded = Vec::<String>::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::<String>::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
|
/// Functions to remove slice(vector) of files with provided method
|
||||||
|
|
|
@ -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::collections::BTreeMap;
|
||||||
|
use std::fs;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{File, Metadata};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use std::{fs, process};
|
|
||||||
|
|
||||||
/// Enum with values which show if folder is empty.
|
/// 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
|
/// 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,
|
delete_folders: bool,
|
||||||
text_messages: Messages,
|
text_messages: Messages,
|
||||||
empty_folder_list: BTreeMap<String, FolderEntry>, // Path, FolderEntry
|
empty_folder_list: BTreeMap<String, FolderEntry>, // Path, FolderEntry
|
||||||
included_directories: Vec<String>,
|
directories: Directories,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Info struck with helpful information's about results
|
/// Info struck with helpful information's about results
|
||||||
|
@ -57,9 +59,9 @@ impl EmptyFolder {
|
||||||
EmptyFolder {
|
EmptyFolder {
|
||||||
information: Default::default(),
|
information: Default::default(),
|
||||||
delete_folders: false,
|
delete_folders: false,
|
||||||
text_messages: Default::default(),
|
text_messages: Messages::new(),
|
||||||
empty_folder_list: Default::default(),
|
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
|
/// Public function used by CLI to search for empty folders
|
||||||
pub fn find_empty_folders(&mut self) {
|
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(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();
|
self.optimize_folders();
|
||||||
if self.delete_folders {
|
if self.delete_folders {
|
||||||
self.delete_empty_folders();
|
self.delete_empty_folders();
|
||||||
|
@ -90,43 +92,6 @@ impl EmptyFolder {
|
||||||
self.delete_folders = delete_folder;
|
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
|
/// 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.
|
/// 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) {
|
fn optimize_folders(&mut self) {
|
||||||
|
@ -157,7 +122,7 @@ impl EmptyFolder {
|
||||||
|
|
||||||
if initial_checking {
|
if initial_checking {
|
||||||
// Add root folders for finding
|
// Add root folders for finding
|
||||||
for id in &self.included_directories {
|
for id in &self.directories.included_directories {
|
||||||
folders_checked.insert(
|
folders_checked.insert(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
FolderEntry {
|
FolderEntry {
|
||||||
|
@ -285,17 +250,18 @@ impl EmptyFolder {
|
||||||
Common::print_time(start_time, SystemTime::now(), "delete_files".to_string());
|
Common::print_time(start_time, SystemTime::now(), "delete_files".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prints basic info about empty folders // TODO print better
|
/// Set include dir which needs to be relative, exists etc.
|
||||||
pub fn print_empty_folders(&self) {
|
pub fn set_included_directory(&mut self, included_directory: String) {
|
||||||
if !self.empty_folder_list.is_empty() {
|
self.directories.set_included_directory(included_directory, &mut self.text_messages);
|
||||||
println!("Found {} empty folders", self.empty_folder_list.len());
|
|
||||||
}
|
|
||||||
for i in &self.empty_folder_list {
|
|
||||||
println!("{}", i.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl Default for EmptyFolder {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Debug print
|
impl DebugPrint for EmptyFolder {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[allow(unreachable_code)]
|
#[allow(unreachable_code)]
|
||||||
fn debug_print(&self) {
|
fn debug_print(&self) {
|
||||||
|
@ -307,128 +273,56 @@ impl EmptyFolder {
|
||||||
println!("---------------DEBUG PRINT---------------");
|
println!("---------------DEBUG PRINT---------------");
|
||||||
println!("Number of all checked folders - {}", self.information.number_of_checked_folders);
|
println!("Number of all checked folders - {}", self.information.number_of_checked_folders);
|
||||||
println!("Number of empty folders - {}", self.information.number_of_empty_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!("-----------------------------------------");
|
println!("-----------------------------------------");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// TODO maybe move this and one from duplicated finder to one common class to avoid duplicating code
|
impl SaveResults for EmptyFolder {
|
||||||
/// Optimize include and exclude directories by removing duplicates etc.
|
fn save_results_to_file(&mut self, file_name: &str) -> bool {
|
||||||
fn optimize_directories(&mut self) {
|
|
||||||
let start_time: SystemTime = SystemTime::now();
|
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<String> = Vec::<String>::new();
|
let mut file = match File::create(&file_name) {
|
||||||
// Remove duplicated entries like: "/", "/"
|
Ok(t) => t,
|
||||||
|
Err(_) => {
|
||||||
self.included_directories.sort();
|
self.text_messages.errors.push("Failed to create file ".to_string() + file_name.as_str());
|
||||||
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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;
|
if !self.empty_folder_list.is_empty() {
|
||||||
optimized_included = Vec::<String>::new();
|
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())
|
||||||
// Remove non existed directories
|
.unwrap();
|
||||||
for id in &self.included_directories {
|
for name in self.empty_folder_list.keys() {
|
||||||
let path = Path::new(id);
|
file.write_all((name.clone() + "\n").as_bytes()).unwrap();
|
||||||
if path.exists() {
|
|
||||||
optimized_included.push(id.to_string());
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
file.write_all(b"Not found any empty folders.").unwrap();
|
||||||
}
|
}
|
||||||
|
Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string());
|
||||||
self.included_directories = optimized_included;
|
true
|
||||||
//optimized_included = Vec::<String>::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<String> = include_directory.split(',').map(String::from).collect();
|
|
||||||
let mut checked_directories: Vec<String> = 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Default for EmptyFolder {
|
impl PrintResults for EmptyFolder {
|
||||||
fn default() -> Self {
|
/// Prints basic info about empty folders // TODO print better
|
||||||
Self::new()
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,10 @@ pub mod duplicate;
|
||||||
pub mod empty_folder;
|
pub mod empty_folder;
|
||||||
|
|
||||||
pub mod common;
|
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";
|
pub const CZKAWKA_VERSION: &str = "0.1.3";
|
||||||
|
|
|
@ -3,7 +3,7 @@ use humansize::{file_size_opts as options, FileSize};
|
||||||
|
|
||||||
extern crate gtk;
|
extern crate gtk;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use czkawka_core::common::Messages;
|
use czkawka_core::common_messages::Messages;
|
||||||
use czkawka_core::duplicate::CheckingMethod;
|
use czkawka_core::duplicate::CheckingMethod;
|
||||||
use czkawka_core::empty_folder::EmptyFolder;
|
use czkawka_core::empty_folder::EmptyFolder;
|
||||||
use duplicate::DuplicateFinder;
|
use duplicate::DuplicateFinder;
|
||||||
|
@ -259,8 +259,8 @@ fn main() {
|
||||||
let mut df = DuplicateFinder::new();
|
let mut df = DuplicateFinder::new();
|
||||||
let check_method = duplicate::CheckingMethod::Hash; // TODO
|
let check_method = duplicate::CheckingMethod::Hash; // TODO
|
||||||
{
|
{
|
||||||
df.set_include_directory(get_string_from_list_store(&scrolled_window_included_directories));
|
df.set_included_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_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_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_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::<u64>() {
|
df.set_min_file_size(match entry_duplicate_minimal_size.get_text().as_str().parse::<u64>() {
|
||||||
|
@ -401,7 +401,7 @@ fn main() {
|
||||||
// TODO Change to proper value
|
// TODO Change to proper value
|
||||||
let mut ef = EmptyFolder::new();
|
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.set_delete_folder(false);
|
||||||
ef.find_empty_folders();
|
ef.find_empty_folders();
|
||||||
|
|
||||||
|
|
|
@ -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(TextBlock::new().text("Include Directory:").v_align("center").h_align("start").attach(Grid::column(0)).attach(Grid::row(0)).build(ctx))
|
||||||
.child(
|
.child(
|
||||||
TextBox::new()
|
TextBox::new()
|
||||||
.id("include_directory")
|
.id("included_directory")
|
||||||
.water_mark("Include Directory")
|
.water_mark("Include Directory")
|
||||||
.v_align("center")
|
.v_align("center")
|
||||||
.h_align("start")
|
.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(TextBlock::new().text("Exclude Directory:").v_align("center").h_align("start").attach(Grid::column(0)).attach(Grid::row(1)).build(ctx))
|
||||||
.child(
|
.child(
|
||||||
TextBox::new()
|
TextBox::new()
|
||||||
.id("exclude_directory")
|
.id("excluded_directory")
|
||||||
.water_mark("Exclude Directory")
|
.water_mark("Exclude Directory")
|
||||||
.v_align("center")
|
.v_align("center")
|
||||||
.h_align("start")
|
.h_align("start")
|
||||||
|
|
Loading…
Reference in a new issue