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

314 lines
12 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};
use crossbeam_channel::Receiver;
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-10-01 17:53:10 +13:00
use std::time::{SystemTime, UNIX_EPOCH};
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-10-01 17:53:10 +13:00
pub modified_date: u64,
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,
stopped_search: bool,
2020-09-01 05:37:30 +12:00
}
2020-09-18 17:32:37 +12:00
/// Info struck with helpful information's about results
#[derive(Default)]
2020-09-18 17:32:37 +12:00
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() -> Self {
Default::default()
2020-09-18 17:32:37 +12:00
}
}
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
pub fn new() -> Self {
Self {
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(),
stopped_search: false,
2020-09-01 05:37:30 +12:00
}
}
pub fn get_stopped_search(&self) -> bool {
self.stopped_search
}
pub const fn get_empty_folder_list(&self) -> &BTreeMap<String, FolderEntry> {
2020-09-22 21:24:55 +12:00
&self.empty_folder_list
}
pub const fn get_text_messages(&self) -> &Messages {
&self.text_messages
2020-09-18 17:32:37 +12:00
}
pub const fn get_information(&self) -> &Info {
2020-09-22 21:24:55 +12:00
&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
pub fn find_empty_folders(&mut self, rx: Option<&Receiver<()>>) {
2020-09-27 03:52:13 +13:00
self.directories.optimize_directories(true, &mut self.text_messages);
if !self.check_for_empty_folders(rx) {
self.stopped_search = true;
return;
}
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
fn check_for_empty_folders(&mut self, rx: Option<&Receiver<()>>) -> 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
// Add root folders for finding
for id in &self.directories.included_directories {
folders_checked.insert(
id.clone(),
FolderEntry {
parent_path: None,
is_empty: FolderEmptiness::Maybe,
modified_date: 0,
},
);
folders_to_check.push(id.clone());
2020-09-01 09:03:10 +12:00
}
let mut current_folder: String;
let mut next_folder: String;
while !folders_to_check.is_empty() {
if rx.is_some() && rx.unwrap().try_recv().is_ok() {
return false;
}
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
+ match &entry_data.file_name().into_string() {
Ok(t) => t,
Err(_) => continue,
}
+ "/";
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) => match t.duration_since(UNIX_EPOCH) {
Ok(d) => d.as_secs(),
Err(_) => {
self.text_messages.warnings.push(format!("Folder {} seems to be modified before Unix Epoch.", current_folder));
0
}
},
2020-09-22 21:24:55 +12:00
Err(_) => {
self.text_messages.warnings.push(format!("Failed to read modification date of folder {}", current_folder));
2020-10-01 17:53:10 +13:00
continue;
2020-09-22 21:24:55 +12:00
}
},
},
);
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
// We need to set empty folder list
for (name, folder_entry) in folders_checked {
if folder_entry.is_empty != FolderEmptiness::No {
self.empty_folder_list.insert(name, folder_entry);
2020-09-02 03:10:54 +12:00
}
}
Common::print_time(start_time, SystemTime::now(), "check_for_empty_folder".to_string());
true
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
}
}
}