1
0
Fork 0
mirror of synced 2024-05-05 04:52:38 +12:00
czkawka/czkawka_core/src/empty_folder.rs

323 lines
13 KiB
Rust
Raw Normal View History

2020-09-27 03:52:13 +13:00
use crate::common::Common;
use crate::common_directory::Directories;
use crate::common_messages::Messages;
use crate::common_traits::{DebugPrint, PrintResults, SaveResults};
2020-09-22 21:24:55 +12:00
use std::collections::BTreeMap;
2020-09-27 03:52:13 +13:00
use std::fs;
use std::fs::{File, Metadata};
use std::io::Write;
2020-09-01 05:37:30 +12:00
use std::time::SystemTime;
2020-09-01 09:03:10 +12:00
2020-09-02 08:48:20 +12:00
/// Enum with values which show if folder is empty.
2020-09-27 06:51:28 +13:00
/// In function "optimize_folders" automatically "Maybe" is changed to "Yes", so it is not necessary to put it here
2020-09-02 03:10:54 +12:00
#[derive(Eq, PartialEq, Copy, Clone)]
2020-09-01 09:03:10 +12:00
enum FolderEmptiness {
No,
Maybe,
}
2020-09-02 03:10:54 +12:00
2020-09-02 08:48:20 +12:00
/// Struct assigned to each checked folder with parent path(used to ignore parent if children are not empty) and flag which shows if folder is empty
2020-09-02 03:10:54 +12:00
#[derive(Clone)]
2020-09-22 21:24:55 +12:00
pub struct FolderEntry {
parent_path: Option<String>, // Usable only when finding
2020-09-01 09:03:10 +12:00
is_empty: FolderEmptiness,
2020-09-22 21:24:55 +12:00
pub modified_date: SystemTime,
2020-09-01 09:03:10 +12:00
}
2020-09-01 05:37:30 +12:00
/// Struct to store most basics info about all folder
2020-09-01 05:37:30 +12:00
pub struct EmptyFolder {
information: Info,
2020-09-18 17:32:37 +12:00
delete_folders: bool,
text_messages: Messages,
2020-09-22 21:24:55 +12:00
empty_folder_list: BTreeMap<String, FolderEntry>, // Path, FolderEntry
2020-09-27 03:52:13 +13:00
directories: Directories,
2020-09-01 05:37:30 +12:00
}
2020-09-18 17:32:37 +12:00
/// Info struck with helpful information's about results
pub struct Info {
number_of_checked_folders: usize,
2020-09-22 21:24:55 +12:00
pub number_of_empty_folders: usize,
2020-09-18 17:32:37 +12:00
}
impl Info {
pub fn new() -> Info {
Info {
number_of_checked_folders: 0,
number_of_empty_folders: 0,
}
}
}
impl Default for Info {
fn default() -> Self {
Self::new()
}
}
2020-09-02 08:48:20 +12:00
/// Method implementation for EmptyFolder
2020-09-01 05:37:30 +12:00
impl EmptyFolder {
2020-09-02 08:48:20 +12:00
/// New function providing basics values
2020-09-01 05:37:30 +12:00
pub fn new() -> EmptyFolder {
EmptyFolder {
information: Default::default(),
2020-09-18 17:32:37 +12:00
delete_folders: false,
2020-09-27 03:52:13 +13:00
text_messages: Messages::new(),
2020-09-02 03:10:54 +12:00
empty_folder_list: Default::default(),
2020-09-27 03:52:13 +13:00
directories: Directories::new(),
2020-09-01 05:37:30 +12:00
}
}
2020-09-22 21:24:55 +12:00
pub fn get_empty_folder_list(&self) -> &BTreeMap<String, FolderEntry> {
&self.empty_folder_list
}
pub fn get_text_messages(&self) -> &Messages {
&self.text_messages
2020-09-18 17:32:37 +12:00
}
2020-09-22 21:24:55 +12:00
pub fn get_information(&self) -> &Info {
&self.information
}
2020-09-18 17:32:37 +12:00
2020-09-02 08:48:20 +12:00
/// Public function used by CLI to search for empty folders
2020-09-18 17:32:37 +12:00
pub fn find_empty_folders(&mut self) {
2020-09-27 03:52:13 +13:00
self.directories.optimize_directories(true, &mut self.text_messages);
2020-09-02 03:10:54 +12:00
self.check_for_empty_folders(true);
2020-09-27 06:51:28 +13:00
//self.check_for_empty_folders(false); // Second check, should be done before deleting to be sure that empty folder is still empty
2020-09-02 03:10:54 +12:00
self.optimize_folders();
2020-09-18 17:32:37 +12:00
if self.delete_folders {
2020-09-01 05:37:30 +12:00
self.delete_empty_folders();
}
2020-09-18 17:32:37 +12:00
self.debug_print();
}
pub fn set_delete_folder(&mut self, delete_folder: bool) {
self.delete_folders = delete_folder;
2020-09-01 05:37:30 +12:00
}
2020-09-02 03:10:54 +12:00
/// Clean directory tree
/// If directory contains only 2 empty folders, then this directory should be removed instead two empty folders inside because it will produce another empty folder.
fn optimize_folders(&mut self) {
2020-09-22 21:24:55 +12:00
let mut new_directory_folders: BTreeMap<String, FolderEntry> = Default::default();
2020-09-02 03:10:54 +12:00
for (name, folder_entry) in &self.empty_folder_list {
2020-09-27 06:51:28 +13:00
match &folder_entry.parent_path {
2020-09-02 03:10:54 +12:00
Some(t) => {
if !self.empty_folder_list.contains_key(t) {
2020-09-27 06:51:28 +13:00
new_directory_folders.insert(name.clone(), folder_entry.clone());
2020-09-02 03:10:54 +12:00
}
}
None => {
2020-09-27 06:51:28 +13:00
new_directory_folders.insert(name.clone(), folder_entry.clone());
2020-09-02 03:10:54 +12:00
}
}
}
self.empty_folder_list = new_directory_folders;
2020-09-18 21:11:08 +12:00
self.information.number_of_empty_folders = self.empty_folder_list.len();
2020-09-02 03:10:54 +12:00
}
2020-09-02 08:48:20 +12:00
/// Function to check if folder are empty.
/// Parameter initial_checking for second check before deleting to be sure that checked folder is still empty
2020-09-02 03:10:54 +12:00
fn check_for_empty_folders(&mut self, initial_checking: bool) {
2020-09-01 09:03:10 +12:00
let start_time: SystemTime = SystemTime::now();
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
2020-09-22 21:24:55 +12:00
let mut folders_checked: BTreeMap<String, FolderEntry> = Default::default();
2020-09-01 09:03:10 +12:00
2020-09-02 03:10:54 +12:00
if initial_checking {
// Add root folders for finding
2020-09-27 03:52:13 +13:00
for id in &self.directories.included_directories {
2020-09-02 03:10:54 +12:00
folders_checked.insert(
id.clone(),
FolderEntry {
parent_path: None,
is_empty: FolderEmptiness::Maybe,
2020-09-22 21:24:55 +12:00
modified_date: SystemTime::now(),
2020-09-02 03:10:54 +12:00
},
);
folders_to_check.push(id.clone());
}
} else {
2020-09-02 08:48:20 +12:00
// Add folders searched before
2020-09-27 06:51:28 +13:00
for (name, folder_entry) in &self.empty_folder_list {
2020-09-02 03:10:54 +12:00
folders_checked.insert(
2020-09-22 21:24:55 +12:00
name.clone(),
2020-09-02 03:10:54 +12:00
FolderEntry {
parent_path: None,
is_empty: FolderEmptiness::Maybe,
2020-09-27 06:51:28 +13:00
modified_date: folder_entry.modified_date,
2020-09-02 03:10:54 +12:00
},
);
2020-09-22 21:24:55 +12:00
folders_to_check.push(name.clone());
2020-09-02 03:10:54 +12:00
}
2020-09-01 09:03:10 +12:00
}
let mut current_folder: String;
let mut next_folder: String;
while !folders_to_check.is_empty() {
2020-09-18 21:11:08 +12:00
self.information.number_of_checked_folders += 1;
2020-09-01 09:03:10 +12:00
current_folder = folders_to_check.pop().unwrap();
2020-09-27 06:51:28 +13:00
// Checked folder may be deleted or we may not have permissions to open it so we assume that this folder is not be empty
2020-09-01 09:03:10 +12:00
let read_dir = match fs::read_dir(&current_folder) {
Ok(t) => t,
2020-09-27 06:51:28 +13:00
Err(_) => {
2020-09-01 09:03:10 +12:00
folders_checked.get_mut(&current_folder).unwrap().is_empty = FolderEmptiness::No;
continue;
}
};
2020-09-02 08:48:20 +12:00
2020-09-01 09:03:10 +12:00
for entry in read_dir {
let entry_data = match entry {
2020-09-08 03:39:16 +12:00
Ok(t) => t,
Err(_) => continue, //Permissions denied
};
let metadata: Metadata = match entry_data.metadata() {
2020-09-08 03:39:16 +12:00
Ok(t) => t,
Err(_) => continue, //Permissions denied
};
2020-09-02 08:48:20 +12:00
// If child is dir, still folder may be considered as empty if all children are only directories.
2020-09-01 09:03:10 +12:00
if metadata.is_dir() {
next_folder = "".to_owned() + &current_folder + &entry_data.file_name().into_string().unwrap() + "/";
folders_to_check.push(next_folder.clone());
folders_checked.insert(
next_folder.clone(),
FolderEntry {
parent_path: Option::from(current_folder.clone()),
is_empty: FolderEmptiness::Maybe,
2020-09-22 21:24:55 +12:00
modified_date: match metadata.modified() {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Failed to read modification date of folder {}", current_folder));
SystemTime::now()
}
},
},
);
2020-09-01 09:03:10 +12:00
} else {
2020-09-02 08:48:20 +12:00
// Not folder so it may be a file or symbolic link so it isn't empty
2020-09-01 09:03:10 +12:00
folders_checked.get_mut(&current_folder).unwrap().is_empty = FolderEmptiness::No;
let mut d = folders_checked.get_mut(&current_folder).unwrap();
2020-09-02 03:10:54 +12:00
let mut cf: String;
2020-09-27 06:51:28 +13:00
// Loop to recursively set as non empty this and all his parent folders
2020-09-01 09:03:10 +12:00
loop {
d.is_empty = FolderEmptiness::No;
if d.parent_path != None {
cf = d.parent_path.clone().unwrap();
d = folders_checked.get_mut(&cf).unwrap();
} else {
break;
}
}
}
}
}
2020-09-27 06:51:28 +13:00
// Now we check if checked folders are really empty, and if are, then
2020-09-02 03:10:54 +12:00
if initial_checking {
2020-09-02 08:48:20 +12:00
// We need to set empty folder list
for (name, folder_entry) in folders_checked {
2020-09-27 06:51:28 +13:00
if folder_entry.is_empty != FolderEmptiness::No {
self.empty_folder_list.insert(name, folder_entry);
2020-09-02 03:10:54 +12:00
}
}
} else {
2020-09-02 08:48:20 +12:00
// We need to check if parent of folder isn't also empty, because we wan't to delete only parent with two empty folders except this folders and at the end parent folder
2020-09-22 21:24:55 +12:00
let mut new_folders_list: BTreeMap<String, FolderEntry> = Default::default();
for (name, folder_entry) in folders_checked {
2020-09-27 06:51:28 +13:00
if folder_entry.is_empty != FolderEmptiness::No && self.empty_folder_list.contains_key(&name) {
new_folders_list.insert(name, folder_entry);
2020-09-02 03:10:54 +12:00
}
}
self.empty_folder_list = new_folders_list;
}
Common::print_time(start_time, SystemTime::now(), "check_for_empty_folder".to_string());
2020-09-01 09:03:10 +12:00
}
2020-09-02 03:10:54 +12:00
2020-09-12 23:25:23 +12:00
/// Deletes earlier found empty folders
2020-09-27 06:51:28 +13:00
fn delete_empty_folders(&mut self) {
2020-09-02 03:10:54 +12:00
let start_time: SystemTime = SystemTime::now();
2020-09-02 08:48:20 +12:00
// Folders may be deleted or require too big privileges
for name in self.empty_folder_list.keys() {
2020-09-27 06:51:28 +13:00
match fs::remove_dir_all(name) {
2020-09-02 03:10:54 +12:00
Ok(_) => (),
Err(_) => self.text_messages.warnings.push(format!("Failed to remove folder {}", name)),
2020-09-02 03:10:54 +12:00
};
}
Common::print_time(start_time, SystemTime::now(), "delete_files".to_string());
2020-09-01 09:03:10 +12:00
}
2020-09-02 03:10:54 +12:00
/// Set included dir which needs to be relative, exists etc.
2020-09-27 03:52:13 +13:00
pub fn set_included_directory(&mut self, included_directory: String) {
self.directories.set_included_directory(included_directory, &mut self.text_messages);
}
}
impl Default for EmptyFolder {
fn default() -> Self {
Self::new()
2020-09-01 05:37:30 +12:00
}
2020-09-27 03:52:13 +13:00
}
2020-09-01 05:37:30 +12:00
2020-09-27 03:52:13 +13:00
impl DebugPrint for EmptyFolder {
#[allow(dead_code)]
#[allow(unreachable_code)]
2020-09-01 05:37:30 +12:00
fn debug_print(&self) {
#[cfg(not(debug_assertions))]
2020-09-18 17:32:37 +12:00
{
return;
2020-09-01 05:37:30 +12:00
}
println!("---------------DEBUG PRINT---------------");
println!("Number of all checked folders - {}", self.information.number_of_checked_folders);
println!("Number of empty folders - {}", self.information.number_of_empty_folders);
2020-09-27 03:52:13 +13:00
println!("Included directories - {:?}", self.directories.included_directories);
println!("-----------------------------------------");
2020-09-01 05:37:30 +12:00
}
2020-09-27 03:52:13 +13:00
}
impl SaveResults for EmptyFolder {
fn save_results_to_file(&mut self, file_name: &str) -> bool {
2020-09-01 05:37:30 +12:00
let start_time: SystemTime = SystemTime::now();
2020-09-27 03:52:13 +13:00
let file_name: String = match file_name {
"" => "results.txt".to_string(),
k => k.to_string(),
};
2020-09-01 05:37:30 +12:00
2020-09-27 03:52:13 +13:00
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;
2020-09-01 05:37:30 +12:00
}
2020-09-27 03:52:13 +13:00
};
2020-09-01 05:37:30 +12:00
2020-09-27 08:50:16 +13:00
match file.write_all(format!("Results of searching {:?} with excluded directories {:?}\n", self.directories.included_directories, self.directories.excluded_directories).as_bytes()) {
2020-09-27 03:52:13 +13:00
Ok(_) => (),
Err(_) => {
self.text_messages.errors.push("Failed to save results to file ".to_string() + file_name.as_str());
return false;
2020-09-01 05:37:30 +12:00
}
}
2020-09-27 03:52:13 +13:00
if !self.empty_folder_list.is_empty() {
file.write_all(b"-------------------------------------------------Empty folder list-------------------------------------------------\n").unwrap();
file.write_all(("Found ".to_string() + self.information.number_of_empty_folders.to_string().as_str() + " empty folders\n").as_bytes()).unwrap();
2020-09-27 03:52:13 +13:00
for name in self.empty_folder_list.keys() {
file.write_all((name.clone() + "\n").as_bytes()).unwrap();
2020-09-01 05:37:30 +12:00
}
2020-09-27 03:52:13 +13:00
} else {
file.write_all(b"Not found any empty folders.").unwrap();
2020-09-01 05:37:30 +12:00
}
2020-09-27 03:52:13 +13:00
Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string());
true
2020-09-01 05:37:30 +12:00
}
}
2020-09-27 03:52:13 +13:00
impl PrintResults for EmptyFolder {
/// Prints basic info about empty folders // TODO print better
fn print_results(&self) {
if !self.empty_folder_list.is_empty() {
println!("Found {} empty folders", self.empty_folder_list.len());
}
2020-09-27 08:50:16 +13:00
for name in self.empty_folder_list.keys() {
println!("{}", name);
2020-09-27 03:52:13 +13:00
}
}
}