diff --git a/czkawka_core/src/common_dir_traversal.rs b/czkawka_core/src/common_dir_traversal.rs index 262cdf1..b608ba9 100644 --- a/czkawka_core/src/common_dir_traversal.rs +++ b/czkawka_core/src/common_dir_traversal.rs @@ -63,7 +63,7 @@ pub enum ErrorType { /// Enum with values which show if folder is empty. /// In function "optimize_folders" automatically "Maybe" is changed to "Yes", so it is not necessary to put it here #[derive(Eq, PartialEq, Copy, Clone)] -enum FolderEmptiness { +pub(crate) enum FolderEmptiness { No, Maybe, } @@ -71,8 +71,8 @@ enum FolderEmptiness { /// Struct assigned to each checked folder with parent path(used to ignore parent if children are not empty) and flag which shows if folder is empty #[derive(Clone)] pub struct FolderEntry { - parent_path: Option, // Usable only when finding - is_empty: FolderEmptiness, + pub(crate) parent_path: Option, // Usable only when finding + pub(crate) is_empty: FolderEmptiness, pub modified_date: u64, } @@ -246,7 +246,7 @@ impl<'a, 'b, F> DirTraversalBuilder<'a, 'b, F> { collect: self.collect, directories: self.directories.expect("could not build"), excluded_items: self.excluded_items.expect("could not build"), - allowed_extensions: self.allowed_extensions.expect("could not build"), + allowed_extensions: self.allowed_extensions.unwrap_or_default(), recursive_search: self.recursive_search, } } @@ -277,6 +277,7 @@ where let mut folder_entries: BTreeMap = BTreeMap::new(); let start_time: SystemTime = SystemTime::now(); + // Add root folders into result (only for empty folder collection) let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector if self.collect == Collect::EmptyFolders { for dir in &self.root_dirs { diff --git a/czkawka_core/src/empty_folder.rs b/czkawka_core/src/empty_folder.rs index 90abeb8..8a42b0b 100644 --- a/czkawka_core/src/empty_folder.rs +++ b/czkawka_core/src/empty_folder.rs @@ -1,45 +1,18 @@ use std::collections::BTreeMap; -use std::fs::{File, Metadata}; +use std::fs; +use std::fs::File; use std::io::{BufWriter, Write}; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::Arc; -use std::thread::sleep; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use std::{fs, thread}; +use std::path::PathBuf; +use std::time::SystemTime; use crossbeam_channel::Receiver; use crate::common::Common; +use crate::common_dir_traversal::{Collect, DirTraversalBuilder, DirTraversalResult, FolderEmptiness, FolderEntry, ProgressData}; use crate::common_directory::Directories; use crate::common_items::ExcludedItems; use crate::common_messages::Messages; use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; -use crate::fl; -use crate::localizer::generate_translation_hashmap; - -#[derive(Debug)] -pub struct ProgressData { - pub current_stage: u8, - pub max_stage: u8, - pub folders_checked: usize, -} - -/// Enum with values which show if folder is empty. -/// In function "optimize_folders" automatically "Maybe" is changed to "Yes", so it is not necessary to put it here -#[derive(Eq, PartialEq, Copy, Clone)] -enum FolderEmptiness { - No, - Maybe, -} - -/// Struct assigned to each checked folder with parent path(used to ignore parent if children are not empty) and flag which shows if folder is empty -#[derive(Clone)] -pub struct FolderEntry { - parent_path: Option, // Usable only when finding - is_empty: FolderEmptiness, - pub modified_date: u64, -} /// Struct to store most basics info about all folder pub struct EmptyFolder { @@ -143,147 +116,41 @@ impl EmptyFolder { /// 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, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender>) -> bool { - let start_time: SystemTime = SystemTime::now(); - let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector - let mut folders_checked: BTreeMap = Default::default(); - - //// PROGRESS THREAD START - const LOOP_DURATION: u32 = 200; //in ms - let progress_thread_run = Arc::new(AtomicBool::new(true)); - - let atomic_folder_counter = Arc::new(AtomicUsize::new(0)); - - let progress_thread_handle = if let Some(progress_sender) = progress_sender { - let progress_send = progress_sender.clone(); - let progress_thread_run = progress_thread_run.clone(); - let atomic_folder_counter = atomic_folder_counter.clone(); - thread::spawn(move || loop { - progress_send - .unbounded_send(ProgressData { - current_stage: 0, - max_stage: 0, - folders_checked: atomic_folder_counter.load(Ordering::Relaxed) as usize, - }) - .unwrap(); - if !progress_thread_run.load(Ordering::Relaxed) { - break; - } - sleep(Duration::from_millis(LOOP_DURATION as u64)); - }) - } else { - thread::spawn(|| {}) - }; - //// PROGRESS THREAD END - - // 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()); - } - - while !folders_to_check.is_empty() { - if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { - // End thread which send info to gui - progress_thread_run.store(false, Ordering::Relaxed); - progress_thread_handle.join().unwrap(); - return false; + let result = DirTraversalBuilder::new() + .root_dirs(self.directories.included_directories.clone()) + .group_by(|_fe| ()) + .stop_receiver(stop_receiver) + .progress_sender(progress_sender) + .directories(self.directories.clone()) + .excluded_items(self.excluded_items.clone()) + .collect(Collect::EmptyFolders) + .max_stage(0) + .build() + .run(); + match result { + DirTraversalResult::SuccessFiles { .. } => { + unreachable!() } - let current_folder = folders_to_check.pop().unwrap(); - // Checked folder may be deleted or we may not have permissions to open it so we assume that this folder is not be empty - // Read current dir childrens - let read_dir = match fs::read_dir(¤t_folder) { - Ok(t) => t, - Err(e) => { - self.text_messages.warnings.push(fl!( - "core_cannot_open_dir", - generate_translation_hashmap(vec![("dir", current_folder.display().to_string()), ("reason", e.to_string())]) - )); - continue; + DirTraversalResult::SuccessFolders { + start_time, + folder_entries, + warnings, + } => { + // We need to set empty folder list + #[allow(unused_mut)] // Used is later by Windows build + for (mut name, folder_entry) in folder_entries { + if folder_entry.is_empty != FolderEmptiness::No { + self.empty_folder_list.insert(name, folder_entry); + } } - }; - // Check every sub folder/file/link etc. - 'dir: for entry in read_dir { - let entry_data = match entry { - Ok(t) => t, - Err(e) => { - self.text_messages.warnings.push(fl!( - "core_cannot_read_entry_dir", - generate_translation_hashmap(vec![("dir", current_folder.display().to_string()), ("reason", e.to_string())]) - )); - continue 'dir; - } - }; - let metadata: Metadata = match entry_data.metadata() { - Ok(t) => t, - Err(e) => { - self.text_messages.warnings.push(fl!( - "core_cannot_read_metadata_dir", - generate_translation_hashmap(vec![("dir", current_folder.display().to_string()), ("reason", e.to_string())]) - )); - continue 'dir; - } - }; - if metadata.is_dir() { - atomic_folder_counter.fetch_add(1, Ordering::Relaxed); - let next_folder = current_folder.join(entry_data.file_name()); - if self.excluded_items.is_excluded(&next_folder) || self.directories.is_excluded(&next_folder) { - set_as_not_empty_folder(&mut folders_checked, ¤t_folder); - continue 'dir; - } - folders_to_check.push(next_folder.clone()); - folders_checked.insert( - next_folder.clone(), - FolderEntry { - parent_path: Some(current_folder.clone()), - is_empty: FolderEmptiness::Maybe, - modified_date: match metadata.modified() { - Ok(t) => match t.duration_since(UNIX_EPOCH) { - Ok(d) => d.as_secs(), - Err(_inspected) => { - self.text_messages.warnings.push(fl!( - "core_folder_modified_before_epoch", - generate_translation_hashmap(vec![("name", current_folder.display().to_string())]) - )); - 0 - } - }, - Err(e) => { - self.text_messages.warnings.push(fl!( - "core_folder_no_modification_date", - generate_translation_hashmap(vec![("name", current_folder.display().to_string()), ("reason", e.to_string())]) - )); - 0 - } - }, - }, - ); - } else { - set_as_not_empty_folder(&mut folders_checked, ¤t_folder) - } + self.text_messages.warnings.extend(warnings); + + Common::print_time(start_time, SystemTime::now(), "check_for_empty_folder".to_string()); + true } + DirTraversalResult::Stopped => false, } - // End thread which send info to gui - progress_thread_run.store(false, Ordering::Relaxed); - progress_thread_handle.join().unwrap(); - - // We need to set empty folder list - #[allow(unused_mut)] // Used is later by Windows build - for (mut name, folder_entry) in folders_checked { - if folder_entry.is_empty != FolderEmptiness::No { - self.empty_folder_list.insert(name, folder_entry); - } - } - - Common::print_time(start_time, SystemTime::now(), "check_for_empty_folder".to_string()); - true } /// Deletes earlier found empty folders @@ -306,22 +173,6 @@ impl EmptyFolder { } } -fn set_as_not_empty_folder(folders_checked: &mut BTreeMap, current_folder: &Path) { - // Not folder so it may be a file or symbolic link so it isn't empty - folders_checked.get_mut(current_folder).unwrap().is_empty = FolderEmptiness::No; - let mut d = folders_checked.get_mut(current_folder).unwrap(); - // Loop to recursively set as non empty this and all his parent folders - loop { - d.is_empty = FolderEmptiness::No; - if d.parent_path != None { - let cf = d.parent_path.clone().unwrap(); - d = folders_checked.get_mut(&cf).unwrap(); - } else { - break; - } - } -} - impl Default for EmptyFolder { fn default() -> Self { Self::new() diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 1c1f42f..cd6220a 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -33,7 +33,7 @@ pub fn connect_button_search( glib_stop_sender: Sender, futures_sender_duplicate_files: futures::channel::mpsc::UnboundedSender, futures_sender_empty_files: futures::channel::mpsc::UnboundedSender, - futures_sender_empty_folder: futures::channel::mpsc::UnboundedSender, + futures_sender_empty_folder: futures::channel::mpsc::UnboundedSender, futures_sender_big_file: futures::channel::mpsc::UnboundedSender, futures_sender_same_music: futures::channel::mpsc::UnboundedSender, futures_sender_similar_images: futures::channel::mpsc::UnboundedSender, diff --git a/czkawka_gui/src/connect_progress_window.rs b/czkawka_gui/src/connect_progress_window.rs index d241f3a..e83dae7 100644 --- a/czkawka_gui/src/connect_progress_window.rs +++ b/czkawka_gui/src/connect_progress_window.rs @@ -1,7 +1,7 @@ use futures::StreamExt; use gtk::prelude::*; -use czkawka_core::{big_file, broken_files, common_dir_traversal, empty_folder, same_music, similar_images, similar_videos, temporary}; +use czkawka_core::{big_file, broken_files, common_dir_traversal, same_music, similar_images, similar_videos, temporary}; use crate::fl; use crate::gui_data::GuiData; @@ -13,7 +13,7 @@ pub fn connect_progress_window( gui_data: &GuiData, mut futures_receiver_duplicate_files: futures::channel::mpsc::UnboundedReceiver, mut futures_receiver_empty_files: futures::channel::mpsc::UnboundedReceiver, - mut futures_receiver_empty_folder: futures::channel::mpsc::UnboundedReceiver, + mut futures_receiver_empty_folder: futures::channel::mpsc::UnboundedReceiver, mut futures_receiver_big_files: futures::channel::mpsc::UnboundedReceiver, mut futures_receiver_same_music: futures::channel::mpsc::UnboundedReceiver, mut futures_receiver_similar_images: futures::channel::mpsc::UnboundedReceiver, @@ -147,7 +147,7 @@ pub fn connect_progress_window( while let Some(item) = futures_receiver_empty_folder.next().await { label_stage.set_text(&fl!( "progress_scanning_empty_folders", - generate_translation_hashmap(vec![("folder_number", item.folders_checked.to_string())]) + generate_translation_hashmap(vec![("folder_number", item.entries_checked.to_string())]) )); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index 36788d3..1ce266d 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -93,8 +93,8 @@ fn main() { futures::channel::mpsc::UnboundedReceiver, ) = futures::channel::mpsc::unbounded(); let (futures_sender_empty_folder, futures_receiver_empty_folder): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, ) = futures::channel::mpsc::unbounded(); let (futures_sender_big_file, futures_receiver_big_file): ( futures::channel::mpsc::UnboundedSender,