From 4c72dd2cedabb03562c6517989084648f263578a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Wed, 22 Dec 2021 19:47:26 +0100 Subject: [PATCH 1/9] Add reference folder --- Changelog.md | 2 +- czkawka_core/src/common_directory.rs | 19 ++++++ czkawka_gui/src/compute_results.rs | 58 ++++++------------ czkawka_gui/src/connect_button_search.rs | 3 + .../src/connect_selection_of_directories.rs | 9 ++- czkawka_gui/src/create_tree_view.rs | 61 ++++++++++++++++--- czkawka_gui/src/gui_main_notebook.rs | 1 + czkawka_gui/src/gui_upper_notebook.rs | 10 +++ czkawka_gui/src/help_functions.rs | 14 +++-- czkawka_gui/src/initialize_gui.rs | 12 ++-- czkawka_gui/src/saving_loading.rs | 19 ++++-- i18n/en/czkawka_gui.ftl | 39 ++++++------ i18n/pl/czkawka_gui.ftl | 2 + 13 files changed, 165 insertions(+), 84 deletions(-) diff --git a/Changelog.md b/Changelog.md index c223e6465..6489430a2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,7 +11,7 @@ - Option to not remove cache from non existent files(e.g. from unplugged pendrive) - [#472](https://github.com/qarmin/czkawka/pull/472) - Add multiple tooltips with helpful messages - [#472](https://github.com/qarmin/czkawka/pull/472) - Allow to cache prehash - [#477](https://github.com/qarmin/czkawka/pull/477) -- Improve custom selecting of records(allows to use Rust red regex) - [#489](https://github.com/qarmin/czkawka/pull/478) +- Improve custom selecting of records(allows to use Rust regex) - [#489](https://github.com/qarmin/czkawka/pull/478) - Remove support for finding zeroed files - [#461](https://github.com/qarmin/czkawka/pull/461) - Remove HashMB mode - [#476](https://github.com/qarmin/czkawka/pull/476) - Approximate comparison of music - [#483](https://github.com/qarmin/czkawka/pull/483) diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index d0271656f..3aa7e4f3f 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -8,6 +8,7 @@ use crate::common_messages::Messages; pub struct Directories { pub excluded_directories: Vec, pub included_directories: Vec, + pub reference_directories: Vec, } impl Directories { @@ -15,6 +16,24 @@ impl Directories { Default::default() } + pub fn set_reference_directory(&mut self, reference_directory: Vec, text_messages: &mut Messages) { + let mut reference_directory = reference_directory; + + if cfg!(target_family = "windows") { + reference_directory = reference_directory.iter().map(Common::normalize_windows_path).collect(); + } + + // TODO silent this + for i in reference_directory { + if self.included_directories.contains(&i) { + text_messages.messages.push(format!("REFERENCE {:?}", i)); + self.reference_directories.push(i); + } else { + text_messages.messages.push(format!("NON REFERENCE {:?}", i)); + } + } + } + /// Setting included directories, at least one must be provided pub fn set_included_directory(&mut self, included_directory: Vec, text_messages: &mut Messages) -> bool { let start_time: SystemTime = SystemTime::now(); diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index 79266247e..9e12b85c5 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -142,7 +142,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< CheckingMethod::Name => { let btreemap = df.get_files_sorted_by_names(); - for (name, vector) in btreemap.iter().rev() { + for (_name, vector) in btreemap.iter().rev() { // Sort let vector = if vector.len() >= 2 { let mut vector = vector.clone(); @@ -155,10 +155,11 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< vector.clone() }; - let values: [(u32, &dyn ToValue); 8] = [ + let values: [(u32, &dyn ToValue); 9] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &false), (ColumnsDuplicates::SelectionButton as u32, &false), - (ColumnsDuplicates::Name as u32, &name), + (ColumnsDuplicates::Size as u32, (&"".to_string())), + (ColumnsDuplicates::Name as u32, (&"".to_string())), (ColumnsDuplicates::Path as u32, (&(format!("{} results", vector.len())))), (ColumnsDuplicates::Modification as u32, (&"".to_string())), // No text in 3 column (ColumnsDuplicates::ModificationAsSecs as u32, (&(0))), // Not used here @@ -169,9 +170,10 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< list_store.set(&list_store.append(), &values); for entry in vector { let (directory, file) = split_path(&entry.path); - let values: [(u32, &dyn ToValue); 8] = [ + let values: [(u32, &dyn ToValue); 9] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &true), (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), (ColumnsDuplicates::Name as u32, &file), (ColumnsDuplicates::Path as u32, &directory), ( @@ -193,7 +195,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< CheckingMethod::Hash => { let btreemap = df.get_files_sorted_by_hash(); - for (size, vectors_vector) in btreemap.iter().rev() { + for (_size, vectors_vector) in btreemap.iter().rev() { for vector in vectors_vector { // Sort let vector = if vector.len() >= 2 { @@ -207,23 +209,12 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< vector.clone() }; - let values: [(u32, &dyn ToValue); 8] = [ + let values: [(u32, &dyn ToValue); 9] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &false), (ColumnsDuplicates::SelectionButton as u32, &false), - ( - ColumnsDuplicates::Name as u32, - &(format!("{} x {} ({} {})", vector.len(), size.file_size(options::BINARY).unwrap(), size, fl!("general_bytes"))), - ), - ( - ColumnsDuplicates::Path as u32, - &(format!( - "{} ({} {}) {}", - ((vector.len() - 1) as u64 * *size as u64).file_size(options::BINARY).unwrap(), - (vector.len() - 1) as u64 * *size as u64, - fl!("general_bytes"), - fl!("general_lost") - )), - ), + (ColumnsDuplicates::Size as u32, (&"".to_string())), + (ColumnsDuplicates::Name as u32, (&"".to_string())), + (ColumnsDuplicates::Path as u32, (&"".to_string())), (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), @@ -234,9 +225,10 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< for entry in vector { let (directory, file) = split_path(&entry.path); - let values: [(u32, &dyn ToValue); 8] = [ + let values: [(u32, &dyn ToValue); 9] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &true), (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), (ColumnsDuplicates::Name as u32, &file), (ColumnsDuplicates::Path as u32, &directory), ( @@ -256,7 +248,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< CheckingMethod::Size => { let btreemap = df.get_files_sorted_by_size(); - for (size, vector) in btreemap.iter().rev() { + for (_size, vector) in btreemap.iter().rev() { // Sort let vector = if vector.len() >= 2 { let mut vector = vector.clone(); @@ -268,23 +260,12 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< } else { vector.clone() }; - let values: [(u32, &dyn ToValue); 8] = [ + let values: [(u32, &dyn ToValue); 9] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &false), (ColumnsDuplicates::SelectionButton as u32, &false), - ( - ColumnsDuplicates::Name as u32, - &(format!("{} x {} ({} {})", vector.len(), size.file_size(options::BINARY).unwrap(), size, fl!("general_bytes"))), - ), - ( - ColumnsDuplicates::Path as u32, - &(format!( - "{} ({} {}) {}", - ((vector.len() - 1) as u64 * *size as u64).file_size(options::BINARY).unwrap(), - (vector.len() - 1) as u64 * *size as u64, - fl!("general_bytes"), - fl!("general_lost") - )), - ), + (ColumnsDuplicates::Size as u32, (&"".to_string())), + (ColumnsDuplicates::Name as u32, (&"".to_string())), + (ColumnsDuplicates::Path as u32, (&"".to_string())), (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), // Not used here (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), @@ -294,9 +275,10 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< list_store.set(&list_store.append(), &values); for entry in vector { let (directory, file) = split_path(&entry.path); - let values: [(u32, &dyn ToValue); 8] = [ + let values: [(u32, &dyn ToValue); 9] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &true), (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), (ColumnsDuplicates::Name as u32, &file), (ColumnsDuplicates::Path as u32, &directory), ( diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index f367ea286..c9713b0a8 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -72,6 +72,7 @@ pub fn connect_button_search( let entry_settings_prehash_cache_file_minimal_size = gui_data.settings.entry_settings_prehash_cache_file_minimal_size.clone(); let grid_progress_stages = gui_data.progress_window.grid_progress_stages.clone(); let image_preview_similar_images = gui_data.main_notebook.image_preview_similar_images.clone(); + let image_preview_duplicates = gui_data.main_notebook.image_preview_duplicates.clone(); let label_stage = gui_data.progress_window.label_stage.clone(); let notebook_main = gui_data.main_notebook.notebook_main.clone(); let notebook_upper = gui_data.upper_notebook.notebook_upper.clone(); @@ -133,6 +134,8 @@ pub fn connect_button_search( match to_notebook_main_enum(notebook_main.current_page().unwrap()) { NotebookMainEnum::Duplicate => { + image_preview_duplicates.hide(); + label_stage.show(); grid_progress_stages.show_all(); window_progress.resize(1, 1); diff --git a/czkawka_gui/src/connect_selection_of_directories.rs b/czkawka_gui/src/connect_selection_of_directories.rs index cde62e013..61dcd7840 100644 --- a/czkawka_gui/src/connect_selection_of_directories.rs +++ b/czkawka_gui/src/connect_selection_of_directories.rs @@ -8,7 +8,7 @@ use czkawka_core::common::Common; use czkawka_core::fl; use crate::gui_data::GuiData; -use crate::help_functions::{get_dialog_box_child, get_list_store, ColumnsDirectory}; +use crate::help_functions::{get_dialog_box_child, get_list_store, ColumnsIncludedDirectory}; pub fn connect_selection_of_directories(gui_data: &GuiData) { // Add manually directory @@ -120,7 +120,10 @@ fn add_chosen_directories(window_main: &Window, tree_view: &TreeView, excluded_i let list_store = get_list_store(&tree_view); for file_entry in &folders { - let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &file_entry.to_string_lossy().to_string())]; + let values: [(u32, &dyn ToValue); 2] = [ + (ColumnsIncludedDirectory::Path as u32, &file_entry.to_string_lossy().to_string()), + (ColumnsIncludedDirectory::ReferenceButton as u32, &false), + ]; list_store.set(&list_store.append(), &values); } } @@ -154,7 +157,7 @@ fn add_manually_directories(window_main: &Window, tree_view: &TreeView) { if !text.is_empty() { let list_store = get_list_store(&tree_view); - let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &text)]; + let values: [(u32, &dyn ToValue); 2] = [(ColumnsIncludedDirectory::Path as u32, &text), (ColumnsIncludedDirectory::ReferenceButton as u32, &false)]; list_store.set(&list_store.append(), &values); } } diff --git a/czkawka_gui/src/create_tree_view.rs b/czkawka_gui/src/create_tree_view.rs index 25cb99ab5..04072e1f0 100644 --- a/czkawka_gui/src/create_tree_view.rs +++ b/czkawka_gui/src/create_tree_view.rs @@ -3,6 +3,46 @@ use gtk::TreeViewColumn; use crate::help_functions::*; +// When adding new column do not forget to update translations + +pub fn create_tree_view_included_directories(tree_view: >k::TreeView) { + let model = get_list_store(tree_view); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.set_title("Folders to check"); + column.pack_start(&renderer, true); + column.add_attribute(&renderer, "text", ColumnsIncludedDirectory::Path as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererToggle::new(); + renderer.connect_toggled(move |_r, path| { + let iter = model.iter(&path).unwrap(); + let mut fixed = model + .value(&iter, ColumnsIncludedDirectory::ReferenceButton as i32) + .get::() + .unwrap_or_else(|err| panic!("ListStore value missing at path {:?}: {}", path, err)); + fixed = !fixed; + model.set_value(&iter, ColumnsIncludedDirectory::ReferenceButton as u32, &fixed.to_value()); + }); + renderer.set_activatable(true); + let column = gtk::TreeViewColumn::new(); + column.set_title("Reference folder"); + column.pack_start(&renderer, true); + column.add_attribute(&renderer, "active", ColumnsIncludedDirectory::ReferenceButton as i32); + tree_view.append_column(&column); +} + +pub fn create_tree_view_excluded_directories(tree_view: >k::TreeView) { + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.add_attribute(&renderer, "text", ColumnsExcludedDirectory::Path as i32); + tree_view.append_column(&column); + + tree_view.set_headers_visible(false); +} + pub fn create_tree_view_duplicates(tree_view: >k::TreeView) { let model = get_list_store(tree_view); @@ -25,6 +65,17 @@ pub fn create_tree_view_duplicates(tree_view: >k::TreeView) { column.add_attribute(&renderer, "cell-background", ColumnsDuplicates::Color as i32); tree_view.append_column(&column); + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Size"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsDuplicates::Size as i32); + column.add_attribute(&renderer, "background", ColumnsDuplicates::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsDuplicates::TextColor as i32); + tree_view.append_column(&column); + let renderer = gtk::CellRendererText::new(); let column: gtk::TreeViewColumn = TreeViewColumn::new(); column.pack_start(&renderer, true); @@ -443,16 +494,6 @@ pub fn create_tree_view_similar_videos(tree_view: >k::TreeView) { tree_view.set_vexpand(true); } -pub fn create_tree_view_directories(tree_view: >k::TreeView) { - let renderer = gtk::CellRendererText::new(); - let column: gtk::TreeViewColumn = TreeViewColumn::new(); - column.pack_start(&renderer, true); - column.add_attribute(&renderer, "text", ColumnsDirectory::Path as i32); - tree_view.append_column(&column); - - tree_view.set_headers_visible(false); -} - pub fn create_tree_view_same_music(tree_view: >k::TreeView) { let model = get_list_store(tree_view); diff --git a/czkawka_gui/src/gui_main_notebook.rs b/czkawka_gui/src/gui_main_notebook.rs index 59282c15c..542e0372a 100644 --- a/czkawka_gui/src/gui_main_notebook.rs +++ b/czkawka_gui/src/gui_main_notebook.rs @@ -406,6 +406,7 @@ impl GuiMainNotebook { // Change names of columns let names_of_columns = [ vec![ + fl!("main_tree_view_column_size"), fl!("main_tree_view_column_file_name"), fl!("main_tree_view_column_path"), fl!("main_tree_view_column_modification"), diff --git a/czkawka_gui/src/gui_upper_notebook.rs b/czkawka_gui/src/gui_upper_notebook.rs index db8efcb98..b6c975557 100644 --- a/czkawka_gui/src/gui_upper_notebook.rs +++ b/czkawka_gui/src/gui_upper_notebook.rs @@ -164,5 +164,15 @@ impl GuiUpperNotebook { .unwrap() .set_text(&fl_thing); } + + let names_of_columns = [ + vec![fl!("upper_tree_view_included_folder_column_title"), fl!("upper_tree_view_included_reference_column_title")], // Included folders + ]; + + for (notebook_index, tree_view) in [self.tree_view_included_directories.clone()].iter().enumerate() { + for (column_index, column) in tree_view.columns().iter().enumerate() { + column.set_title(&names_of_columns[notebook_index][column_index]); + } + } } } diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index f5910cdc2..7c2269a98 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -69,8 +69,8 @@ pub static NOTEBOOKS_INFOS: [NotebookObject; NUMBER_OF_NOTEBOOK_MAIN_TABS] = [ column_selection: ColumnsDuplicates::SelectionButton as i32, column_color: Some(ColumnsDuplicates::Color as i32), column_dimensions: None, - column_size: None, - column_size_as_bytes: None, + column_size: None, // Do not add, useless in hash and size mode + column_size_as_bytes: None, // Do not add, useless in hash and size mode column_modification_as_secs: Some(ColumnsDuplicates::ModificationAsSecs as i32), }, NotebookObject { @@ -209,6 +209,7 @@ pub enum ColumnsDuplicates { // Columns for duplicate treeview ActivatableSelectButton = 0, SelectionButton, + Size, Name, Path, Modification, @@ -226,8 +227,13 @@ pub enum ColumnsEmptyFolders { ModificationAsSecs, } -pub enum ColumnsDirectory { - // Columns for Included and Excluded Directories in upper Notebook +pub enum ColumnsIncludedDirectory { + // Columns for Included Directories in upper Notebook + Path = 0, + ReferenceButton, +} +pub enum ColumnsExcludedDirectory { + // Columns for Excluded Directories in upper Notebook Path = 0, } diff --git a/czkawka_gui/src/initialize_gui.rs b/czkawka_gui/src/initialize_gui.rs index 94708e9f8..56414edec 100644 --- a/czkawka_gui/src/initialize_gui.rs +++ b/czkawka_gui/src/initialize_gui.rs @@ -124,9 +124,10 @@ pub fn initialize_gui(gui_data: &mut GuiData) { let image_preview = gui_data.main_notebook.image_preview_duplicates.clone(); image_preview.hide(); - let col_types: [glib::types::Type; 8] = [ + let col_types: [glib::types::Type; 9] = [ glib::types::Type::BOOL, // ActivatableSelectButton glib::types::Type::BOOL, // SelectionButton + glib::types::Type::STRING, // Size glib::types::Type::STRING, // Name glib::types::Type::STRING, // Path glib::types::Type::STRING, // Modification @@ -397,13 +398,16 @@ pub fn initialize_gui(gui_data: &mut GuiData) { let tree_view = gui_data.upper_notebook.tree_view_included_directories.clone(); let evk = gui_data.upper_notebook.evk_tree_view_included_directories.clone(); - let col_types: [glib::types::Type; 1] = [glib::types::Type::STRING]; + let col_types: [glib::types::Type; 2] = [ + glib::types::Type::STRING, // Path + glib::types::Type::BOOL, // ReferenceButton + ]; let list_store: gtk::ListStore = gtk::ListStore::new(&col_types); tree_view.set_model(Some(&list_store)); tree_view.selection().set_mode(SelectionMode::Multiple); - create_tree_view_directories(&tree_view); + create_tree_view_included_directories(&tree_view); scrolled_window.add(&tree_view); scrolled_window.show_all(); @@ -433,7 +437,7 @@ pub fn initialize_gui(gui_data: &mut GuiData) { tree_view.set_model(Some(&list_store)); tree_view.selection().set_mode(SelectionMode::Multiple); - create_tree_view_directories(&tree_view); + create_tree_view_excluded_directories(&tree_view); scrolled_window.add(&tree_view); scrolled_window.show_all(); diff --git a/czkawka_gui/src/saving_loading.rs b/czkawka_gui/src/saving_loading.rs index 4b57ba69d..264159a52 100644 --- a/czkawka_gui/src/saving_loading.rs +++ b/czkawka_gui/src/saving_loading.rs @@ -57,7 +57,8 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb let list_store = get_list_store(&tree_view_included_directories); if let Some(iter) = list_store.iter_first() { loop { - data_to_save.push(list_store.value(&iter, ColumnsDirectory::Path as i32).get::().unwrap()); + // TODO maybe save also here reference directories? + data_to_save.push(list_store.value(&iter, ColumnsIncludedDirectory::Path as i32).get::().unwrap()); if !list_store.iter_next(&iter) { break; } @@ -70,7 +71,7 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb let list_store = get_list_store(&tree_view_excluded_directories); if let Some(iter) = list_store.iter_first() { loop { - data_to_save.push(list_store.value(&iter, ColumnsDirectory::Path as i32).get::().unwrap()); + data_to_save.push(list_store.value(&iter, ColumnsExcludedDirectory::Path as i32).get::().unwrap()); if !list_store.iter_next(&iter) { break; } @@ -682,7 +683,10 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb list_store.clear(); for directory in included_directories { - let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &directory)]; + let values: [(u32, &dyn ToValue); 2] = [ + (ColumnsIncludedDirectory::Path as u32, &directory), + (ColumnsIncludedDirectory::ReferenceButton as u32, &false), + ]; list_store.set(&list_store.append(), &values); } @@ -692,7 +696,7 @@ pub fn load_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb list_store.clear(); for directory in excluded_directories { - let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &directory)]; + let values: [(u32, &dyn ToValue); 1] = [(ColumnsExcludedDirectory::Path as u32, &directory)]; list_store.set(&list_store.append(), &values); } @@ -782,7 +786,10 @@ pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNoteb } }; - let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, ¤t_dir)]; + let values: [(u32, &dyn ToValue); 2] = [ + (ColumnsIncludedDirectory::Path as u32, ¤t_dir), + (ColumnsIncludedDirectory::ReferenceButton as u32, &false), + ]; list_store.set(&list_store.append(), &values); } // Resetting excluded directories @@ -792,7 +799,7 @@ pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNoteb list_store.clear(); if cfg!(target_family = "unix") { for i in ["/proc", "/dev", "/sys", "/run", "/snap"].iter() { - let values: [(u32, &dyn ToValue); 1] = [(ColumnsDirectory::Path as u32, &i)]; + let values: [(u32, &dyn ToValue); 1] = [(ColumnsExcludedDirectory::Path as u32, &i)]; list_store.set(&list_store.append(), &values); } } diff --git a/i18n/en/czkawka_gui.ftl b/i18n/en/czkawka_gui.ftl index 9e379b769..1c722d405 100644 --- a/i18n/en/czkawka_gui.ftl +++ b/i18n/en/czkawka_gui.ftl @@ -117,8 +117,11 @@ check_button_general_same_size_tooltip = Ignore from results, files which have i main_label_size_bytes_tooltip = Size of files which will be used in scan # Upper window +upper_tree_view_included_folder_column_title = Folders to Search +upper_tree_view_included_reference_column_title = Reference Folders + upper_recursive_button = Recursive -upper_recursive_button_tooltip = If selected, search also for files which are not placed directly under chosen folders +upper_recursive_button_tooltip = If selected, search also for files which are not placed directly under chosen folders. upper_manual_add_included_button = Manual Add upper_add_included_button = Add @@ -127,19 +130,19 @@ upper_manual_add_excluded_button = Manual Add upper_add_excluded_button = Add upper_remove_excluded_button = Remove -upper_manual_add_included_button_tooltip = Allows to add directory name to search by hand -upper_add_included_button_tooltip = Add new directory to search -upper_remove_included_button_tooltip = Delete directory from search -upper_manual_add_excluded_button_tooltip = Allows to add excluded directory name by hand -upper_add_excluded_button_tooltip = Add directory to be excluded in search -upper_remove_excluded_button_tooltip = Delete directory from excluded +upper_manual_add_included_button_tooltip = Allows to add directory name to search by hand. +upper_add_included_button_tooltip = Add new directory to search. +upper_remove_included_button_tooltip = Delete directory from search. +upper_manual_add_excluded_button_tooltip = Allows to add excluded directory name by hand. +upper_add_excluded_button_tooltip = Add directory to be excluded in search. +upper_remove_excluded_button_tooltip = Delete directory from excluded. upper_notebook_items_configuration = Items Configuration upper_notebook_excluded_directories = Excluded Directories upper_notebook_included_directories = Included Directories upper_allowed_extensions_tooltip = - Allowed extensions must be separated by commas(by default all are available) + Allowed extensions must be separated by commas(by default all are available). Macros IMAGE, VIDEO, MUSIC, TEXT which adds multiple extensions at once are also available. @@ -181,7 +184,7 @@ popover_custom_name_check_button_entry_tooltip = popover_custom_regex_check_button_entry_tooltip = Allows to select records by specified Regex. - With this mode, searched text is Path with Name + With this mode, searched text is Path with Name. Example usage: /usr/bin/ziemniak.txt can be found with /ziem[a-z]+ @@ -216,9 +219,9 @@ bottom_symlink_button = Symlink bottom_hardlink_button = Hardlink bottom_move_button = Move -bottom_search_button_tooltip = Start to search for files/folders +bottom_search_button_tooltip = Start to search for files/folders. bottom_select_button_tooltip = Selects records. Only selected files/folders can be later processed. -bottom_delete_button_tooltip = Delete selected files/folders +bottom_delete_button_tooltip = Delete selected files/folders. bottom_save_button_tooltip = Save data about search to file bottom_symlink_button_tooltip = Creates symbolic links. @@ -397,7 +400,7 @@ progress_all_stages = All Stages:{" "} # Saving loading saving_loading_saving_success = Saved configuration to file -saving_loading_reset_configuration = Current configuration was cleared. +saving_loading_reset_configuration = Current configuration was cleared saving_loading_loading_success = Properly loaded configuration from file # Invalid symlinks @@ -438,7 +441,7 @@ hard_sym_link_label = Are you sure that you want to link this files? move_folder_failed = Failed to move folder {$name}, reason {$reason} move_file_failed = Failed to move file {$name}, reason {$reason} move_files_title_dialog = Choose folder to which you want to move duplicated files -move_files_choose_more_than_1_path = Only 1 path must be selected to be able to copy there duplicated files, selected {$path_number} +move_files_choose_more_than_1_path = Only 1 path must be selected to be able to copy there duplicated files, selected {$path_number}. move_stats = Properly moved {$num_files}/{$all_files} items save_results_to_file = Saved results to file {$name} @@ -460,8 +463,8 @@ cache_clear_message_label_3 = This may speedup a little loading/saving to cache. cache_clear_message_label_4 = WARNING: Operation will remove all cached data from unplugged external drives, so hash will need to be generated again. # Show preview -preview_temporary_file = Failed to open temporary image file {$name}, reason {$reason} -preview_0_size = Cannot create preview of image {$name}, with 0 width or height -preview_temporary_image_save = Failed to save temporary image file to {$name}, reason {$reason} -preview_temporary_image_remove = Failed to delete temporary image file {$name}, reason {$reason} -preview_failed_to_create_cache_dir = Failed to create dir {$name} needed by image preview, reason {$reason} +preview_temporary_file = Failed to open temporary image file {$name}, reason {$reason}. +preview_0_size = Cannot create preview of image {$name}, with 0 width or height. +preview_temporary_image_save = Failed to save temporary image file to {$name}, reason {$reason}. +preview_temporary_image_remove = Failed to delete temporary image file {$name}, reason {$reason}. +preview_failed_to_create_cache_dir = Failed to create dir {$name} needed by image preview, reason {$reason}. diff --git a/i18n/pl/czkawka_gui.ftl b/i18n/pl/czkawka_gui.ftl index 86e850135..aaf0c691d 100644 --- a/i18n/pl/czkawka_gui.ftl +++ b/i18n/pl/czkawka_gui.ftl @@ -97,6 +97,8 @@ check_button_general_same_size = Ignoruj identyczny rozmiar check_button_general_same_size_tooltip = Wyrzuca z wyników skanowania pliki, które posiadają identyczny rozmiar, po to by w wynikach zostały tylko niemal identyczne rekordy. main_label_size_bytes_tooltip = Rozmiar plików które będą zawarte przy przeszukiwaniu # Upper window +upper_tree_view_included_folder_column_title = Foldery do Przeszukania +upper_tree_view_included_reference_column_title = Źródłowy Folder upper_recursive_button = Rekursywnie upper_recursive_button_tooltip = Jeśli zaznaczony, szuka plików i folderów również w katalogach wewnątrz, nawet jeśli nie znajdują się one bezpośrednio w tym folderze. upper_manual_add_included_button = Ręcznie Dodaj From c1ef3059d52525e329c54a95d0bcf3d5cc0f2627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Wed, 22 Dec 2021 20:53:37 +0100 Subject: [PATCH 2/9] Basic tree view cleaning --- czkawka_gui/src/connect_button_delete.rs | 2 +- czkawka_gui/src/connect_button_hardlink.rs | 2 +- czkawka_gui/src/connect_button_move.rs | 2 +- czkawka_gui/src/help_functions.rs | 130 +++++++++++++++------ 4 files changed, 95 insertions(+), 41 deletions(-) diff --git a/czkawka_gui/src/connect_button_delete.rs b/czkawka_gui/src/connect_button_delete.rs index 5b76dc621..6f191c33b 100644 --- a/czkawka_gui/src/connect_button_delete.rs +++ b/czkawka_gui/src/connect_button_delete.rs @@ -523,7 +523,7 @@ pub fn tree_remove( } } - clean_invalid_headers(&model, column_color); + clean_invalid_headers(&model, column_color, column_path); text_view_errors.buffer().unwrap().set_text(messages.as_str()); } diff --git a/czkawka_gui/src/connect_button_hardlink.rs b/czkawka_gui/src/connect_button_hardlink.rs index 8b8bca532..b0cb1f1fe 100644 --- a/czkawka_gui/src/connect_button_hardlink.rs +++ b/czkawka_gui/src/connect_button_hardlink.rs @@ -253,7 +253,7 @@ pub fn hardlink_symlink( model.remove(&model.iter(tree_path).unwrap()); } - clean_invalid_headers(&model, column_color); + clean_invalid_headers(&model, column_color, column_path); } fn create_dialog_non_group(window_main: >k::Window) -> Dialog { diff --git a/czkawka_gui/src/connect_button_move.rs b/czkawka_gui/src/connect_button_move.rs index dab4717cd..67321fba3 100644 --- a/czkawka_gui/src/connect_button_move.rs +++ b/czkawka_gui/src/connect_button_move.rs @@ -175,7 +175,7 @@ fn move_with_tree( move_files_common(&selected_rows, &model, column_file_name, column_path, &destination_folder, entry_info, text_view_errors); - clean_invalid_headers(&model, column_color); + clean_invalid_headers(&model, column_color, column_path); } fn move_with_list( diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index 7c2269a98..b5276a532 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -490,7 +490,7 @@ pub fn get_full_name_from_path_name(path: &str, name: &str) -> String { } // After e.g. deleting files, header may become orphan or have one child, so should be deleted in this case -pub fn clean_invalid_headers(model: >k::ListStore, column_color: i32) { +pub fn clean_invalid_headers(model: >k::ListStore, column_color: i32, column_path: i32) { // Remove only child from header if let Some(first_iter) = model.iter_first() { let mut vec_tree_path_to_delete: Vec = Vec::new(); @@ -501,55 +501,109 @@ pub fn clean_invalid_headers(model: >k::ListStore, column_color: i32) { let mut next_iter; let mut next_next_iter; - 'main: loop { - if model.value(¤t_iter, column_color).get::().unwrap() != HEADER_ROW_COLOR { - panic!("First deleted element, should be a header"); // First element should be header - }; - - next_iter = current_iter.clone(); - if !model.iter_next(&next_iter) { - // There is only single header left (H1 -> END) -> (NOTHING) - vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); - break 'main; - } - - if model.value(&next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { - // There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2) - vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); - current_iter = next_iter.clone(); - continue 'main; - } - next_next_iter = next_iter.clone(); - if !model.iter_next(&next_next_iter) { - // There is only one child of header left, so we remove it with header (H1 -> C1 -> END) -> (NOTHING) - vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); - vec_tree_path_to_delete.push(model.path(&next_iter).unwrap()); - break 'main; - } + // Empty means default check type + if model.value(¤t_iter, column_path).get::().unwrap().is_empty() { + 'main: loop { + if model.value(¤t_iter, column_color).get::().unwrap() != HEADER_ROW_COLOR { + panic!("First deleted element, should be a header"); // First element should be header + }; + + next_iter = current_iter.clone(); + if !model.iter_next(&next_iter) { + // There is only single header left (H1 -> END) -> (NOTHING) + vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); + break 'main; + } - if model.value(&next_next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { - // One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2) - vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); - vec_tree_path_to_delete.push(model.path(&next_iter).unwrap()); - current_iter = next_next_iter.clone(); - continue 'main; - } + if model.value(&next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { + // There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2) + vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); + current_iter = next_iter.clone(); + continue 'main; + } - loop { - // (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD) + next_next_iter = next_iter.clone(); if !model.iter_next(&next_next_iter) { + // There is only one child of header left, so we remove it with header (H1 -> C1 -> END) -> (NOTHING) + vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); + vec_tree_path_to_delete.push(model.path(&next_iter).unwrap()); break 'main; } - // Move to next header + if model.value(&next_next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { + // One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2) + vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); + vec_tree_path_to_delete.push(model.path(&next_iter).unwrap()); current_iter = next_next_iter.clone(); continue 'main; } + + loop { + // (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD) + if !model.iter_next(&next_next_iter) { + break 'main; + } + // Move to next header + if model.value(&next_next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { + current_iter = next_next_iter.clone(); + continue 'main; + } + } + } + for tree_path in vec_tree_path_to_delete.iter().rev() { + model.remove(&model.iter(tree_path).unwrap()); } } - for tree_path in vec_tree_path_to_delete.iter().rev() { - model.remove(&model.iter(tree_path).unwrap()); + // Non empty means that header points at reference folder + else { + // TODO verify how it works + 'reference: loop { + if model.value(¤t_iter, column_color).get::().unwrap() != HEADER_ROW_COLOR { + panic!("First deleted element, should be a header"); // First element should be header + }; + + next_iter = current_iter.clone(); + if !model.iter_next(&next_iter) { + // There is only single header left (H1 -> END) -> (NOTHING) + vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); + break 'reference; + } + + if model.value(&next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { + // There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2) + vec_tree_path_to_delete.push(model.path(¤t_iter).unwrap()); + current_iter = next_iter.clone(); + continue 'reference; + } + + next_next_iter = next_iter.clone(); + if !model.iter_next(&next_next_iter) { + // There is only one child of header left, so we remove it with header (H1 -> C1 -> END) -> (NOTHING) + break 'reference; + } + + if model.value(&next_next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { + // One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2) + current_iter = next_next_iter.clone(); + continue 'reference; + } + + loop { + // (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD) + if !model.iter_next(&next_next_iter) { + break 'reference; + } + // Move to next header + if model.value(&next_next_iter, column_color).get::().unwrap() == HEADER_ROW_COLOR { + current_iter = next_next_iter.clone(); + continue 'reference; + } + } + } + for tree_path in vec_tree_path_to_delete.iter().rev() { + model.remove(&model.iter(tree_path).unwrap()); + } } } From e3041289ea2f441281fa4a49dcb7eecf15587aaf Mon Sep 17 00:00:00 2001 From: qarmin Date: Thu, 23 Dec 2021 14:57:30 +0100 Subject: [PATCH 3/9] Computations --- czkawka_core/src/common_directory.rs | 39 ++++--- czkawka_core/src/similar_images.rs | 51 +++++++++ czkawka_gui/src/compute_results.rs | 158 +++++++++++++++++++-------- 3 files changed, 185 insertions(+), 63 deletions(-) diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index 3aa7e4f3f..9d9c78390 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -16,22 +16,8 @@ impl Directories { Default::default() } - pub fn set_reference_directory(&mut self, reference_directory: Vec, text_messages: &mut Messages) { - let mut reference_directory = reference_directory; - - if cfg!(target_family = "windows") { - reference_directory = reference_directory.iter().map(Common::normalize_windows_path).collect(); - } - - // TODO silent this - for i in reference_directory { - if self.included_directories.contains(&i) { - text_messages.messages.push(format!("REFERENCE {:?}", i)); - self.reference_directories.push(i); - } else { - text_messages.messages.push(format!("NON REFERENCE {:?}", i)); - } - } + pub fn set_reference_directory(&mut self, reference_directory: Vec) { + self.reference_directories = reference_directory } /// Setting included directories, at least one must be provided @@ -165,15 +151,18 @@ impl Directories { if cfg!(target_family = "windows") { self.included_directories = self.included_directories.iter().map(Common::normalize_windows_path).collect(); self.excluded_directories = self.excluded_directories.iter().map(Common::normalize_windows_path).collect(); + self.reference_directories = self.reference_directories.iter().map(Common::normalize_windows_path).collect(); } // Remove duplicated entries like: "/", "/" self.excluded_directories.sort(); self.included_directories.sort(); + self.reference_directories.sort(); self.excluded_directories.dedup(); self.included_directories.dedup(); + self.reference_directories.dedup(); // Optimize for duplicated included directories - "/", "/home". "/home/Pulpit" to "/" if recursive_search { @@ -270,6 +259,20 @@ impl Directories { self.excluded_directories = optimized_excluded; + // Selecting Reference folders + { + let mut ref_folders = Vec::new(); + for folder in &self.reference_directories { + if self.included_directories.contains(&folder) { + ref_folders.push(folder.clone()); + println!("REF: VALID reference folder {:?}", folder); + } else { + println!("REF: Invalid reference folder {:?}", folder); + } + } + self.reference_directories = ref_folders; + } + if self.included_directories.is_empty() { text_messages .errors @@ -292,4 +295,8 @@ impl Directories { // We're assuming that `excluded_directories` are already normalized self.excluded_directories.iter().any(|p| p.as_path() == path) } + + pub fn is_reference_folders_used(&self) -> bool { + !self.reference_directories.is_empty() + } } diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 6340a374d..1ed414883 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -57,6 +57,7 @@ pub struct FileEntry { pub modified_date: u64, pub hash: Vec, pub similarity: Similarity, + pub is_in_reference_folder: bool, } // This is used by CLI tool when we cann @@ -93,6 +94,7 @@ pub struct SimilarImages { excluded_items: ExcludedItems, bktree: BKTree, Hamming>, similar_vectors: Vec>, + similar_referenced_vectors: Option)>>, recursive_search: bool, minimal_file_size: u64, maximal_file_size: u64, @@ -134,6 +136,7 @@ impl SimilarImages { allowed_extensions: Extensions::new(), bktree: BKTree::new(Hamming), similar_vectors: vec![], + similar_referenced_vectors: None, recursive_search: true, minimal_file_size: 1024 * 16, // 16 KB should be enough to exclude too small images from search maximal_file_size: u64::MAX, @@ -187,6 +190,21 @@ impl SimilarImages { &self.similar_vectors } + pub fn get_similar_images_referenced(&self) -> &Vec<(FileEntry, Vec)> { + self.similar_referenced_vectors.as_ref().unwrap() + } + + pub fn get_number_of_base_duplicated_files(&self) -> usize { + match &self.similar_referenced_vectors { + Some(s_reference) => s_reference.len(), + None => self.similar_vectors.len(), + } + } + + pub fn get_use_reference(&self) -> bool { + self.similar_referenced_vectors.is_some() + } + pub const fn get_information(&self) -> &Info { &self.information } @@ -400,6 +418,7 @@ impl SimilarImages { hash: Vec::new(), similarity: Similarity::None, + is_in_reference_folder: false, }; fe_result.push((current_file_name.to_string_lossy().to_string(), fe)); @@ -647,6 +666,7 @@ impl SimilarImages { modified_date: fe.modified_date, hash: fe.hash.clone(), similarity: Similarity::Similar(0), + is_in_reference_folder: false, }) .collect(); collected_similar_images.get_mut(hash).unwrap().append(&mut things); @@ -666,6 +686,7 @@ impl SimilarImages { modified_date: fe.modified_date, hash: Vec::new(), similarity: Similarity::Similar(current_similarity), + is_in_reference_folder: false, }) .collect::>(); collected_similar_images.get_mut(hash).unwrap().append(&mut things); @@ -695,6 +716,35 @@ impl SimilarImages { } } + // TODO use reference search + if self.directories.is_reference_folders_used() { + let mut similars_vector = Default::default(); + mem::swap(&mut self.similar_vectors, &mut similars_vector); + let reference_directories = self.directories.reference_directories.clone(); + self.similar_referenced_vectors = Some( + similars_vector + .into_iter() + .filter_map(|vec_file_entry| { + let mut files_from_referenced_folders = Vec::new(); + let mut normal_files = Vec::new(); + for file_entry in vec_file_entry { + if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { + files_from_referenced_folders.push(file_entry); + } else { + normal_files.push(file_entry); + } + } + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(), + ); + } + Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - selecting data from BtreeMap".to_string()); // Clean unused data @@ -961,6 +1011,7 @@ pub fn load_hashes_from_file( }, hash, similarity: Similarity::None, + is_in_reference_folder: false, }, ); } diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index 9e12b85c5..0d325767a 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -570,7 +570,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< //let information = sf.get_information(); let text_messages = sf.get_text_messages(); - let base_images_size = sf.get_similar_images().len(); + let base_images_size = sf.get_number_of_base_duplicated_files(); entry_info.set_text( format!( @@ -587,62 +587,126 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< { let list_store = get_list_store(&tree_view_similar_images_finder); - let vec_struct_similar = sf.get_similar_images(); - - for vec_file_entry in vec_struct_similar.iter() { - // Sort - let vec_file_entry = if vec_file_entry.len() >= 2 { - let mut vec_file_entry = vec_file_entry.clone(); - vec_file_entry.sort_by_key(|e| { - let t = split_path(e.path.as_path()); - (t.0, t.1) - }); - vec_file_entry - } else { - vec_file_entry.clone() - }; - - // Header - let values: [(u32, &dyn ToValue); 12] = [ - (ColumnsSimilarImages::ActivatableSelectButton as u32, &false), - (ColumnsSimilarImages::SelectionButton as u32, &false), - (ColumnsSimilarImages::Similarity as u32, &"".to_string()), - (ColumnsSimilarImages::Size as u32, &"".to_string()), - (ColumnsSimilarImages::SizeAsBytes as u32, &(0)), - (ColumnsSimilarImages::Dimensions as u32, &"".to_string()), - (ColumnsSimilarImages::Name as u32, &"".to_string()), - (ColumnsSimilarImages::Path as u32, &"".to_string()), - (ColumnsSimilarImages::Modification as u32, &"".to_string()), - (ColumnsSimilarImages::ModificationAsSecs as u32, &(0)), - (ColumnsSimilarImages::Color as u32, &(HEADER_ROW_COLOR.to_string())), - (ColumnsSimilarImages::TextColor as u32, &(TEXT_COLOR.to_string())), - ]; - list_store.set(&list_store.append(), &values); - - // Meat - for file_entry in vec_file_entry.iter() { - let (directory, file) = split_path(&file_entry.path); + if sf.get_use_reference() { + let vec_struct_similar: &Vec<(czkawka_core::similar_images::FileEntry, Vec)> = + sf.get_similar_images_referenced(); + for (base_file_entry, vec_file_entry) in vec_struct_similar.iter() { + // Sort + let vec_file_entry = if vec_file_entry.len() >= 2 { + let mut vec_file_entry = vec_file_entry.clone(); + vec_file_entry.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vec_file_entry + } else { + vec_file_entry.clone() + }; + + // Header + let (directory, file) = split_path(&base_file_entry.path); let values: [(u32, &dyn ToValue); 12] = [ - (ColumnsSimilarImages::ActivatableSelectButton as u32, &true), + (ColumnsSimilarImages::ActivatableSelectButton as u32, &false), (ColumnsSimilarImages::SelectionButton as u32, &false), - ( - ColumnsSimilarImages::Similarity as u32, - &(similar_images::get_string_from_similarity(&file_entry.similarity, hash_size).to_string()), - ), - (ColumnsSimilarImages::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), - (ColumnsSimilarImages::SizeAsBytes as u32, &file_entry.size), - (ColumnsSimilarImages::Dimensions as u32, &file_entry.dimensions), + (ColumnsSimilarImages::Similarity as u32, &"".to_string()), + (ColumnsSimilarImages::Size as u32, &base_file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSimilarImages::SizeAsBytes as u32, &base_file_entry.size), + (ColumnsSimilarImages::Dimensions as u32, &base_file_entry.dimensions), (ColumnsSimilarImages::Name as u32, &file), (ColumnsSimilarImages::Path as u32, &directory), ( ColumnsSimilarImages::Modification as u32, - &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + &(NaiveDateTime::from_timestamp(base_file_entry.modified_date as i64, 0).to_string()), ), - (ColumnsSimilarImages::ModificationAsSecs as u32, &(file_entry.modified_date)), - (ColumnsSimilarImages::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSimilarImages::ModificationAsSecs as u32, &(base_file_entry.modified_date)), + (ColumnsSimilarImages::Color as u32, &(HEADER_ROW_COLOR.to_string())), (ColumnsSimilarImages::TextColor as u32, &(TEXT_COLOR.to_string())), ]; list_store.set(&list_store.append(), &values); + + // Meat + for file_entry in vec_file_entry.iter() { + let (directory, file) = split_path(&file_entry.path); + let values: [(u32, &dyn ToValue); 12] = [ + (ColumnsSimilarImages::ActivatableSelectButton as u32, &true), + (ColumnsSimilarImages::SelectionButton as u32, &false), + ( + ColumnsSimilarImages::Similarity as u32, + &(similar_images::get_string_from_similarity(&file_entry.similarity, hash_size).to_string()), + ), + (ColumnsSimilarImages::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSimilarImages::SizeAsBytes as u32, &file_entry.size), + (ColumnsSimilarImages::Dimensions as u32, &file_entry.dimensions), + (ColumnsSimilarImages::Name as u32, &file), + (ColumnsSimilarImages::Path as u32, &directory), + ( + ColumnsSimilarImages::Modification as u32, + &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsSimilarImages::ModificationAsSecs as u32, &(file_entry.modified_date)), + (ColumnsSimilarImages::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSimilarImages::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } + } + } else { + let vec_struct_similar = sf.get_similar_images(); + for vec_file_entry in vec_struct_similar.iter() { + // Sort + let vec_file_entry = if vec_file_entry.len() >= 2 { + let mut vec_file_entry = vec_file_entry.clone(); + vec_file_entry.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vec_file_entry + } else { + vec_file_entry.clone() + }; + + // Header + let values: [(u32, &dyn ToValue); 12] = [ + (ColumnsSimilarImages::ActivatableSelectButton as u32, &false), + (ColumnsSimilarImages::SelectionButton as u32, &false), + (ColumnsSimilarImages::Similarity as u32, &"".to_string()), + (ColumnsSimilarImages::Size as u32, &"".to_string()), + (ColumnsSimilarImages::SizeAsBytes as u32, &(0)), + (ColumnsSimilarImages::Dimensions as u32, &"".to_string()), + (ColumnsSimilarImages::Name as u32, &"".to_string()), + (ColumnsSimilarImages::Path as u32, &"".to_string()), + (ColumnsSimilarImages::Modification as u32, &"".to_string()), + (ColumnsSimilarImages::ModificationAsSecs as u32, &(0)), + (ColumnsSimilarImages::Color as u32, &(HEADER_ROW_COLOR.to_string())), + (ColumnsSimilarImages::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + + // Meat + for file_entry in vec_file_entry.iter() { + let (directory, file) = split_path(&file_entry.path); + let values: [(u32, &dyn ToValue); 12] = [ + (ColumnsSimilarImages::ActivatableSelectButton as u32, &true), + (ColumnsSimilarImages::SelectionButton as u32, &false), + ( + ColumnsSimilarImages::Similarity as u32, + &(similar_images::get_string_from_similarity(&file_entry.similarity, hash_size).to_string()), + ), + (ColumnsSimilarImages::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSimilarImages::SizeAsBytes as u32, &file_entry.size), + (ColumnsSimilarImages::Dimensions as u32, &file_entry.dimensions), + (ColumnsSimilarImages::Name as u32, &file), + (ColumnsSimilarImages::Path as u32, &directory), + ( + ColumnsSimilarImages::Modification as u32, + &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsSimilarImages::ModificationAsSecs as u32, &(file_entry.modified_date)), + (ColumnsSimilarImages::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSimilarImages::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } } } From dcdf418dff68f79ee0f9a783fafaaeb7b4e2eed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Thu, 23 Dec 2021 18:12:51 +0100 Subject: [PATCH 4/9] Similar Images --- czkawka_core/src/common_directory.rs | 2 +- czkawka_core/src/similar_images.rs | 14 +++++++----- czkawka_gui/src/compute_results.rs | 8 +++++++ czkawka_gui/src/connect_button_search.rs | 10 +++++++-- czkawka_gui/src/help_functions.rs | 22 ++++++++++++++----- czkawka_gui/src/opening_selecting_records.rs | 23 ++++++++++++++++---- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index 9d9c78390..05c755156 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -263,7 +263,7 @@ impl Directories { { let mut ref_folders = Vec::new(); for folder in &self.reference_directories { - if self.included_directories.contains(&folder) { + if self.included_directories.contains(folder) { ref_folders.push(folder.clone()); println!("REF: VALID reference folder {:?}", folder); } else { diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 1ed414883..c0f1dd749 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -57,7 +57,6 @@ pub struct FileEntry { pub modified_date: u64, pub hash: Vec, pub similarity: Similarity, - pub is_in_reference_folder: bool, } // This is used by CLI tool when we cann @@ -108,6 +107,7 @@ pub struct SimilarImages { use_cache: bool, delete_outdated_cache: bool, exclude_images_with_same_size: bool, + use_reference_folders: bool, } /// Info struck with helpful information's about results @@ -150,6 +150,7 @@ impl SimilarImages { use_cache: true, delete_outdated_cache: true, exclude_images_with_same_size: false, + use_reference_folders: false, } } @@ -202,7 +203,7 @@ impl SimilarImages { } pub fn get_use_reference(&self) -> bool { - self.similar_referenced_vectors.is_some() + self.use_reference_folders } pub const fn get_information(&self) -> &Info { @@ -239,6 +240,7 @@ impl SimilarImages { /// Public function used by CLI to search for empty folders pub fn find_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender>) { self.directories.optimize_directories(true, &mut self.text_messages); + self.use_reference_folders = !self.directories.reference_directories.is_empty(); if !self.check_for_similar_images(stop_receiver, progress_sender) { self.stopped_search = true; return; @@ -418,7 +420,6 @@ impl SimilarImages { hash: Vec::new(), similarity: Similarity::None, - is_in_reference_folder: false, }; fe_result.push((current_file_name.to_string_lossy().to_string(), fe)); @@ -666,7 +667,6 @@ impl SimilarImages { modified_date: fe.modified_date, hash: fe.hash.clone(), similarity: Similarity::Similar(0), - is_in_reference_folder: false, }) .collect(); collected_similar_images.get_mut(hash).unwrap().append(&mut things); @@ -686,7 +686,6 @@ impl SimilarImages { modified_date: fe.modified_date, hash: Vec::new(), similarity: Similarity::Similar(current_similarity), - is_in_reference_folder: false, }) .collect::>(); collected_similar_images.get_mut(hash).unwrap().append(&mut things); @@ -760,6 +759,10 @@ impl SimilarImages { self.directories.set_included_directory(included_directory, &mut self.text_messages); } + pub fn set_reference_directory(&mut self, reference_directory: Vec) { + self.directories.set_reference_directory(reference_directory); + } + pub fn set_excluded_directory(&mut self, excluded_directory: Vec) { self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); } @@ -1011,7 +1014,6 @@ pub fn load_hashes_from_file( }, hash, similarity: Similarity::None, - is_in_reference_folder: false, }, ); } diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index 0d325767a..13788d9d2 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -17,6 +17,7 @@ use crate::gui_data::GuiData; use crate::help_combo_box::IMAGES_HASH_SIZE_COMBO_BOX; use crate::help_functions::*; use crate::notebook_enums::*; +use crate::opening_selecting_records::{select_function_always_true, select_function_similar_images}; pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver) { let combo_box_image_hash_size = gui_data.main_notebook.combo_box_image_hash_size.clone(); @@ -567,6 +568,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< if sf.get_stopped_search() { entry_info.set_text(&fl!("compute_stopped_by_user")); } else { + if sf.get_use_reference() { + tree_view_similar_images_finder.selection().set_select_function(Some(Box::new(select_function_always_true))); + } else { + tree_view_similar_images_finder + .selection() + .set_select_function(Some(Box::new(select_function_similar_images))); + } //let information = sf.get_information(); let text_messages = sf.get_text_messages(); diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index c9713b0a8..1ad733ef7 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -103,8 +103,13 @@ pub fn connect_button_search( let check_button_music_approximate_comparison = gui_data.main_notebook.check_button_music_approximate_comparison.clone(); buttons_search_clone.connect_clicked(move |_| { - let included_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_included_directories)); - let excluded_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_excluded_directories)); + let included_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_included_directories, ColumnsIncludedDirectory::Path as i32, None)); + let excluded_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_excluded_directories, ColumnsExcludedDirectory::Path as i32, None)); + let reference_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store( + &tree_view_included_directories, + ColumnsIncludedDirectory::Path as i32, + Some(ColumnsIncludedDirectory::ReferenceButton as i32), + )); let recursive_search = check_button_recursive.is_active(); let excluded_items = entry_excluded_items.text().as_str().to_string().split(',').map(|e| e.to_string()).collect::>(); let allowed_extensions = entry_allowed_extensions.text().as_str().to_string(); @@ -296,6 +301,7 @@ pub fn connect_button_search( sf.set_included_directory(included_directories); sf.set_excluded_directory(excluded_directories); + sf.set_reference_directory(reference_directories); sf.set_recursive_search(recursive_search); sf.set_excluded_items(excluded_items); sf.set_minimal_file_size(minimal_file_size); diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index b5276a532..315db8b85 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -334,7 +334,7 @@ pub const HEADER_ROW_COLOR: &str = "#272727"; //pub const MAIN_ROW_COLOR: &str = "#f4f434"; // TEST //pub const HEADER_ROW_COLOR: &str = "#010101"; // TEST -pub fn get_string_from_list_store(tree_view: >k::TreeView) -> Vec { +pub fn get_string_from_list_store(tree_view: >k::TreeView, column_full_path: i32, column_selection: Option) -> Vec { let list_store: gtk::ListStore = get_list_store(tree_view); let mut string_vector: Vec = Vec::new(); @@ -345,11 +345,21 @@ pub fn get_string_from_list_store(tree_view: >k::TreeView) -> Vec { return string_vector; } }; - loop { - string_vector.push(list_store.value(&tree_iter, 0).get::().unwrap()); - if !list_store.iter_next(&tree_iter) { - return string_vector; - } + match column_selection { + Some(column_selection) => loop { + if list_store.value(&tree_iter, column_selection).get::().unwrap() { + string_vector.push(list_store.value(&tree_iter, column_full_path).get::().unwrap()); + } + if !list_store.iter_next(&tree_iter) { + return string_vector; + } + }, + None => loop { + string_vector.push(list_store.value(&tree_iter, column_full_path).get::().unwrap()); + if !list_store.iter_next(&tree_iter) { + return string_vector; + } + }, } } diff --git a/czkawka_gui/src/opening_selecting_records.rs b/czkawka_gui/src/opening_selecting_records.rs index 9613bfa5b..e9673d0e1 100644 --- a/czkawka_gui/src/opening_selecting_records.rs +++ b/czkawka_gui/src/opening_selecting_records.rs @@ -13,7 +13,14 @@ pub fn opening_enter_function_ported(event_controller: >k::EventControllerKey, } let nt_object = get_notebook_object_from_tree_view(&tree_view); - handle_tree_keypress(&tree_view, key_code, nt_object.column_name, nt_object.column_path, nt_object.column_selection); + handle_tree_keypress( + &tree_view, + key_code, + nt_object.column_name, + nt_object.column_path, + nt_object.column_selection, + nt_object.column_color, + ); false // True catches signal, and don't send it to function, e.g. up button is catched and don't move selection } @@ -58,13 +65,18 @@ enum OpenMode { PathAndName, } -fn common_mark_function(tree_view: >k::TreeView, column_name: i32) { +fn common_mark_function(tree_view: >k::TreeView, column_name: i32, column_color: Option) { let selection = tree_view.selection(); let (selected_rows, tree_model) = selection.selected_rows(); let model = get_list_store(tree_view); for tree_path in selected_rows.iter().rev() { + if let Some(column_color) = column_color { + if model.value(&model.iter(tree_path).unwrap(), column_color).get::().unwrap() == HEADER_ROW_COLOR { + continue; + } + } let value = !tree_model.value(&tree_model.iter(tree_path).unwrap(), column_name).get::().unwrap(); model.set_value(&tree_model.iter(tree_path).unwrap(), column_name as u32, &value.to_value()); } @@ -91,13 +103,13 @@ fn common_open_function(tree_view: >k::TreeView, column_name: i32, column_path } } -fn handle_tree_keypress(tree_view: >k::TreeView, key_code: u32, name_column: i32, path_column: i32, mark_column: i32) { +fn handle_tree_keypress(tree_view: >k::TreeView, key_code: u32, name_column: i32, path_column: i32, mark_column: i32, column_color: Option) { match key_code { KEY_ENTER => { common_open_function(tree_view, name_column, path_column, OpenMode::PathAndName); } KEY_SPACE => { - common_mark_function(tree_view, mark_column); + common_mark_function(tree_view, mark_column, column_color); } _ => {} } @@ -154,3 +166,6 @@ pub fn select_function_similar_videos(_tree_selection: >k::TreeSelection, tree true } +pub fn select_function_always_true(_tree_selection: >k::TreeSelection, _tree_model: >k::TreeModel, _tree_path: >k::TreePath, _is_path_currently_selected: bool) -> bool { + true +} From 7026c86b5ef72e12c67bb87df9d2f133edabe737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Thu, 23 Dec 2021 19:51:32 +0100 Subject: [PATCH 5/9] Muzyka i Wideo --- czkawka_core/src/common_directory.rs | 4 - czkawka_core/src/same_music.rs | 68 ++++- czkawka_core/src/similar_images.rs | 54 ++-- czkawka_core/src/similar_videos.rs | 50 +++ czkawka_gui/src/compute_results.rs | 370 ++++++++++++++++------- czkawka_gui/src/connect_button_search.rs | 2 + 6 files changed, 394 insertions(+), 154 deletions(-) diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index 05c755156..0266e24d1 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -295,8 +295,4 @@ impl Directories { // We're assuming that `excluded_directories` are already normalized self.excluded_directories.iter().any(|p| p.as_path() == path) } - - pub fn is_reference_folders_used(&self) -> bool { - !self.reference_directories.is_empty() - } } diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs index 6555d4648..c2fbbc23d 100644 --- a/czkawka_core/src/same_music.rs +++ b/czkawka_core/src/same_music.rs @@ -7,7 +7,7 @@ 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::{fs, mem, thread}; use audiotags::Tag; use crossbeam_channel::Receiver; @@ -90,6 +90,7 @@ pub struct SameMusic { music_to_check: Vec, music_entries: Vec, duplicated_music_entries: Vec>, + duplicated_music_entries_referenced: Vec<(FileEntry, Vec)>, directories: Directories, allowed_extensions: Extensions, excluded_items: ExcludedItems, @@ -100,6 +101,7 @@ pub struct SameMusic { music_similarity: MusicSimilarity, stopped_search: bool, approximate_comparison: bool, + use_reference_folders: bool, } impl SameMusic { @@ -120,11 +122,14 @@ impl SameMusic { duplicated_music_entries: vec![], music_to_check: Vec::with_capacity(2048), approximate_comparison: true, + use_reference_folders: false, + duplicated_music_entries_referenced: vec![], } } pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender>) { self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); + self.use_reference_folders = !self.directories.reference_directories.is_empty(); if !self.check_files(stop_receiver, progress_sender) { self.stopped_search = true; return; @@ -172,8 +177,13 @@ impl SameMusic { self.recursive_search = recursive_search; } - pub fn set_included_directory(&mut self, included_directory: Vec) -> bool { - self.directories.set_included_directory(included_directory, &mut self.text_messages) + /// Set included dir which needs to be relative, exists etc. + pub fn set_included_directory(&mut self, included_directory: Vec) { + self.directories.set_included_directory(included_directory, &mut self.text_messages); + } + + pub fn set_reference_directory(&mut self, reference_directory: Vec) { + self.directories.set_reference_directory(reference_directory); } pub fn set_excluded_directory(&mut self, excluded_directory: Vec) { @@ -183,6 +193,7 @@ impl SameMusic { pub fn set_excluded_items(&mut self, excluded_items: Vec) { self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); } + pub fn set_allowed_extensions(&mut self, allowed_extensions: String) { self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages); } @@ -198,6 +209,22 @@ impl SameMusic { }; } + pub fn get_similar_music_referenced(&self) -> &Vec<(FileEntry, Vec)> { + &self.duplicated_music_entries_referenced + } + + pub fn get_number_of_base_duplicated_files(&self) -> usize { + if self.use_reference_folders { + self.duplicated_music_entries_referenced.len() + } else { + self.duplicated_music_entries.len() + } + } + + pub fn get_use_reference(&self) -> bool { + self.use_reference_folders + } + /// Check files for any with size == 0 fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender>) -> bool { let start_time: SystemTime = SystemTime::now(); @@ -671,9 +698,40 @@ impl SameMusic { self.duplicated_music_entries = old_duplicates; - for vec in &self.duplicated_music_entries { - self.information.number_of_duplicates_music_files += vec.len() - 1; + if self.use_reference_folders { + let mut similars_vector = Default::default(); + mem::swap(&mut self.duplicated_music_entries, &mut similars_vector); + let reference_directories = self.directories.reference_directories.clone(); + self.duplicated_music_entries_referenced = similars_vector + .into_iter() + .filter_map(|vec_file_entry| { + let mut files_from_referenced_folders = Vec::new(); + let mut normal_files = Vec::new(); + for file_entry in vec_file_entry { + if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { + files_from_referenced_folders.push(file_entry); + } else { + normal_files.push(file_entry); + } + } + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); + + for (_fe, vec) in &self.duplicated_music_entries_referenced { + self.information.number_of_duplicates_music_files += vec.len(); + } + } else { + for vec in &self.duplicated_music_entries { + self.information.number_of_duplicates_music_files += vec.len() - 1; + } } + // End thread which send info to gui progress_thread_run.store(false, Ordering::Relaxed); progress_thread_handle.join().unwrap(); diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index c0f1dd749..be3b5312d 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -93,7 +93,7 @@ pub struct SimilarImages { excluded_items: ExcludedItems, bktree: BKTree, Hamming>, similar_vectors: Vec>, - similar_referenced_vectors: Option)>>, + similar_referenced_vectors: Vec<(FileEntry, Vec)>, recursive_search: bool, minimal_file_size: u64, maximal_file_size: u64, @@ -136,7 +136,7 @@ impl SimilarImages { allowed_extensions: Extensions::new(), bktree: BKTree::new(Hamming), similar_vectors: vec![], - similar_referenced_vectors: None, + similar_referenced_vectors: Default::default(), recursive_search: true, minimal_file_size: 1024 * 16, // 16 KB should be enough to exclude too small images from search maximal_file_size: u64::MAX, @@ -192,13 +192,14 @@ impl SimilarImages { } pub fn get_similar_images_referenced(&self) -> &Vec<(FileEntry, Vec)> { - self.similar_referenced_vectors.as_ref().unwrap() + &self.similar_referenced_vectors } pub fn get_number_of_base_duplicated_files(&self) -> usize { - match &self.similar_referenced_vectors { - Some(s_reference) => s_reference.len(), - None => self.similar_vectors.len(), + if self.use_reference_folders { + self.similar_referenced_vectors.len() + } else { + self.similar_vectors.len() } } @@ -715,33 +716,30 @@ impl SimilarImages { } } - // TODO use reference search - if self.directories.is_reference_folders_used() { + if self.use_reference_folders { let mut similars_vector = Default::default(); mem::swap(&mut self.similar_vectors, &mut similars_vector); let reference_directories = self.directories.reference_directories.clone(); - self.similar_referenced_vectors = Some( - similars_vector - .into_iter() - .filter_map(|vec_file_entry| { - let mut files_from_referenced_folders = Vec::new(); - let mut normal_files = Vec::new(); - for file_entry in vec_file_entry { - if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { - files_from_referenced_folders.push(file_entry); - } else { - normal_files.push(file_entry); - } - } - - if files_from_referenced_folders.is_empty() || normal_files.is_empty() { - None + self.similar_referenced_vectors = similars_vector + .into_iter() + .filter_map(|vec_file_entry| { + let mut files_from_referenced_folders = Vec::new(); + let mut normal_files = Vec::new(); + for file_entry in vec_file_entry { + if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { + files_from_referenced_folders.push(file_entry); } else { - Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + normal_files.push(file_entry); } - }) - .collect::)>>(), - ); + } + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); } Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - selecting data from BtreeMap".to_string()); diff --git a/czkawka_core/src/similar_videos.rs b/czkawka_core/src/similar_videos.rs index d66beecf7..179c14c8e 100644 --- a/czkawka_core/src/similar_videos.rs +++ b/czkawka_core/src/similar_videos.rs @@ -68,6 +68,7 @@ pub struct SimilarVideos { excluded_items: ExcludedItems, allowed_extensions: Extensions, similar_vectors: Vec>, + similar_referenced_vectors: Vec<(FileEntry, Vec)>, recursive_search: bool, minimal_file_size: u64, maximal_file_size: u64, @@ -78,6 +79,7 @@ pub struct SimilarVideos { tolerance: i32, delete_outdated_cache: bool, exclude_videos_with_same_size: bool, + use_reference_folders: bool, } /// Info struck with helpful information's about results @@ -115,6 +117,8 @@ impl SimilarVideos { tolerance: 10, delete_outdated_cache: false, exclude_videos_with_same_size: false, + use_reference_folders: false, + similar_referenced_vectors: vec![], } } @@ -171,6 +175,21 @@ impl SimilarVideos { t => t, }; } + pub fn get_similar_videos_referenced(&self) -> &Vec<(FileEntry, Vec)> { + &self.similar_referenced_vectors + } + + pub fn get_number_of_base_duplicated_files(&self) -> usize { + if self.use_reference_folders { + self.similar_referenced_vectors.len() + } else { + self.similar_vectors.len() + } + } + + pub fn get_use_reference(&self) -> bool { + self.use_reference_folders + } /// Public function used by CLI to search for empty folders pub fn find_similar_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender>) { @@ -178,6 +197,7 @@ impl SimilarVideos { self.text_messages.errors.push("Cannot find proper installation of FFmpeg.".to_string()); } else { self.directories.optimize_directories(true, &mut self.text_messages); + self.use_reference_folders = !self.directories.reference_directories.is_empty(); if !self.check_for_similar_videos(stop_receiver, progress_sender) { self.stopped_search = true; return; @@ -539,6 +559,32 @@ impl SimilarVideos { self.similar_vectors = collected_similar_videos; + if self.use_reference_folders { + let mut similars_vector = Default::default(); + mem::swap(&mut self.similar_vectors, &mut similars_vector); + let reference_directories = self.directories.reference_directories.clone(); + self.similar_referenced_vectors = similars_vector + .into_iter() + .filter_map(|vec_file_entry| { + let mut files_from_referenced_folders = Vec::new(); + let mut normal_files = Vec::new(); + for file_entry in vec_file_entry { + if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { + files_from_referenced_folders.push(file_entry); + } else { + normal_files.push(file_entry); + } + } + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); + } + Common::print_time(hash_map_modification, SystemTime::now(), "sort_videos - selecting data from BtreeMap".to_string()); // Clean unused data @@ -553,6 +599,10 @@ impl SimilarVideos { self.directories.set_included_directory(included_directory, &mut self.text_messages); } + pub fn set_reference_directory(&mut self, reference_directory: Vec) { + self.directories.set_reference_directory(reference_directory); + } + pub fn set_excluded_directory(&mut self, excluded_directory: Vec) { self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); } diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index 13788d9d2..18f75a755 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -17,7 +17,7 @@ use crate::gui_data::GuiData; use crate::help_combo_box::IMAGES_HASH_SIZE_COMBO_BOX; use crate::help_functions::*; use crate::notebook_enums::*; -use crate::opening_selecting_records::{select_function_always_true, select_function_similar_images}; +use crate::opening_selecting_records::*; pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver) { let combo_box_image_hash_size = gui_data.main_notebook.combo_box_image_hash_size.clone(); @@ -744,6 +744,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< if ff.get_stopped_search() { entry_info.set_text(&fl!("compute_stopped_by_user")); } else { + if ff.get_use_reference() { + tree_view_similar_videos_finder.selection().set_select_function(Some(Box::new(select_function_always_true))); + } else { + tree_view_similar_videos_finder + .selection() + .set_select_function(Some(Box::new(select_function_similar_videos))); + } //let information = ff.get_information(); let text_messages = ff.get_text_messages(); @@ -764,55 +771,113 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< { let list_store = get_list_store(&tree_view_similar_videos_finder); - let vec_struct_similar = ff.get_similar_videos(); - - for vec_file_entry in vec_struct_similar.iter() { - // Sort - let vec_file_entry = if vec_file_entry.len() >= 2 { - let mut vec_file_entry = vec_file_entry.clone(); - vec_file_entry.sort_by_key(|e| { - let t = split_path(e.path.as_path()); - (t.0, t.1) - }); - vec_file_entry - } else { - vec_file_entry.clone() - }; - - // Header - let values: [(u32, &dyn ToValue); 10] = [ - (ColumnsSimilarVideos::ActivatableSelectButton as u32, &false), - (ColumnsSimilarVideos::SelectionButton as u32, &false), - (ColumnsSimilarVideos::Size as u32, &"".to_string()), - (ColumnsSimilarVideos::SizeAsBytes as u32, &(0)), - (ColumnsSimilarVideos::Name as u32, &"".to_string()), - (ColumnsSimilarVideos::Path as u32, &"".to_string()), - (ColumnsSimilarVideos::Modification as u32, &"".to_string()), - (ColumnsSimilarVideos::ModificationAsSecs as u32, &(0)), - (ColumnsSimilarVideos::Color as u32, &(HEADER_ROW_COLOR.to_string())), - (ColumnsSimilarVideos::TextColor as u32, &(TEXT_COLOR.to_string())), - ]; - list_store.set(&list_store.append(), &values); + if ff.get_use_reference() { + let vec_struct_similar = ff.get_similar_videos_referenced(); - // Meat - for file_entry in vec_file_entry.iter() { - let (directory, file) = split_path(&file_entry.path); + for (base_file_entry, vec_file_entry) in vec_struct_similar.iter() { + // Sort + let vec_file_entry = if vec_file_entry.len() >= 2 { + let mut vec_file_entry = vec_file_entry.clone(); + vec_file_entry.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vec_file_entry + } else { + vec_file_entry.clone() + }; + + // Header + let (directory, file) = split_path(&base_file_entry.path); let values: [(u32, &dyn ToValue); 10] = [ - (ColumnsSimilarVideos::ActivatableSelectButton as u32, &true), + (ColumnsSimilarVideos::ActivatableSelectButton as u32, &false), (ColumnsSimilarVideos::SelectionButton as u32, &false), - (ColumnsSimilarVideos::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), - (ColumnsSimilarVideos::SizeAsBytes as u32, &file_entry.size), + (ColumnsSimilarVideos::Size as u32, &base_file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSimilarVideos::SizeAsBytes as u32, &base_file_entry.size), (ColumnsSimilarVideos::Name as u32, &file), (ColumnsSimilarVideos::Path as u32, &directory), ( ColumnsSimilarVideos::Modification as u32, - &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + &(NaiveDateTime::from_timestamp(base_file_entry.modified_date as i64, 0).to_string()), ), - (ColumnsSimilarVideos::ModificationAsSecs as u32, &(file_entry.modified_date)), - (ColumnsSimilarVideos::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSimilarVideos::ModificationAsSecs as u32, &(base_file_entry.modified_date)), + (ColumnsSimilarVideos::Color as u32, &(HEADER_ROW_COLOR.to_string())), + (ColumnsSimilarVideos::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + + // Meat + for file_entry in vec_file_entry.iter() { + let (directory, file) = split_path(&file_entry.path); + let values: [(u32, &dyn ToValue); 10] = [ + (ColumnsSimilarVideos::ActivatableSelectButton as u32, &true), + (ColumnsSimilarVideos::SelectionButton as u32, &false), + (ColumnsSimilarVideos::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSimilarVideos::SizeAsBytes as u32, &file_entry.size), + (ColumnsSimilarVideos::Name as u32, &file), + (ColumnsSimilarVideos::Path as u32, &directory), + ( + ColumnsSimilarVideos::Modification as u32, + &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsSimilarVideos::ModificationAsSecs as u32, &(file_entry.modified_date)), + (ColumnsSimilarVideos::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSimilarVideos::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } + } + } else { + let vec_struct_similar = ff.get_similar_videos(); + + for vec_file_entry in vec_struct_similar.iter() { + // Sort + let vec_file_entry = if vec_file_entry.len() >= 2 { + let mut vec_file_entry = vec_file_entry.clone(); + vec_file_entry.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vec_file_entry + } else { + vec_file_entry.clone() + }; + + // Header + let values: [(u32, &dyn ToValue); 10] = [ + (ColumnsSimilarVideos::ActivatableSelectButton as u32, &false), + (ColumnsSimilarVideos::SelectionButton as u32, &false), + (ColumnsSimilarVideos::Size as u32, &"".to_string()), + (ColumnsSimilarVideos::SizeAsBytes as u32, &(0)), + (ColumnsSimilarVideos::Name as u32, &"".to_string()), + (ColumnsSimilarVideos::Path as u32, &"".to_string()), + (ColumnsSimilarVideos::Modification as u32, &"".to_string()), + (ColumnsSimilarVideos::ModificationAsSecs as u32, &(0)), + (ColumnsSimilarVideos::Color as u32, &(HEADER_ROW_COLOR.to_string())), (ColumnsSimilarVideos::TextColor as u32, &(TEXT_COLOR.to_string())), ]; list_store.set(&list_store.append(), &values); + + // Meat + for file_entry in vec_file_entry.iter() { + let (directory, file) = split_path(&file_entry.path); + let values: [(u32, &dyn ToValue); 10] = [ + (ColumnsSimilarVideos::ActivatableSelectButton as u32, &true), + (ColumnsSimilarVideos::SelectionButton as u32, &false), + (ColumnsSimilarVideos::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSimilarVideos::SizeAsBytes as u32, &file_entry.size), + (ColumnsSimilarVideos::Name as u32, &file), + (ColumnsSimilarVideos::Path as u32, &directory), + ( + ColumnsSimilarVideos::Modification as u32, + &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsSimilarVideos::ModificationAsSecs as u32, &(file_entry.modified_date)), + (ColumnsSimilarVideos::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSimilarVideos::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } } } @@ -842,6 +907,12 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< if mf.get_stopped_search() { entry_info.set_text(&fl!("compute_stopped_by_user")); } else { + if mf.get_use_reference() { + tree_view_same_music_finder.selection().set_select_function(Some(Box::new(select_function_always_true))); + } else { + tree_view_same_music_finder.selection().set_select_function(Some(Box::new(select_function_same_music))); + } + let information = mf.get_information(); let text_messages = mf.get_text_messages(); @@ -853,8 +924,6 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< { let list_store = get_list_store(&tree_view_same_music_finder); - let vector = mf.get_duplicated_music_entries(); - let music_similarity = *mf.get_music_similarity(); let is_title = (MusicSimilarity::TITLE & music_similarity) != MusicSimilarity::NONE; @@ -863,92 +932,159 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let is_album_artist = (MusicSimilarity::ALBUM_ARTIST & music_similarity) != MusicSimilarity::NONE; let is_year = (MusicSimilarity::YEAR & music_similarity) != MusicSimilarity::NONE; - let text: String = "-----".to_string(); - - for vec_file_entry in vector { - // Sort - let vec_file_entry = if vec_file_entry.len() >= 2 { - let mut vec_file_entry = vec_file_entry.clone(); - vec_file_entry.sort_by_key(|e| { - let t = split_path(e.path.as_path()); - (t.0, t.1) - }); - vec_file_entry - } else { - vec_file_entry.clone() - }; - - let values: [(u32, &dyn ToValue); 15] = [ - (ColumnsSameMusic::ActivatableSelectButton as u32, &false), - (ColumnsSameMusic::SelectionButton as u32, &false), - (ColumnsSameMusic::Size as u32, &"".to_string()), - (ColumnsSameMusic::SizeAsBytes as u32, &(0)), - (ColumnsSameMusic::Name as u32, &"".to_string()), - (ColumnsSameMusic::Path as u32, &"".to_string()), - ( - ColumnsSameMusic::Title as u32, - &(match is_title { - true => text.clone(), - false => "".to_string(), - }), - ), - ( - ColumnsSameMusic::Artist as u32, - &(match is_artist { - true => text.clone(), - false => "".to_string(), - }), - ), - ( - ColumnsSameMusic::AlbumTitle as u32, - &(match is_album_title { - true => text.clone(), - false => "".to_string(), - }), - ), - ( - ColumnsSameMusic::AlbumArtist as u32, - &(match is_album_artist { - true => text.clone(), - false => "".to_string(), - }), - ), - ( - ColumnsSameMusic::Year as u32, - &(match is_year { - true => text.clone(), - false => "".to_string(), - }), - ), - (ColumnsSameMusic::Modification as u32, &"".to_string()), - (ColumnsSameMusic::ModificationAsSecs as u32, &(0)), - (ColumnsSameMusic::Color as u32, &(HEADER_ROW_COLOR.to_string())), - (ColumnsSameMusic::TextColor as u32, &(TEXT_COLOR.to_string())), - ]; - list_store.set(&list_store.append(), &values); - for file_entry in vec_file_entry { - let (directory, file) = split_path(&file_entry.path); + if mf.get_use_reference() { + let vector = mf.get_similar_music_referenced(); + + for (base_file_entry, vec_file_entry) in vector { + // Sort + let vec_file_entry = if vec_file_entry.len() >= 2 { + let mut vec_file_entry = vec_file_entry.clone(); + vec_file_entry.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vec_file_entry + } else { + vec_file_entry.clone() + }; + + let (directory, file) = split_path(&base_file_entry.path); let values: [(u32, &dyn ToValue); 15] = [ - (ColumnsSameMusic::ActivatableSelectButton as u32, &true), + (ColumnsSameMusic::ActivatableSelectButton as u32, &false), (ColumnsSameMusic::SelectionButton as u32, &false), - (ColumnsSameMusic::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), - (ColumnsSameMusic::SizeAsBytes as u32, &file_entry.size), + (ColumnsSameMusic::Size as u32, &base_file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSameMusic::SizeAsBytes as u32, &base_file_entry.size), (ColumnsSameMusic::Name as u32, &file), (ColumnsSameMusic::Path as u32, &directory), - (ColumnsSameMusic::Title as u32, &file_entry.title), - (ColumnsSameMusic::Artist as u32, &file_entry.artist), - (ColumnsSameMusic::AlbumTitle as u32, &file_entry.album_title), - (ColumnsSameMusic::AlbumArtist as u32, &file_entry.album_artist), - (ColumnsSameMusic::Year as u32, &file_entry.year.to_string()), + (ColumnsSameMusic::Title as u32, &base_file_entry.title), + (ColumnsSameMusic::Artist as u32, &base_file_entry.artist), + (ColumnsSameMusic::AlbumTitle as u32, &base_file_entry.album_title), + (ColumnsSameMusic::AlbumArtist as u32, &base_file_entry.album_artist), + (ColumnsSameMusic::Year as u32, &base_file_entry.year.to_string()), ( ColumnsSameMusic::Modification as u32, - &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + &(NaiveDateTime::from_timestamp(base_file_entry.modified_date as i64, 0).to_string()), ), - (ColumnsSameMusic::ModificationAsSecs as u32, &(file_entry.modified_date)), - (ColumnsSameMusic::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSameMusic::ModificationAsSecs as u32, &(base_file_entry.modified_date)), + (ColumnsSameMusic::Color as u32, &(HEADER_ROW_COLOR.to_string())), (ColumnsSameMusic::TextColor as u32, &(TEXT_COLOR.to_string())), ]; list_store.set(&list_store.append(), &values); + for file_entry in vec_file_entry { + let (directory, file) = split_path(&file_entry.path); + let values: [(u32, &dyn ToValue); 15] = [ + (ColumnsSameMusic::ActivatableSelectButton as u32, &true), + (ColumnsSameMusic::SelectionButton as u32, &false), + (ColumnsSameMusic::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSameMusic::SizeAsBytes as u32, &file_entry.size), + (ColumnsSameMusic::Name as u32, &file), + (ColumnsSameMusic::Path as u32, &directory), + (ColumnsSameMusic::Title as u32, &file_entry.title), + (ColumnsSameMusic::Artist as u32, &file_entry.artist), + (ColumnsSameMusic::AlbumTitle as u32, &file_entry.album_title), + (ColumnsSameMusic::AlbumArtist as u32, &file_entry.album_artist), + (ColumnsSameMusic::Year as u32, &file_entry.year.to_string()), + ( + ColumnsSameMusic::Modification as u32, + &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsSameMusic::ModificationAsSecs as u32, &(file_entry.modified_date)), + (ColumnsSameMusic::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSameMusic::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } + } + } else { + let vector = mf.get_duplicated_music_entries(); + + let text: String = "-----".to_string(); + + for vec_file_entry in vector { + // Sort + let vec_file_entry = if vec_file_entry.len() >= 2 { + let mut vec_file_entry = vec_file_entry.clone(); + vec_file_entry.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vec_file_entry + } else { + vec_file_entry.clone() + }; + + let values: [(u32, &dyn ToValue); 15] = [ + (ColumnsSameMusic::ActivatableSelectButton as u32, &false), + (ColumnsSameMusic::SelectionButton as u32, &false), + (ColumnsSameMusic::Size as u32, &"".to_string()), + (ColumnsSameMusic::SizeAsBytes as u32, &(0)), + (ColumnsSameMusic::Name as u32, &"".to_string()), + (ColumnsSameMusic::Path as u32, &"".to_string()), + ( + ColumnsSameMusic::Title as u32, + &(match is_title { + true => text.clone(), + false => "".to_string(), + }), + ), + ( + ColumnsSameMusic::Artist as u32, + &(match is_artist { + true => text.clone(), + false => "".to_string(), + }), + ), + ( + ColumnsSameMusic::AlbumTitle as u32, + &(match is_album_title { + true => text.clone(), + false => "".to_string(), + }), + ), + ( + ColumnsSameMusic::AlbumArtist as u32, + &(match is_album_artist { + true => text.clone(), + false => "".to_string(), + }), + ), + ( + ColumnsSameMusic::Year as u32, + &(match is_year { + true => text.clone(), + false => "".to_string(), + }), + ), + (ColumnsSameMusic::Modification as u32, &"".to_string()), + (ColumnsSameMusic::ModificationAsSecs as u32, &(0)), + (ColumnsSameMusic::Color as u32, &(HEADER_ROW_COLOR.to_string())), + (ColumnsSameMusic::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + for file_entry in vec_file_entry { + let (directory, file) = split_path(&file_entry.path); + let values: [(u32, &dyn ToValue); 15] = [ + (ColumnsSameMusic::ActivatableSelectButton as u32, &true), + (ColumnsSameMusic::SelectionButton as u32, &false), + (ColumnsSameMusic::Size as u32, &file_entry.size.file_size(options::BINARY).unwrap()), + (ColumnsSameMusic::SizeAsBytes as u32, &file_entry.size), + (ColumnsSameMusic::Name as u32, &file), + (ColumnsSameMusic::Path as u32, &directory), + (ColumnsSameMusic::Title as u32, &file_entry.title), + (ColumnsSameMusic::Artist as u32, &file_entry.artist), + (ColumnsSameMusic::AlbumTitle as u32, &file_entry.album_title), + (ColumnsSameMusic::AlbumArtist as u32, &file_entry.album_artist), + (ColumnsSameMusic::Year as u32, &file_entry.year.to_string()), + ( + ColumnsSameMusic::Modification as u32, + &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsSameMusic::ModificationAsSecs as u32, &(file_entry.modified_date)), + (ColumnsSameMusic::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsSameMusic::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } } } print_text_messages_to_text_view(text_messages, &text_view_errors); diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 1ad733ef7..04b33cd6c 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -341,6 +341,7 @@ pub fn connect_button_search( sf.set_included_directory(included_directories); sf.set_excluded_directory(excluded_directories); + sf.set_reference_directory(reference_directories); sf.set_recursive_search(recursive_search); sf.set_excluded_items(excluded_items); sf.set_minimal_file_size(minimal_file_size); @@ -391,6 +392,7 @@ pub fn connect_button_search( mf.set_included_directory(included_directories); mf.set_excluded_directory(excluded_directories); + mf.set_reference_directory(reference_directories); mf.set_excluded_items(excluded_items); mf.set_minimal_file_size(minimal_file_size); mf.set_maximal_file_size(maximal_file_size); From 0df8a406e8db72f5a4451e18825a04889ec961d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Thu, 23 Dec 2021 21:27:31 +0100 Subject: [PATCH 6/9] =?UTF-8?q?Podw=C3=B3jne=20dwa=20razy,=20cztery=20razy?= =?UTF-8?q?!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 62 ++++++++-------- czkawka_gui/src/connect_button_search.rs | 14 +--- czkawka_gui/src/connect_duplicate_buttons.rs | 12 ++-- czkawka_gui/src/gui_main_notebook.rs | 23 +++++- czkawka_gui/src/help_functions.rs | 12 +++- czkawka_gui/src/initialize_gui.rs | 11 ++- czkawka_gui/src/opening_selecting_records.rs | 75 +++++++++++++++++++- 7 files changed, 154 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 16c892805..a9a359254 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ - CLI frontend - for easy automation - GUI frontend - uses modern GTK 3 and looks similar to FSlint - No spying - Czkawka does not have access to the Internet, nor does it collect any user information or statistics -- Multilingual - app support multiple languages +- Multilingual - support multiple languages like Polish, English or Italian - Multiple tools to use: - Duplicates - Finds duplicates based on file name, size or hash - Empty Folders - Finds empty folders with the help of an advanced algorithm @@ -50,12 +50,12 @@ I set the minimal file size to check to 1KB on all programs. | App | Executing Time | |:---------------------------:|:--------------:| -| FSlint 2.4.7 (First Run) | 86s | -| FSlint 2.4.7 (Second Run) | 43s | -| Czkawka 3.0.0 (First Run) | 8s | -| Czkawka 3.0.0 (Second Run) | 7s | -| DupeGuru 4.1.1 (First Run) | 22s | -| DupeGuru 4.1.1 (Second Run) | 21s | +| FSlint 2.4.7 (First Run) | 86s | +| FSlint 2.4.7 (Second Run) | 43s | +| Czkawka 3.0.0 (First Run) | 8s | +| Czkawka 3.0.0 (Second Run) | 7s | +| DupeGuru 4.1.1 (First Run) | 22s | +| DupeGuru 4.1.1 (Second Run) | 21s | I used Mprof for checking memory usage of FSlint and DupeGuru, and Heaptrack for Czkawka. @@ -91,31 +91,31 @@ Similar images which check 349 image files that occupied 1.7 GB Bleachbit is a master at finding and removing temporary files, while Czkawka only finds the most basic ones. So these two apps shouldn't be compared directly or be considered as an alternative to one another. -| | Czkawka | FSlint | DupeGuru | Bleachbit | +| | Czkawka | FSlint | DupeGuru | Bleachbit | |:----------------------:|:-----------:|:----------:|:-----------------:|:-----------:| -| Language | Rust | Python | Python/Obj-C | Python | -| OS | Lin,Mac,Win | Lin | Lin,Mac,Win | Lin,Mac,Win | -| Framework | GTK 3 | PyGTK2 | Qt 5 (PyQt)/Cocoa | PyGTK3 | -| Duplicate finder | • | • | • | | -| Empty files | • | • | | | -| Empty folders | • | • | | | -| Temporary files | • | • | | • | -| Big files | • | | | | -| Similar images | • | | • | | -| Similar videos | • | | | | -| Music duplicates(tags) | • | | • | | -| Invalid symlinks | • | • | | | -| Broken files | • | | | | -| Names conflict | • | • | | | -| Installed packages | | • | | | -| Invalid names | | • | | | -| Bad ID | | • | | | -| Non stripped binaries | | • | | | -| Redundant whitespace | | • | | | -| Overwriting files | | • | | • | -| Multiple languages(po) | • | • | • | • | -| Cache support | • | | • | | -| In active development | Yes | No | Yes | Yes | +| Language | Rust | Python | Python/Obj-C | Python | +| OS | Lin,Mac,Win | Lin | Lin,Mac,Win | Lin,Mac,Win | +| Framework | GTK 3 | PyGTK2 | Qt 5 (PyQt)/Cocoa | PyGTK3 | +| Duplicate finder | • | • | • | | +| Empty files | • | • | | | +| Empty folders | • | • | | | +| Temporary files | • | • | | • | +| Big files | • | | | | +| Similar images | • | | • | | +| Similar videos | • | | | | +| Music duplicates(tags) | • | | • | | +| Invalid symlinks | • | • | | | +| Broken files | • | | | | +| Names conflict | • | • | | | +| Installed packages | | • | | | +| Invalid names | | • | | | +| Bad ID | | • | | | +| Non stripped binaries | | • | | | +| Redundant whitespace | | • | | | +| Overwriting files | | • | | • | +| Multiple languages | • | • | • | • | +| Cache support | • | | • | | +| In active development | Yes | No | Yes | Yes | ## Other apps There are many similar applications to Czkawka on the Internet, which do some things better and some things worse. diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 04b33cd6c..711417c70 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -117,6 +117,9 @@ pub fn connect_button_search( let use_cache = check_button_settings_use_cache.is_active(); let minimal_cache_file_size = entry_settings_cache_file_minimal_size.text().as_str().parse::().unwrap_or(1024 * 1024 / 4); + let minimal_file_size = entry_general_minimal_size.text().as_str().parse::().unwrap_or(1024 * 8); + let maximal_file_size = entry_general_maximal_size.text().as_str().parse::().unwrap_or(1024 * 1024 * 1024 * 1024); + let show_dialog = Arc::new(AtomicBool::new(true)); hide_all_buttons(&buttons_array); @@ -153,9 +156,6 @@ pub fn connect_button_search( let hash_type_index = combo_box_duplicate_hash_type.active().unwrap() as usize; let hash_type = DUPLICATES_HASH_TYPE_COMBO_BOX[hash_type_index].hash_type; - let minimal_file_size = entry_general_minimal_size.text().as_str().parse::().unwrap_or(1024 * 8); - let maximal_file_size = entry_general_maximal_size.text().as_str().parse::().unwrap_or(1024 * 1024 * 1024 * 1024); - let use_prehash_cache = check_button_duplicates_use_prehash_cache.is_active(); let minimal_prehash_cache_file_size = entry_settings_prehash_cache_file_minimal_size.text().as_str().parse::().unwrap_or(0); @@ -285,9 +285,6 @@ pub fn connect_button_search( let hash_alg_index = combo_box_image_hash_algorithm.active().unwrap() as usize; let hash_alg = IMAGES_HASH_TYPE_COMBO_BOX[hash_alg_index].hash_alg; - let minimal_file_size = entry_general_minimal_size.text().as_str().parse::().unwrap_or(1024 * 16); - let maximal_file_size = entry_general_maximal_size.text().as_str().parse::().unwrap_or(1024 * 1024 * 1024 * 1024); - let ignore_same_size = check_button_image_ignore_same_size.is_active(); let similarity = similar_images::Similarity::Similar(scale_similarity_similar_images.value() as u32); @@ -325,9 +322,6 @@ pub fn connect_button_search( get_list_store(&tree_view_similar_videos_finder).clear(); - let minimal_file_size = entry_general_minimal_size.text().as_str().parse::().unwrap_or(1024 * 16); - let maximal_file_size = entry_general_maximal_size.text().as_str().parse::().unwrap_or(1024 * 1024 * 1024 * 1024); - let tolerance = scale_similarity_similar_videos.value() as i32; let delete_outdated_cache = check_button_settings_similar_videos_delete_outdated_cache.is_active(); @@ -362,8 +356,6 @@ pub fn connect_button_search( get_list_store(&tree_view_same_music_finder).clear(); - let minimal_file_size = entry_general_minimal_size.text().as_str().parse::().unwrap_or(1024 * 8); - let maximal_file_size = entry_general_maximal_size.text().as_str().parse::().unwrap_or(1024 * 1024 * 1024 * 1024); let approximate_comparison = check_button_music_approximate_comparison.is_active(); let mut music_similarity: MusicSimilarity = MusicSimilarity::NONE; diff --git a/czkawka_gui/src/connect_duplicate_buttons.rs b/czkawka_gui/src/connect_duplicate_buttons.rs index 072ec3116..f43c29546 100644 --- a/czkawka_gui/src/connect_duplicate_buttons.rs +++ b/czkawka_gui/src/connect_duplicate_buttons.rs @@ -9,11 +9,13 @@ pub fn connect_duplicate_combo_box(gui_data: &GuiData) { let combo_box_duplicate_check_method = gui_data.main_notebook.combo_box_duplicate_check_method.clone(); let combo_box_duplicate_hash_type = gui_data.main_notebook.combo_box_duplicate_hash_type.clone(); combo_box_duplicate_check_method.connect_changed(move |combo_box_duplicate_check_method| { - let chosen_index = combo_box_duplicate_check_method.active().unwrap() as usize; - if DUPLICATES_CHECK_METHOD_COMBO_BOX[chosen_index].check_method == CheckingMethod::Hash { - combo_box_duplicate_hash_type.set_sensitive(true); - } else { - combo_box_duplicate_hash_type.set_sensitive(false); + // None active can be if when adding elements(this signal is activated when e.g. adding new fields or removing them) + if let Some(chosen_index) = combo_box_duplicate_check_method.active() { + if DUPLICATES_CHECK_METHOD_COMBO_BOX[chosen_index as usize].check_method == CheckingMethod::Hash { + combo_box_duplicate_hash_type.set_sensitive(true); + } else { + combo_box_duplicate_hash_type.set_sensitive(false); + } } }); } diff --git a/czkawka_gui/src/gui_main_notebook.rs b/czkawka_gui/src/gui_main_notebook.rs index 542e0372a..a7cb2f16b 100644 --- a/czkawka_gui/src/gui_main_notebook.rs +++ b/czkawka_gui/src/gui_main_notebook.rs @@ -1,10 +1,11 @@ +use czkawka_core::duplicate::CheckingMethod; use gtk::prelude::*; use gtk::{EventControllerKey, TreeView}; use czkawka_core::similar_images::{get_string_from_similarity, Similarity, SIMILAR_VALUES}; use crate::fl; -use crate::help_combo_box::IMAGES_HASH_SIZE_COMBO_BOX; +use crate::help_combo_box::{DUPLICATES_CHECK_METHOD_COMBO_BOX, IMAGES_HASH_SIZE_COMBO_BOX}; use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS}; #[derive(Clone)] @@ -480,5 +481,25 @@ impl GuiMainNotebook { column.set_title(&names_of_columns[notebook_index][column_index - 1]); } } + + { + let active = match self.combo_box_duplicate_check_method.active() { + Some(t) => t, + None => 0, + }; + self.combo_box_duplicate_check_method.remove_all(); + for i in &DUPLICATES_CHECK_METHOD_COMBO_BOX { + let text = match i.check_method { + CheckingMethod::Hash => fl!("duplicate_mode_hash_combo_box"), + CheckingMethod::Size => fl!("duplicate_mode_size_combo_box"), + CheckingMethod::Name => fl!("duplicate_mode_name_combo_box"), + _ => { + panic!() + } + }; + self.combo_box_duplicate_check_method.append_text(&text); + } + self.combo_box_duplicate_check_method.set_active(Some(active)); + } } } diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index 315db8b85..64c19b46f 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -17,7 +17,7 @@ use czkawka_core::similar_videos::SimilarVideos; use czkawka_core::temporary::Temporary; use czkawka_core::{fl, invalid_symlinks}; -use crate::notebook_enums::{NotebookMainEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS}; +use crate::notebook_enums::{NotebookMainEnum, NotebookUpperEnum, NUMBER_OF_NOTEBOOK_MAIN_TABS}; #[cfg(not(target_family = "windows"))] pub const CHARACTER: char = '/'; @@ -486,6 +486,16 @@ pub fn get_notebook_enum_from_tree_view(tree_view: >k::TreeView) -> NotebookMa } } +pub fn get_notebook_upper_enum_from_tree_view(tree_view: >k::TreeView) -> NotebookUpperEnum { + match (*tree_view).widget_name().to_string().as_str() { + "tree_view_upper_included_directories" => NotebookUpperEnum::IncludedDirectories, + "tree_view_upper_excluded_directories" => NotebookUpperEnum::ExcludedDirectories, + e => { + panic!("{}", e) + } + } +} + pub fn get_notebook_object_from_tree_view(tree_view: >k::TreeView) -> &NotebookObject { let nb_enum = get_notebook_enum_from_tree_view(tree_view); &NOTEBOOKS_INFOS[nb_enum as usize] diff --git a/czkawka_gui/src/initialize_gui.rs b/czkawka_gui/src/initialize_gui.rs index 56414edec..5e9d0777c 100644 --- a/czkawka_gui/src/initialize_gui.rs +++ b/czkawka_gui/src/initialize_gui.rs @@ -101,8 +101,8 @@ pub fn initialize_gui(gui_data: &mut GuiData) { // Set step increment { let scale_similarity_similar_images = gui_data.main_notebook.scale_similarity_similar_images.clone(); - scale_similarity_similar_images.set_range(0_f64, SIMILAR_VALUES[1][5] as f64); // This defaults to value of minimal size of hash 8 - scale_similarity_similar_images.set_fill_level(SIMILAR_VALUES[1][5] as f64); + scale_similarity_similar_images.set_range(0_f64, SIMILAR_VALUES[0][5] as f64); // This defaults to value of minimal size of hash 8 + scale_similarity_similar_images.set_fill_level(SIMILAR_VALUES[0][5] as f64); scale_similarity_similar_images.adjustment().set_step_increment(1_f64); } // Set step increment @@ -409,9 +409,12 @@ pub fn initialize_gui(gui_data: &mut GuiData) { create_tree_view_included_directories(&tree_view); + tree_view.set_widget_name("tree_view_upper_included_directories"); scrolled_window.add(&tree_view); scrolled_window.show_all(); + tree_view.connect_button_press_event(opening_double_click_function_directories); + evk.connect_key_pressed(opening_enter_function_ported_upper_directories); evk.connect_key_released(move |_event_controller_key, _key_value, key_code, _modifier_type| { if key_code == KEY_DELETE { let list_store = get_list_store(&tree_view); @@ -439,9 +442,12 @@ pub fn initialize_gui(gui_data: &mut GuiData) { create_tree_view_excluded_directories(&tree_view); + tree_view.set_widget_name("tree_view_upper_excluded_directories"); scrolled_window.add(&tree_view); scrolled_window.show_all(); + tree_view.connect_button_press_event(opening_double_click_function_directories); + evk.connect_key_pressed(opening_enter_function_ported_upper_directories); evk.connect_key_released(move |_event_controller_key, _key_value, key_code, _modifier_type| { if key_code == KEY_DELETE { let list_store = get_list_store(&tree_view); @@ -520,7 +526,6 @@ fn connect_event_mouse(gui_data: &GuiData) { let preview_path = gui_data.preview_path.clone(); let image_preview = gui_data.main_notebook.image_preview_similar_images.clone(); - tree_view.connect_button_press_event(opening_double_click_function); tree_view.connect_button_release_event(move |tree_view, _event| { let nb_object = &NOTEBOOKS_INFOS[NotebookMainEnum::SimilarImages as usize]; let preview_path = preview_path.clone(); diff --git a/czkawka_gui/src/opening_selecting_records.rs b/czkawka_gui/src/opening_selecting_records.rs index e9673d0e1..e2d56e923 100644 --- a/czkawka_gui/src/opening_selecting_records.rs +++ b/czkawka_gui/src/opening_selecting_records.rs @@ -2,6 +2,7 @@ use gdk::ModifierType; use gtk::prelude::*; use crate::help_functions::*; +use crate::notebook_enums::NotebookUpperEnum; // TODO add option to open files and folders from context menu activated by pressing ONCE with right mouse button @@ -24,6 +25,32 @@ pub fn opening_enter_function_ported(event_controller: >k::EventControllerKey, false // True catches signal, and don't send it to function, e.g. up button is catched and don't move selection } +pub fn opening_enter_function_ported_upper_directories(event_controller: >k::EventControllerKey, _key_value: u32, key_code: u32, _modifier_type: ModifierType) -> bool { + let tree_view = event_controller.widget().unwrap().downcast::().unwrap(); + #[cfg(debug_assertions)] + { + println!("key_code {}", key_code); + } + + match get_notebook_upper_enum_from_tree_view(&tree_view) { + NotebookUpperEnum::IncludedDirectories => { + handle_tree_keypress_upper_directories( + &tree_view, + key_code, + ColumnsIncludedDirectory::Path as i32, + Some(ColumnsIncludedDirectory::ReferenceButton as i32), + ); + } + NotebookUpperEnum::ExcludedDirectories => { + handle_tree_keypress_upper_directories(&tree_view, key_code, ColumnsExcludedDirectory::Path as i32, None); + } + _ => { + panic!() + } + } + false // True catches signal, and don't send it to function, e.g. up button is catched and don't move selection +} + pub fn opening_double_click_function(tree_view: >k::TreeView, event: &gdk::EventButton) -> gtk::Inhibit { let nt_object = get_notebook_object_from_tree_view(tree_view); if event.event_type() == gdk::EventType::DoubleButtonPress && event.button() == 1 { @@ -34,6 +61,23 @@ pub fn opening_double_click_function(tree_view: >k::TreeView, event: &gdk::Eve gtk::Inhibit(false) } +pub fn opening_double_click_function_directories(tree_view: >k::TreeView, event: &gdk::EventButton) -> gtk::Inhibit { + if event.event_type() == gdk::EventType::DoubleButtonPress && (event.button() == 1 || event.button() == 3) { + match get_notebook_upper_enum_from_tree_view(tree_view) { + NotebookUpperEnum::IncludedDirectories => { + common_open_function_upper_directories(tree_view, ColumnsIncludedDirectory::Path as i32); + } + NotebookUpperEnum::ExcludedDirectories => { + common_open_function_upper_directories(tree_view, ColumnsExcludedDirectory::Path as i32); + } + _ => { + panic!() + } + } + } + gtk::Inhibit(false) +} + // // GTK 4 // pub fn opening_enter_function_ported(event_controller: >k4::EventControllerKey, _key: gdk4::keys::Key, key_code: u32, _modifier_type: ModifierType) -> gtk4::Inhibit { // let tree_view = event_controller.widget().unwrap().downcast::().unwrap(); @@ -65,7 +109,7 @@ enum OpenMode { PathAndName, } -fn common_mark_function(tree_view: >k::TreeView, column_name: i32, column_color: Option) { +fn common_mark_function(tree_view: >k::TreeView, column_selection: i32, column_color: Option) { let selection = tree_view.selection(); let (selected_rows, tree_model) = selection.selected_rows(); @@ -77,8 +121,8 @@ fn common_mark_function(tree_view: >k::TreeView, column_name: i32, column_colo continue; } } - let value = !tree_model.value(&tree_model.iter(tree_path).unwrap(), column_name).get::().unwrap(); - model.set_value(&tree_model.iter(tree_path).unwrap(), column_name as u32, &value.to_value()); + let value = !tree_model.value(&tree_model.iter(tree_path).unwrap(), column_selection).get::().unwrap(); + model.set_value(&tree_model.iter(tree_path).unwrap(), column_selection as u32, &value.to_value()); } } @@ -103,6 +147,31 @@ fn common_open_function(tree_view: >k::TreeView, column_name: i32, column_path } } +fn common_open_function_upper_directories(tree_view: >k::TreeView, column_full_path: i32) { + let selection = tree_view.selection(); + let (selected_rows, tree_model) = selection.selected_rows(); + + for tree_path in selected_rows.iter().rev() { + let full_path = tree_model.value(&tree_model.iter(tree_path).unwrap(), column_full_path).get::().unwrap(); + + open::that_in_background(&full_path); + } +} + +fn handle_tree_keypress_upper_directories(tree_view: >k::TreeView, key_code: u32, full_path_column: i32, mark_column: Option) { + match key_code { + KEY_ENTER => { + common_open_function_upper_directories(tree_view, full_path_column); + } + KEY_SPACE => { + if let Some(mark_column) = mark_column { + common_mark_function(tree_view, mark_column, None); + } + } + _ => {} + } +} + fn handle_tree_keypress(tree_view: >k::TreeView, key_code: u32, name_column: i32, path_column: i32, mark_column: i32, column_color: Option) { match key_code { KEY_ENTER => { From fbf924e2e9f050e8c0d711ea49eb9cb6e95328ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Thu, 23 Dec 2021 22:25:35 +0100 Subject: [PATCH 7/9] Rozmiar i Nazwa --- czkawka_core/src/common_directory.rs | 2 +- czkawka_core/src/duplicate.rs | 103 ++++++- czkawka_gui/src/compute_results.rs | 371 +++++++++++++++++------ czkawka_gui/src/connect_button_search.rs | 1 + czkawka_gui/src/gui_main_notebook.rs | 5 +- 5 files changed, 380 insertions(+), 102 deletions(-) diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index 0266e24d1..7f2a04c6d 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -263,7 +263,7 @@ impl Directories { { let mut ref_folders = Vec::new(); for folder in &self.reference_directories { - if self.included_directories.contains(folder) { + if self.included_directories.iter().any(|e| folder.starts_with(&e)) { ref_folders.push(folder.clone()); println!("REF: VALID reference folder {:?}", folder); } else { diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index ead7afc94..c73d13584 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -105,9 +105,12 @@ impl Info { pub struct DuplicateFinder { text_messages: Messages, information: Info, - files_with_identical_names: BTreeMap>, // File Size, File Entry - files_with_identical_size: BTreeMap>, // File Size, File Entry - files_with_identical_hashes: BTreeMap>>, // File Size, File Entry + files_with_identical_names: BTreeMap>, // File Size, File Entry + files_with_identical_size: BTreeMap>, // File Size, File Entry + files_with_identical_hashes: BTreeMap>>, // File Size, next grouped by file size, next grouped by hash + files_with_identical_names_referenced: BTreeMap)>, // File Size, File Entry + files_with_identical_size_referenced: BTreeMap)>, // File Size, File Entry + files_with_identical_hashes_referenced: BTreeMap)>>, // File Size, next grouped by file size, next grouped by hash directories: Directories, allowed_extensions: Extensions, excluded_items: ExcludedItems, @@ -125,6 +128,7 @@ pub struct DuplicateFinder { minimal_cache_file_size: u64, minimal_prehash_cache_file_size: u64, delete_outdated_cache: bool, + use_reference_folders: bool, } impl DuplicateFinder { @@ -135,6 +139,9 @@ impl DuplicateFinder { files_with_identical_names: Default::default(), files_with_identical_size: Default::default(), files_with_identical_hashes: Default::default(), + files_with_identical_names_referenced: Default::default(), + files_with_identical_size_referenced: Default::default(), + files_with_identical_hashes_referenced: Default::default(), recursive_search: true, allowed_extensions: Extensions::new(), check_method: CheckingMethod::None, @@ -152,11 +159,13 @@ impl DuplicateFinder { minimal_cache_file_size: 1024 * 1024 / 4, // By default cache only >= 256 KB files minimal_prehash_cache_file_size: 0, delete_outdated_cache: true, + use_reference_folders: false, } } pub fn find_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender>) { self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); + self.use_reference_folders = !self.directories.reference_directories.is_empty(); match self.check_method { CheckingMethod::Name => { @@ -270,23 +279,43 @@ impl DuplicateFinder { }; } + pub fn get_use_reference(&self) -> bool { + self.use_reference_folders + } + pub fn set_recursive_search(&mut self, recursive_search: bool) { self.recursive_search = recursive_search; } - pub fn set_included_directory(&mut self, included_directory: Vec) -> bool { - self.directories.set_included_directory(included_directory, &mut self.text_messages) + pub fn set_included_directory(&mut self, included_directory: Vec) { + self.directories.set_included_directory(included_directory, &mut self.text_messages); + } + + pub fn set_reference_directory(&mut self, reference_directory: Vec) { + self.directories.set_reference_directory(reference_directory); } pub fn set_excluded_directory(&mut self, excluded_directory: Vec) { self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); } + + pub fn set_excluded_items(&mut self, excluded_items: Vec) { + self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); + } pub fn set_allowed_extensions(&mut self, allowed_extensions: String) { self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages); } - pub fn set_excluded_items(&mut self, excluded_items: Vec) { - self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); + pub fn get_files_with_identical_hashes_referenced(&self) -> &BTreeMap)>> { + &self.files_with_identical_hashes_referenced + } + + pub fn get_files_with_identical_name_referenced(&self) -> &BTreeMap)> { + &self.files_with_identical_names_referenced + } + + pub fn get_files_with_identical_size_referenced(&self) -> &BTreeMap)> { + &self.files_with_identical_size_referenced } fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::UnboundedSender>) -> bool { @@ -480,6 +509,36 @@ impl DuplicateFinder { } self.files_with_identical_names = new_map; + // Reference - only use in size, because later hash will be counted differently + if self.use_reference_folders { + let mut btree_map = Default::default(); + mem::swap(&mut self.files_with_identical_names, &mut btree_map); + let reference_directories = self.directories.reference_directories.clone(); + let vec = btree_map + .into_iter() + .filter_map(|(_size, vec_file_entry)| { + let mut files_from_referenced_folders = Vec::new(); + let mut normal_files = Vec::new(); + for file_entry in vec_file_entry { + if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { + files_from_referenced_folders.push(file_entry); + } else { + normal_files.push(file_entry); + } + } + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); + for (fe, vec_fe) in vec { + self.files_with_identical_names_referenced.insert(fe.path.to_string_lossy().to_string(), (fe, vec_fe)); + } + } + Common::print_time(start_time, SystemTime::now(), "check_files_name".to_string()); true } @@ -691,6 +750,36 @@ impl DuplicateFinder { } } + // Reference - only use in size, because later hash will be counted differently + if self.use_reference_folders && self.check_method == CheckingMethod::Size { + let mut btree_map = Default::default(); + mem::swap(&mut self.files_with_identical_size, &mut btree_map); + let reference_directories = self.directories.reference_directories.clone(); + let vec = btree_map + .into_iter() + .filter_map(|(_size, vec_file_entry)| { + let mut files_from_referenced_folders = Vec::new(); + let mut normal_files = Vec::new(); + for file_entry in vec_file_entry { + if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { + files_from_referenced_folders.push(file_entry); + } else { + normal_files.push(file_entry); + } + } + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); + for (fe, vec_fe) in vec { + self.files_with_identical_size_referenced.insert(fe.size, (fe, vec_fe)); + } + } + Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string()); true } diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index 18f75a755..de94d2fb1 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -77,6 +77,12 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< if df.get_stopped_search() { entry_info.set_text(&fl!("compute_stopped_by_user")); } else { + if df.get_use_reference() { + tree_view_duplicate_finder.selection().set_select_function(Some(Box::new(select_function_always_true))); + } else { + tree_view_duplicate_finder.selection().set_select_function(Some(Box::new(select_function_duplicates))); + } + let information = df.get_information(); let text_messages = df.get_text_messages(); @@ -139,65 +145,196 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< { let list_store = get_list_store(&tree_view_duplicate_finder); - match df.get_check_method() { - CheckingMethod::Name => { - let btreemap = df.get_files_sorted_by_names(); - - for (_name, vector) in btreemap.iter().rev() { - // Sort - let vector = if vector.len() >= 2 { - let mut vector = vector.clone(); - vector.sort_by_key(|e| { - let t = split_path(e.path.as_path()); - (t.0, t.1) - }); - vector - } else { - vector.clone() - }; - - let values: [(u32, &dyn ToValue); 9] = [ - (ColumnsDuplicates::ActivatableSelectButton as u32, &false), - (ColumnsDuplicates::SelectionButton as u32, &false), - (ColumnsDuplicates::Size as u32, (&"".to_string())), - (ColumnsDuplicates::Name as u32, (&"".to_string())), - (ColumnsDuplicates::Path as u32, (&(format!("{} results", vector.len())))), - (ColumnsDuplicates::Modification as u32, (&"".to_string())), // No text in 3 column - (ColumnsDuplicates::ModificationAsSecs as u32, (&(0))), // Not used here - (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), - (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), - ]; + if df.get_use_reference() { + match df.get_check_method() { + CheckingMethod::Name => { + let btreemap = df.get_files_with_identical_name_referenced(); - list_store.set(&list_store.append(), &values); - for entry in vector { - let (directory, file) = split_path(&entry.path); + for (_name, (base_file_entry, vector)) in btreemap.iter().rev() { + // Sort + let vector = if vector.len() >= 2 { + let mut vector = vector.clone(); + vector.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vector + } else { + vector.clone() + }; + + // HEADER + let (directory, file) = split_path(&base_file_entry.path); let values: [(u32, &dyn ToValue); 9] = [ - (ColumnsDuplicates::ActivatableSelectButton as u32, &true), + (ColumnsDuplicates::ActivatableSelectButton as u32, &false), (ColumnsDuplicates::SelectionButton as u32, &false), - (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Size as u32, (&base_file_entry.size.file_size(options::BINARY).unwrap())), (ColumnsDuplicates::Name as u32, &file), (ColumnsDuplicates::Path as u32, &directory), ( ColumnsDuplicates::Modification as u32, &(format!( "{} - ({})", - NaiveDateTime::from_timestamp(entry.modified_date as i64, 0), - entry.size.file_size(options::BINARY).unwrap() + NaiveDateTime::from_timestamp(base_file_entry.modified_date as i64, 0), + base_file_entry.size.file_size(options::BINARY).unwrap() )), ), - (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), - (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsDuplicates::ModificationAsSecs as u32, &(base_file_entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + + list_store.set(&list_store.append(), &values); + + // MEAT + for entry in vector { + let (directory, file) = split_path(&entry.path); + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &true), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Name as u32, &file), + (ColumnsDuplicates::Path as u32, &directory), + ( + ColumnsDuplicates::Modification as u32, + &(format!( + "{} - ({})", + NaiveDateTime::from_timestamp(entry.modified_date as i64, 0), + entry.size.file_size(options::BINARY).unwrap() + )), + ), + (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } + } + } + CheckingMethod::Hash => { + let btreemap = df.get_files_with_identical_hashes_referenced(); + + for (_size, vectors_vector) in btreemap.iter().rev() { + for (base_file_entry, vector) in vectors_vector { + // Sort + let vector = if vector.len() >= 2 { + let mut vector = vector.clone(); + vector.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vector + } else { + vector.clone() + }; + + // HEADER + let (directory, file) = split_path(&base_file_entry.path); + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &false), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&base_file_entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Name as u32, &file), + (ColumnsDuplicates::Path as u32, &directory), + ( + ColumnsDuplicates::Modification as u32, + &(NaiveDateTime::from_timestamp(base_file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsDuplicates::ModificationAsSecs as u32, &(base_file_entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + + // MEAT + list_store.set(&list_store.append(), &values); + for entry in vector { + let (directory, file) = split_path(&entry.path); + + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &true), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Name as u32, &file), + (ColumnsDuplicates::Path as u32, &directory), + ( + ColumnsDuplicates::Modification as u32, + &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), + ), + (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + + list_store.set(&list_store.append(), &values); + } + } + } + } + CheckingMethod::Size => { + let btreemap = df.get_files_with_identical_size_referenced(); + + for (_size, (base_file_entry, vector)) in btreemap.iter().rev() { + // Sort + let vector = if vector.len() >= 2 { + let mut vector = vector.clone(); + vector.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vector + } else { + vector.clone() + }; + + // HEADER + let (directory, file) = split_path(&base_file_entry.path); + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &false), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&base_file_entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Name as u32, &file), + (ColumnsDuplicates::Path as u32, &directory), + ( + ColumnsDuplicates::Modification as u32, + &(NaiveDateTime::from_timestamp(base_file_entry.modified_date as i64, 0).to_string()), + ), + (ColumnsDuplicates::ModificationAsSecs as u32, &(base_file_entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), ]; + + // MEAT list_store.set(&list_store.append(), &values); + for entry in vector { + let (directory, file) = split_path(&entry.path); + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &true), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Name as u32, &file), + (ColumnsDuplicates::Path as u32, &directory), + ( + ColumnsDuplicates::Modification as u32, + &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), + ), + (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } } } + CheckingMethod::None => { + panic!(); + } } - CheckingMethod::Hash => { - let btreemap = df.get_files_sorted_by_hash(); + } else { + match df.get_check_method() { + CheckingMethod::Name => { + let btreemap = df.get_files_sorted_by_names(); - for (_size, vectors_vector) in btreemap.iter().rev() { - for vector in vectors_vector { + for (_name, vector) in btreemap.iter().rev() { // Sort let vector = if vector.len() >= 2 { let mut vector = vector.clone(); @@ -215,9 +352,9 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< (ColumnsDuplicates::SelectionButton as u32, &false), (ColumnsDuplicates::Size as u32, (&"".to_string())), (ColumnsDuplicates::Name as u32, (&"".to_string())), - (ColumnsDuplicates::Path as u32, (&"".to_string())), - (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column - (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), + (ColumnsDuplicates::Path as u32, (&(format!("{} results", vector.len())))), + (ColumnsDuplicates::Modification as u32, (&"".to_string())), // No text in 3 column + (ColumnsDuplicates::ModificationAsSecs as u32, (&(0))), // Not used here (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), ]; @@ -225,7 +362,6 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< list_store.set(&list_store.append(), &values); for entry in vector { let (directory, file) = split_path(&entry.path); - let values: [(u32, &dyn ToValue); 9] = [ (ColumnsDuplicates::ActivatableSelectButton as u32, &true), (ColumnsDuplicates::SelectionButton as u32, &false), @@ -234,71 +370,126 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< (ColumnsDuplicates::Path as u32, &directory), ( ColumnsDuplicates::Modification as u32, - &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), + &(format!( + "{} - ({})", + NaiveDateTime::from_timestamp(entry.modified_date as i64, 0), + entry.size.file_size(options::BINARY).unwrap() + )), ), (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), ]; + list_store.set(&list_store.append(), &values); + } + } + } + CheckingMethod::Hash => { + let btreemap = df.get_files_sorted_by_hash(); + + for (_size, vectors_vector) in btreemap.iter().rev() { + for vector in vectors_vector { + // Sort + let vector = if vector.len() >= 2 { + let mut vector = vector.clone(); + vector.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vector + } else { + vector.clone() + }; + + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &false), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&"".to_string())), + (ColumnsDuplicates::Name as u32, (&"".to_string())), + (ColumnsDuplicates::Path as u32, (&"".to_string())), + (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column + (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), + (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; list_store.set(&list_store.append(), &values); + for entry in vector { + let (directory, file) = split_path(&entry.path); + + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &true), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Name as u32, &file), + (ColumnsDuplicates::Path as u32, &directory), + ( + ColumnsDuplicates::Modification as u32, + &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), + ), + (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + + list_store.set(&list_store.append(), &values); + } } } } - } - CheckingMethod::Size => { - let btreemap = df.get_files_sorted_by_size(); - - for (_size, vector) in btreemap.iter().rev() { - // Sort - let vector = if vector.len() >= 2 { - let mut vector = vector.clone(); - vector.sort_by_key(|e| { - let t = split_path(e.path.as_path()); - (t.0, t.1) - }); - vector - } else { - vector.clone() - }; - let values: [(u32, &dyn ToValue); 9] = [ - (ColumnsDuplicates::ActivatableSelectButton as u32, &false), - (ColumnsDuplicates::SelectionButton as u32, &false), - (ColumnsDuplicates::Size as u32, (&"".to_string())), - (ColumnsDuplicates::Name as u32, (&"".to_string())), - (ColumnsDuplicates::Path as u32, (&"".to_string())), - (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column - (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), // Not used here - (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), - (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), - ]; + CheckingMethod::Size => { + let btreemap = df.get_files_sorted_by_size(); - list_store.set(&list_store.append(), &values); - for entry in vector { - let (directory, file) = split_path(&entry.path); + for (_size, vector) in btreemap.iter().rev() { + // Sort + let vector = if vector.len() >= 2 { + let mut vector = vector.clone(); + vector.sort_by_key(|e| { + let t = split_path(e.path.as_path()); + (t.0, t.1) + }); + vector + } else { + vector.clone() + }; let values: [(u32, &dyn ToValue); 9] = [ - (ColumnsDuplicates::ActivatableSelectButton as u32, &true), + (ColumnsDuplicates::ActivatableSelectButton as u32, &false), (ColumnsDuplicates::SelectionButton as u32, &false), - (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), - (ColumnsDuplicates::Name as u32, &file), - (ColumnsDuplicates::Path as u32, &directory), - ( - ColumnsDuplicates::Modification as u32, - &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), - ), - (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), - (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsDuplicates::Size as u32, (&"".to_string())), + (ColumnsDuplicates::Name as u32, (&"".to_string())), + (ColumnsDuplicates::Path as u32, (&"".to_string())), + (ColumnsDuplicates::Modification as u32, &"".to_string()), // No text in 3 column + (ColumnsDuplicates::ModificationAsSecs as u32, &(0)), // Not used here + (ColumnsDuplicates::Color as u32, &(HEADER_ROW_COLOR.to_string())), (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), ]; + list_store.set(&list_store.append(), &values); + for entry in vector { + let (directory, file) = split_path(&entry.path); + let values: [(u32, &dyn ToValue); 9] = [ + (ColumnsDuplicates::ActivatableSelectButton as u32, &true), + (ColumnsDuplicates::SelectionButton as u32, &false), + (ColumnsDuplicates::Size as u32, (&entry.size.file_size(options::BINARY).unwrap())), + (ColumnsDuplicates::Name as u32, &file), + (ColumnsDuplicates::Path as u32, &directory), + ( + ColumnsDuplicates::Modification as u32, + &(NaiveDateTime::from_timestamp(entry.modified_date as i64, 0).to_string()), + ), + (ColumnsDuplicates::ModificationAsSecs as u32, &(entry.modified_date)), + (ColumnsDuplicates::Color as u32, &(MAIN_ROW_COLOR.to_string())), + (ColumnsDuplicates::TextColor as u32, &(TEXT_COLOR.to_string())), + ]; + list_store.set(&list_store.append(), &values); + } } } - } - CheckingMethod::None => { - panic!(); + CheckingMethod::None => { + panic!(); + } } } - print_text_messages_to_text_view(text_messages, &text_view_errors); } diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 711417c70..5f59a3b25 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -167,6 +167,7 @@ pub fn connect_button_search( let mut df = DuplicateFinder::new(); df.set_included_directory(included_directories); df.set_excluded_directory(excluded_directories); + df.set_reference_directory(reference_directories); df.set_recursive_search(recursive_search); df.set_excluded_items(excluded_items); df.set_allowed_extensions(allowed_extensions); diff --git a/czkawka_gui/src/gui_main_notebook.rs b/czkawka_gui/src/gui_main_notebook.rs index a7cb2f16b..c7b40864b 100644 --- a/czkawka_gui/src/gui_main_notebook.rs +++ b/czkawka_gui/src/gui_main_notebook.rs @@ -483,10 +483,7 @@ impl GuiMainNotebook { } { - let active = match self.combo_box_duplicate_check_method.active() { - Some(t) => t, - None => 0, - }; + let active = self.combo_box_duplicate_check_method.active().unwrap_or(0); self.combo_box_duplicate_check_method.remove_all(); for i in &DUPLICATES_CHECK_METHOD_COMBO_BOX { let text = match i.check_method { From 686236c8e222d0c86c06ec301b9aef3521f49d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Fri, 24 Dec 2021 08:15:08 +0100 Subject: [PATCH 8/9] =?UTF-8?q?Spanko=20i=20dzia=C5=82anko(przynajmniej=20?= =?UTF-8?q?zdaje=20mi=20si=C4=99=20=C5=BCe=20dzia=C5=82a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- czkawka_core/src/big_file.rs | 7 -- czkawka_core/src/broken_files.rs | 4 - czkawka_core/src/duplicate.rs | 98 +++++++++++++++++++----- czkawka_core/src/empty_files.rs | 4 - czkawka_core/src/invalid_symlinks.rs | 4 - czkawka_core/src/same_music.rs | 32 ++++---- czkawka_core/src/similar_images.rs | 17 +++- czkawka_core/src/similar_videos.rs | 17 +++- czkawka_core/src/temporary.rs | 4 - czkawka_gui/src/compute_results.rs | 2 +- czkawka_gui/src/connect_button_delete.rs | 7 ++ 11 files changed, 129 insertions(+), 67 deletions(-) diff --git a/czkawka_core/src/big_file.rs b/czkawka_core/src/big_file.rs index be12df0ac..69b7541f1 100644 --- a/czkawka_core/src/big_file.rs +++ b/czkawka_core/src/big_file.rs @@ -44,7 +44,6 @@ pub enum DeleteMethod { /// Info struck with helpful information's about results #[derive(Default)] pub struct Info { - pub taken_space: u64, pub number_of_real_files: usize, } @@ -302,7 +301,6 @@ impl BigFile { if self.information.number_of_real_files < self.number_of_files_to_check { new_map.entry(*size).or_insert_with(Vec::new); new_map.get_mut(size).unwrap().push(file.clone()); - self.information.taken_space += size; self.information.number_of_real_files += 1; } else { break; @@ -445,11 +443,6 @@ impl SaveResults for BigFile { impl PrintResults for BigFile { fn print_results(&self) { 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() - ); for (size, vector) in self.big_files.iter().rev() { // TODO Align all to same width for entry in vector { diff --git a/czkawka_core/src/broken_files.rs b/czkawka_core/src/broken_files.rs index 51ac56543..09a939d85 100644 --- a/czkawka_core/src/broken_files.rs +++ b/czkawka_core/src/broken_files.rs @@ -60,8 +60,6 @@ pub enum TypeOfFile { #[derive(Default)] pub struct Info { pub number_of_broken_files: usize, - pub number_of_removed_files: usize, - pub number_of_failed_to_remove_files: usize, } impl Info { @@ -550,8 +548,6 @@ impl DebugPrint for BrokenFiles { println!("Errors size - {}", self.text_messages.errors.len()); println!("Warnings size - {}", self.text_messages.warnings.len()); println!("Messages size - {}", self.text_messages.messages.len()); - println!("Number of removed files - {}", self.information.number_of_removed_files); - println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files); println!("### Other"); diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index c73d13584..6333e7d37 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -92,7 +92,6 @@ pub struct Info { pub number_of_duplicated_files_by_name: usize, pub lost_space_by_size: u64, pub lost_space_by_hash: u64, - pub gained_space: u64, } impl Info { @@ -502,8 +501,6 @@ impl DuplicateFinder { for (name, vector) in &self.files_with_identical_names { if vector.len() > 1 { - self.information.number_of_duplicated_files_by_name += vector.len() - 1; - self.information.number_of_groups_by_name += 1; new_map.insert(name.clone(), vector.clone()); } } @@ -539,6 +536,18 @@ impl DuplicateFinder { } } + if self.use_reference_folders { + for (_fe, vector) in self.files_with_identical_names_referenced.values() { + self.information.number_of_duplicated_files_by_name += vector.len(); + self.information.number_of_groups_by_name += 1; + } + } else { + for vector in self.files_with_identical_names.values() { + self.information.number_of_duplicated_files_by_name += vector.len() - 1; + self.information.number_of_groups_by_name += 1; + } + } + Common::print_time(start_time, SystemTime::now(), "check_files_name".to_string()); true } @@ -743,9 +752,6 @@ impl DuplicateFinder { let vector = if self.ignore_hard_links { filter_hard_links(&vec) } else { vec }; if vector.len() > 1 { - self.information.number_of_duplicated_files_by_size += vector.len() - 1; - self.information.number_of_groups_by_size += 1; - self.information.lost_space_by_size += (vector.len() as u64 - 1) * size; self.files_with_identical_size.insert(size, vector); } } @@ -780,6 +786,20 @@ impl DuplicateFinder { } } + if self.use_reference_folders { + for (size, (_fe, vector)) in &self.files_with_identical_size_referenced { + self.information.number_of_duplicated_files_by_size += vector.len(); + self.information.number_of_groups_by_size += 1; + self.information.lost_space_by_size += (vector.len() as u64) * size; + } + } else { + for (size, vector) in &self.files_with_identical_size { + self.information.number_of_duplicated_files_by_size += vector.len() - 1; + self.information.number_of_groups_by_size += 1; + self.information.lost_space_by_size += (vector.len() as u64 - 1) * size; + } + } + Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string()); true } @@ -1125,9 +1145,57 @@ impl DuplicateFinder { } } } + } + + ///////////////////////////////////////////////////////////////////////////// HASHING END + + // Reference - only use in size, because later hash will be counted differently + if self.use_reference_folders { + let mut btree_map = Default::default(); + mem::swap(&mut self.files_with_identical_hashes, &mut btree_map); + let reference_directories = self.directories.reference_directories.clone(); + let vec = btree_map + .into_iter() + .filter_map(|(_size, vec_vec_file_entry)| { + let mut all_results_with_same_size = Vec::new(); + for vec_file_entry in vec_vec_file_entry { + let mut files_from_referenced_folders = Vec::new(); + let mut normal_files = Vec::new(); + for file_entry in vec_file_entry { + if reference_directories.iter().any(|e| file_entry.path.starts_with(&e)) { + files_from_referenced_folders.push(file_entry); + } else { + normal_files.push(file_entry); + } + } - ///////////////////////// + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + continue; + } else { + all_results_with_same_size.push((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + } + if all_results_with_same_size.is_empty() { + None + } else { + Some(all_results_with_same_size) + } + }) + .collect::)>>>(); + for vec_of_vec in vec { + self.files_with_identical_hashes_referenced.insert(vec_of_vec[0].0.size, vec_of_vec); + } + } + if self.use_reference_folders { + for (size, vector_vectors) in &self.files_with_identical_hashes_referenced { + for (_fe, vector) in vector_vectors { + self.information.number_of_duplicated_files_by_hash += vector.len(); + self.information.number_of_groups_by_hash += 1; + self.information.lost_space_by_hash += (vector.len() as u64) * size; + } + } + } else { for (size, vector_vectors) in &self.files_with_identical_hashes { for vector in vector_vectors { self.information.number_of_duplicated_files_by_hash += vector.len() - 1; @@ -1137,8 +1205,6 @@ impl DuplicateFinder { } } - ///////////////////////////////////////////////////////////////////////////// HASHING END - Common::print_time(start_time, SystemTime::now(), "check_files_hash - full hash".to_string()); // Clean unused data @@ -1158,22 +1224,19 @@ impl DuplicateFinder { match self.check_method { CheckingMethod::Name => { for vector in self.files_with_identical_names.values() { - let tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun); - self.information.gained_space += tuple.0; + let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun); } } CheckingMethod::Hash => { for vector_vectors in self.files_with_identical_hashes.values() { for vector in vector_vectors.iter() { - let tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun); - self.information.gained_space += tuple.0; + let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun); } } } CheckingMethod::Size => { for vector in self.files_with_identical_size.values() { - let tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun); - self.information.gained_space += tuple.0; + let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.text_messages, self.dryrun); } } CheckingMethod::None => { @@ -1229,11 +1292,6 @@ impl DebugPrint for DuplicateFinder { self.information.lost_space_by_hash.file_size(options::BINARY).unwrap(), self.information.lost_space_by_hash ); - println!( - "Gained space by removing duplicated entries - {} ({} bytes)", - self.information.gained_space.file_size(options::BINARY).unwrap(), - self.information.gained_space - ); println!("### Other"); diff --git a/czkawka_core/src/empty_files.rs b/czkawka_core/src/empty_files.rs index 4a4bc3883..49253caf4 100644 --- a/czkawka_core/src/empty_files.rs +++ b/czkawka_core/src/empty_files.rs @@ -43,8 +43,6 @@ pub struct FileEntry { #[derive(Default)] pub struct Info { pub number_of_empty_files: usize, - pub number_of_removed_files: usize, - pub number_of_failed_to_remove_files: usize, } impl Info { @@ -350,8 +348,6 @@ impl DebugPrint for EmptyFiles { println!("Errors size - {}", self.text_messages.errors.len()); println!("Warnings size - {}", self.text_messages.warnings.len()); println!("Messages size - {}", self.text_messages.messages.len()); - println!("Number of removed files - {}", self.information.number_of_removed_files); - println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files); println!("### Other"); diff --git a/czkawka_core/src/invalid_symlinks.rs b/czkawka_core/src/invalid_symlinks.rs index fca7d685d..fe3e08d81 100644 --- a/czkawka_core/src/invalid_symlinks.rs +++ b/czkawka_core/src/invalid_symlinks.rs @@ -53,8 +53,6 @@ pub struct FileEntry { #[derive(Default)] pub struct Info { pub number_of_invalid_symlinks: usize, - pub number_of_removed_files: usize, - pub number_of_failed_to_remove_files: usize, } impl Info { @@ -400,8 +398,6 @@ impl DebugPrint for InvalidSymlinks { println!("Errors size - {}", self.text_messages.errors.len()); println!("Warnings size - {}", self.text_messages.warnings.len()); println!("Messages size - {}", self.text_messages.messages.len()); - println!("Number of removed files - {}", self.information.number_of_removed_files); - println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files); println!("### Other"); diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs index c2fbbc23d..9454e3808 100644 --- a/czkawka_core/src/same_music.rs +++ b/czkawka_core/src/same_music.rs @@ -71,10 +71,8 @@ pub struct FileEntry { /// Info struck with helpful information's about results #[derive(Default)] pub struct Info { - pub number_of_music_entries: usize, - pub number_of_removed_files: usize, - pub number_of_failed_to_remove_files: usize, - pub number_of_duplicates_music_files: usize, + pub number_of_duplicates: usize, + pub number_of_groups: u64, } impl Info { @@ -409,7 +407,6 @@ impl SameMusic { // End thread which send info to gui progress_thread_run.store(false, Ordering::Relaxed); progress_thread_handle.join().unwrap(); - self.information.number_of_music_entries = self.music_entries.len(); Common::print_time(start_time, SystemTime::now(), "check_files".to_string()); true @@ -696,6 +693,10 @@ impl SameMusic { // new_duplicates = Vec::new(); } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + self.duplicated_music_entries = old_duplicates; if self.use_reference_folders { @@ -722,20 +723,20 @@ impl SameMusic { } }) .collect::)>>(); + } - for (_fe, vec) in &self.duplicated_music_entries_referenced { - self.information.number_of_duplicates_music_files += vec.len(); + if self.use_reference_folders { + for (_fe, vector) in &self.duplicated_music_entries_referenced { + self.information.number_of_duplicates += vector.len(); + self.information.number_of_groups += 1; } } else { - for vec in &self.duplicated_music_entries { - self.information.number_of_duplicates_music_files += vec.len() - 1; + for vector in &self.duplicated_music_entries { + self.information.number_of_duplicates += vector.len() - 1; + self.information.number_of_groups += 1; } } - // End thread which send info to gui - progress_thread_run.store(false, Ordering::Relaxed); - progress_thread_handle.join().unwrap(); - Common::print_time(start_time, SystemTime::now(), "check_for_duplicates".to_string()); // Clear unused data @@ -793,9 +794,6 @@ impl DebugPrint for SameMusic { println!("Errors size - {}", self.text_messages.errors.len()); println!("Warnings size - {}", self.text_messages.warnings.len()); println!("Messages size - {}", self.text_messages.messages.len()); - println!("Number of removed files - {}", self.information.number_of_removed_files); - println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files); - println!("Number of duplicated music files - {}", self.information.number_of_duplicates_music_files); println!("### Other"); @@ -838,7 +836,7 @@ impl SaveResults for SameMusic { } if !self.music_entries.is_empty() { - writeln!(writer, "Found {} same music files.", self.information.number_of_music_entries).unwrap(); + writeln!(writer, "Found {} same music files.", self.information.number_of_duplicates).unwrap(); for file_entry in self.music_entries.iter() { writeln!(writer, "{}", file_entry.path.display()).unwrap(); } diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index be3b5312d..4f3a5b909 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -113,9 +113,8 @@ pub struct SimilarImages { /// Info struck with helpful information's about results #[derive(Default)] pub struct Info { - pub number_of_removed_files: usize, - pub number_of_failed_to_remove_files: usize, - pub gained_space: u64, + pub number_of_duplicates: usize, + pub number_of_groups: u64, } impl Info { @@ -744,6 +743,18 @@ impl SimilarImages { Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - selecting data from BtreeMap".to_string()); + if self.use_reference_folders { + for (_fe, vector) in &self.similar_referenced_vectors { + self.information.number_of_duplicates += vector.len(); + self.information.number_of_groups += 1; + } + } else { + for vector in &self.similar_vectors { + self.information.number_of_duplicates += vector.len() - 1; + self.information.number_of_groups += 1; + } + } + // Clean unused data self.image_hashes = Default::default(); self.images_to_check = Default::default(); diff --git a/czkawka_core/src/similar_videos.rs b/czkawka_core/src/similar_videos.rs index 179c14c8e..c6da81d6b 100644 --- a/czkawka_core/src/similar_videos.rs +++ b/czkawka_core/src/similar_videos.rs @@ -85,9 +85,8 @@ pub struct SimilarVideos { /// Info struck with helpful information's about results #[derive(Default)] pub struct Info { - pub number_of_removed_files: usize, - pub number_of_failed_to_remove_files: usize, - pub gained_space: u64, + pub number_of_duplicates: usize, + pub number_of_groups: u64, } impl Info { @@ -585,6 +584,18 @@ impl SimilarVideos { .collect::)>>(); } + if self.use_reference_folders { + for (_fe, vector) in &self.similar_referenced_vectors { + self.information.number_of_duplicates += vector.len(); + self.information.number_of_groups += 1; + } + } else { + for vector in &self.similar_vectors { + self.information.number_of_duplicates += vector.len() - 1; + self.information.number_of_groups += 1; + } + } + Common::print_time(hash_map_modification, SystemTime::now(), "sort_videos - selecting data from BtreeMap".to_string()); // Clean unused data diff --git a/czkawka_core/src/temporary.rs b/czkawka_core/src/temporary.rs index 48ab22866..39393ecfb 100644 --- a/czkawka_core/src/temporary.rs +++ b/czkawka_core/src/temporary.rs @@ -42,8 +42,6 @@ pub struct FileEntry { #[derive(Default)] pub struct Info { pub number_of_temporary_files: usize, - pub number_of_removed_files: usize, - pub number_of_failed_to_remove_files: usize, } impl Info { @@ -357,8 +355,6 @@ impl DebugPrint for Temporary { println!("Errors size - {}", self.text_messages.errors.len()); println!("Warnings size - {}", self.text_messages.warnings.len()); println!("Messages size - {}", self.text_messages.messages.len()); - println!("Number of removed files - {}", self.information.number_of_removed_files); - println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files); println!("### Other"); diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index de94d2fb1..533ad7f78 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -1107,7 +1107,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let information = mf.get_information(); let text_messages = mf.get_text_messages(); - let same_music_number: usize = information.number_of_duplicates_music_files; + let same_music_number: usize = information.number_of_duplicates; entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), same_music_number, fl!("compute_music_files")).as_str()); diff --git a/czkawka_gui/src/connect_button_delete.rs b/czkawka_gui/src/connect_button_delete.rs index 6f191c33b..8ecf4ab65 100644 --- a/czkawka_gui/src/connect_button_delete.rs +++ b/czkawka_gui/src/connect_button_delete.rs @@ -63,6 +63,7 @@ pub async fn delete_things(gui_data: GuiData) { tree_view, column_color, nb_object.column_selection, + nb_object.column_path, &window_main, &check_button_settings_confirm_group_deletion, ) @@ -201,6 +202,7 @@ pub async fn check_if_deleting_all_files_in_group( tree_view: >k::TreeView, column_color: i32, column_selection: i32, + column_path: i32, window_main: >k::Window, check_button_settings_confirm_group_deletion: >k::CheckButton, ) -> bool { @@ -211,6 +213,11 @@ pub async fn check_if_deleting_all_files_in_group( if let Some(iter) = model.iter_first() { assert_eq!(model.value(&iter, column_color).get::().unwrap(), HEADER_ROW_COLOR); // First element should be header + // It is safe to remove any number of files in reference mode + if !model.value(&iter, column_path).get::().unwrap().is_empty() { + return false; + } + loop { if !model.iter_next(&iter) { break; From e86f4e9feb725df6198ab4e21aa44705bddcaf3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= Date: Fri, 24 Dec 2021 09:04:31 +0100 Subject: [PATCH 9/9] Translatanko --- czkawka_core/src/common_directory.rs | 4 +- czkawka_core/src/common_messages.rs | 9 +- czkawka_core/src/similar_images.rs | 8 -- czkawka_gui/src/compute_results.rs | 140 ++++++++++++++------- czkawka_gui/src/connect_change_language.rs | 14 +-- czkawka_gui/src/saving_loading.rs | 20 ++- i18n/en/czkawka_gui.ftl | 35 +++--- 7 files changed, 141 insertions(+), 89 deletions(-) diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index 7f2a04c6d..98dee74d4 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -265,9 +265,9 @@ impl Directories { for folder in &self.reference_directories { if self.included_directories.iter().any(|e| folder.starts_with(&e)) { ref_folders.push(folder.clone()); - println!("REF: VALID reference folder {:?}", folder); + // println!("REF: VALID reference folder {:?}", folder); } else { - println!("REF: Invalid reference folder {:?}", folder); + // println!("REF: Invalid reference folder {:?}", folder); } } self.reference_directories = ref_folders; diff --git a/czkawka_core/src/common_messages.rs b/czkawka_core/src/common_messages.rs index ac0309f95..a97d04fc9 100644 --- a/czkawka_core/src/common_messages.rs +++ b/czkawka_core/src/common_messages.rs @@ -18,7 +18,8 @@ impl Messages { if !self.messages.is_empty() { text_to_return += "-------------------------------MESSAGES--------------------------------\n"; for i in &self.messages { - text_to_return += format!("{}\n", i).as_str(); + text_to_return += i; + text_to_return += "\n"; } text_to_return += "---------------------------END OF MESSAGES-----------------------------\n"; } @@ -27,7 +28,8 @@ impl Messages { text_to_return += "-------------------------------WARNINGS--------------------------------\n"; for i in &self.warnings { - text_to_return += format!("{}\n", i).as_str(); + text_to_return += i; + text_to_return += "\n"; } text_to_return += "---------------------------END OF WARNINGS-----------------------------\n"; } @@ -36,7 +38,8 @@ impl Messages { text_to_return += "--------------------------------ERRORS---------------------------------\n"; for i in &self.errors { - text_to_return += format!("{}\n", i).as_str(); + text_to_return += i; + text_to_return += "\n"; } text_to_return += "----------------------------END OF ERRORS------------------------------\n"; } diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 4f3a5b909..36b6f2c6d 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -194,14 +194,6 @@ impl SimilarImages { &self.similar_referenced_vectors } - pub fn get_number_of_base_duplicated_files(&self) -> usize { - if self.use_reference_folders { - self.similar_referenced_vectors.len() - } else { - self.similar_vectors.len() - } - } - pub fn get_use_reference(&self) -> bool { self.use_reference_folders } diff --git a/czkawka_gui/src/compute_results.rs b/czkawka_gui/src/compute_results.rs index 533ad7f78..965c583fd 100644 --- a/czkawka_gui/src/compute_results.rs +++ b/czkawka_gui/src/compute_results.rs @@ -18,6 +18,7 @@ use crate::help_combo_box::IMAGES_HASH_SIZE_COMBO_BOX; use crate::help_functions::*; use crate::notebook_enums::*; use crate::opening_selecting_records::*; +use czkawka_core::localizer::generate_translation_hashmap; pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver) { let combo_box_image_hash_size = gui_data.main_notebook.combo_box_image_hash_size.clone(); @@ -90,31 +91,31 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let duplicates_size: u64; let duplicates_group: usize; - let fl_found = fl!("compute_found"); - let fl_groups = fl!("compute_groups"); - let fl_groups_which_took = fl!("compute_groups_which_took"); - let fl_duplicated_files_in = fl!("compute_duplicated_files_in"); - match df.get_check_method() { CheckingMethod::Name => { duplicates_number = information.number_of_duplicated_files_by_name; // duplicates_size = 0; duplicates_group = information.number_of_groups_by_name; - entry_info.set_text(format!("{} {} {} {} {}", fl_found, duplicates_number, fl_duplicated_files_in, duplicates_group, fl_groups).as_str()); + entry_info.set_text( + fl!( + "compute_found_duplicates_name", + generate_translation_hashmap(vec![("number_files", duplicates_number.to_string()), ("number_groups", duplicates_group.to_string())]) + ) + .as_str(), + ); } CheckingMethod::Hash => { duplicates_number = information.number_of_duplicated_files_by_hash; duplicates_size = information.lost_space_by_hash; duplicates_group = information.number_of_groups_by_hash; entry_info.set_text( - format!( - "{} {} {} {} {} {}.", - fl_found, - duplicates_number, - fl_duplicated_files_in, - duplicates_group, - fl_groups_which_took, - duplicates_size.file_size(options::BINARY).unwrap() + fl!( + "compute_found_duplicates_hash_size", + generate_translation_hashmap(vec![ + ("number_files", duplicates_number.to_string()), + ("number_groups", duplicates_group.to_string()), + ("size", duplicates_size.file_size(options::BINARY).unwrap()) + ]) ) .as_str(), ); @@ -124,14 +125,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< duplicates_size = information.lost_space_by_size; duplicates_group = information.number_of_groups_by_size; entry_info.set_text( - format!( - "{} {} {} {} {} {}.", - fl_found, - duplicates_number, - fl_duplicated_files_in, - duplicates_group, - fl_groups_which_took, - duplicates_size.file_size(options::BINARY).unwrap() + fl!( + "compute_found_duplicates_hash_size", + generate_translation_hashmap(vec![ + ("number_files", duplicates_number.to_string()), + ("number_groups", duplicates_group.to_string()), + ("size", duplicates_size.file_size(options::BINARY).unwrap()) + ]) ) .as_str(), ); @@ -521,7 +521,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let empty_folder_number: usize = information.number_of_empty_folders; - entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), empty_folder_number, fl!("compute_empty_folders")).as_str()); + entry_info.set_text( + fl!( + "compute_found_empty_folders", + generate_translation_hashmap(vec![("number_files", empty_folder_number.to_string()),]) + ) + .as_str(), + ); // Create GUI { @@ -580,7 +586,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let empty_files_number: usize = information.number_of_empty_files; - entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), empty_files_number, fl!("compute_empty_files")).as_str()); + entry_info.set_text( + fl!( + "compute_found_empty_files", + generate_translation_hashmap(vec![("number_files", empty_files_number.to_string()),]) + ) + .as_str(), + ); // Create GUI { @@ -640,7 +652,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let biggest_files_number: usize = information.number_of_real_files; - entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), biggest_files_number, fl!("compute_biggest_files")).as_str()); + entry_info.set_text( + fl!( + "compute_found_big_files", + generate_translation_hashmap(vec![("number_files", biggest_files_number.to_string()),]) + ) + .as_str(), + ); // Create GUI { @@ -704,7 +722,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let text_messages = tf.get_text_messages(); let temporary_files_number: usize = information.number_of_temporary_files; - entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), temporary_files_number, fl!("compute_temporary_files")).as_str()); + entry_info.set_text( + fl!( + "compute_found_temporary_files", + generate_translation_hashmap(vec![("number_files", temporary_files_number.to_string()),]) + ) + .as_str(), + ); // Create GUI { @@ -766,18 +790,18 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< .selection() .set_select_function(Some(Box::new(select_function_similar_images))); } - //let information = sf.get_information(); + let information = sf.get_information(); let text_messages = sf.get_text_messages(); - let base_images_size = sf.get_number_of_base_duplicated_files(); + let found_any_duplicates = information.number_of_duplicates > 0; entry_info.set_text( - format!( - "{} {} {} {}.", - fl!("compute_found"), - fl!("compute_duplicates_for"), - base_images_size, - fl!("compute_similar_image") + fl!( + "compute_found_images", + generate_translation_hashmap(vec![ + ("number_files", information.number_of_duplicates.to_string()), + ("number_groups", information.number_of_groups.to_string()), + ]) ) .as_str(), ); @@ -920,7 +944,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< &shared_buttons, &NotebookMainEnum::SimilarImages, &["save", "delete", "select", "symlink", "hardlink", "move"], - base_images_size > 0, + found_any_duplicates, ); set_buttons( @@ -942,18 +966,17 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< .selection() .set_select_function(Some(Box::new(select_function_similar_videos))); } - //let information = ff.get_information(); + let information = ff.get_information(); let text_messages = ff.get_text_messages(); - - let base_videos_size = ff.get_similar_videos().len(); + let found_any_duplicates = information.number_of_duplicates > 0; entry_info.set_text( - format!( - "{} {} {} {}.", - fl!("compute_found"), - fl!("compute_duplicates_for"), - base_videos_size, - fl!("compute_similar_videos") + fl!( + "compute_found_videos", + generate_translation_hashmap(vec![ + ("number_files", information.number_of_duplicates.to_string()), + ("number_groups", information.number_of_groups.to_string()), + ]) ) .as_str(), ); @@ -1083,7 +1106,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< &shared_buttons, &NotebookMainEnum::SimilarVideos, &["save", "delete", "select", "symlink", "hardlink", "move"], - base_videos_size > 0, + found_any_duplicates, ); set_buttons( @@ -1109,7 +1132,16 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let same_music_number: usize = information.number_of_duplicates; - entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), same_music_number, fl!("compute_music_files")).as_str()); + entry_info.set_text( + fl!( + "compute_found_music", + generate_translation_hashmap(vec![ + ("number_files", information.number_of_duplicates.to_string()), + ("number_groups", information.number_of_groups.to_string()), + ]) + ) + .as_str(), + ); // Create GUI { @@ -1309,7 +1341,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let invalid_symlinks: usize = information.number_of_invalid_symlinks; - entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), invalid_symlinks, fl!("compute_symlinks")).as_str()); + entry_info.set_text( + fl!( + "compute_found_invalid_symlinks", + generate_translation_hashmap(vec![("number_files", invalid_symlinks.to_string()),]) + ) + .as_str(), + ); // Create GUI { @@ -1367,7 +1405,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< let broken_files_number: usize = information.number_of_broken_files; - entry_info.set_text(format!("{} {} {}.", fl!("compute_found"), broken_files_number, fl!("compute_broken_files")).as_str()); + entry_info.set_text( + fl!( + "compute_found_broken_files", + generate_translation_hashmap(vec![("number_files", broken_files_number.to_string()),]) + ) + .as_str(), + ); // Create GUI { diff --git a/czkawka_gui/src/connect_change_language.rs b/czkawka_gui/src/connect_change_language.rs index adbf98516..daff69f6c 100644 --- a/czkawka_gui/src/connect_change_language.rs +++ b/czkawka_gui/src/connect_change_language.rs @@ -45,18 +45,18 @@ pub fn load_system_language(gui_data: &GuiData) { break; } } - let mut found: bool = false; + // let mut found: bool = false; for (index, lang) in LANGUAGES_ALL.iter().enumerate() { if lang.short_text == short_lang { - found = true; + // found = true; gui_data.settings.combo_box_settings_language.set_active(Some(index as u32)); break; } } - if found { - println!("INFO: Default system language {} is available, so choosing them", short_lang); - } else { - println!("INFO: Default system language {} is not available, using English(en) instead", short_lang); - } + // if found { + // println!("INFO: Default system language {} is available, so choosing them", short_lang); + // } else { + // println!("INFO: Default system language {} is not available, using English(en) instead", short_lang); + // } } } diff --git a/czkawka_gui/src/saving_loading.rs b/czkawka_gui/src/saving_loading.rs index 264159a52..5791d15bf 100644 --- a/czkawka_gui/src/saving_loading.rs +++ b/czkawka_gui/src/saving_loading.rs @@ -214,12 +214,26 @@ pub fn save_configuration(manual_execution: bool, upper_notebook: &GuiUpperNoteb } } if data_saved { - add_text_to_text_view(&text_view_errors, format!("{} {}", fl!("saving_loading_saving_success"), config_file.display()).as_str()); + add_text_to_text_view( + &text_view_errors, + fl!( + "saving_loading_saving_success", + generate_translation_hashmap(vec![("name", config_file.display().to_string())]) + ) + .as_str(), + ); } else { - add_text_to_text_view(&text_view_errors, format!("Failed to save configuration data to file {}", config_file.display()).as_str()); + add_text_to_text_view( + &text_view_errors, + fl!( + "saving_loading_saving_failure", + generate_translation_hashmap(vec![("name", config_file.display().to_string())]) + ) + .as_str(), + ); } } else { - add_text_to_text_view(&text_view_errors, "Failed to get home directory, so can't save file."); + add_text_to_text_view(&text_view_errors, fl!("saving_loading_failed_to_get_home_directory").as_str()); } } diff --git a/i18n/en/czkawka_gui.ftl b/i18n/en/czkawka_gui.ftl index 1c722d405..54551f71e 100644 --- a/i18n/en/czkawka_gui.ftl +++ b/i18n/en/czkawka_gui.ftl @@ -365,21 +365,17 @@ settings_folder_settings_open = Open settings folder # Compute results compute_stopped_by_user = Searching was stopped by user -compute_found = Found -compute_duplicated_files_in = duplicated files in -compute_groups_which_took = groups which took -compute_groups = groups -compute_duplicates_for = duplicates for - -compute_empty_folders = empty folders -compute_empty_files = empty files -compute_biggest_files = biggest files -compute_temporary_files = temporary files -compute_similar_image = images -compute_similar_videos = videos -compute_music_files = music files -compute_symlinks = invalid symlinks -compute_broken_files = broken files +compute_found_duplicates_hash_size = Found { $number_files } duplicates in { $number_groups } groups which took { $size } +compute_found_duplicates_name = Found { $number_files } duplicates in { $number_groups } groups +compute_found_empty_folders = Found { $number_files } empty folders +compute_found_empty_files = Found { $number_files } empty files +compute_found_big_files = Found { $number_files } big files +compute_found_temporary_files = Found { $number_files } temporary files +compute_found_images = Found { $number_files } similar images in { $number_groups } groups +compute_found_videos = Found { $number_files } similar videos in { $number_groups } groups +compute_found_music = Found { $number_files } similar music files in { $number_groups } groups +compute_found_invalid_symlinks = Found { $number_files } invalid symlinks +compute_found_broken_files = Found { $number_files } broken files # Progress window progress_scanning_general_file = Scanning {$file_number} file @@ -399,9 +395,12 @@ progress_current_stage = Current Stage:{" "} progress_all_stages = All Stages:{" "} # Saving loading -saving_loading_saving_success = Saved configuration to file -saving_loading_reset_configuration = Current configuration was cleared -saving_loading_loading_success = Properly loaded configuration from file +saving_loading_saving_success = Saved configuration to file { $name }. +saving_loading_saving_failure = Failed to save configuration data to file { $name }. +saving_loading_reset_configuration = Current configuration was cleared. +saving_loading_loading_success = Properly loaded configuration from file. + +saving_loading_failed_to_get_home_directory = Failed to get home directory to open/save config file. # Invalid symlinks invalid_symlink_infinite_recursion = Infinite recursion