From 13add1cbedafba43b24a0816bf20bca36b38cfbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= <41945903+qarmin@users.noreply.github.com> Date: Wed, 24 Feb 2021 13:53:46 +0100 Subject: [PATCH] Add hardlinking support for GUI (#276) --- README.md | 1 - czkawka_core/src/duplicate.rs | 2 +- czkawka_gui/czkawka.glade | 44 +++++ czkawka_gui/src/connect_button_hardlink.rs | 197 +++++++++++++++++++++ czkawka_gui/src/connect_compute_results.rs | 6 + czkawka_gui/src/gui_bottom_buttons.rs | 11 +- czkawka_gui/src/initialize_gui.rs | 2 + czkawka_gui/src/main.rs | 3 + 8 files changed, 260 insertions(+), 6 deletions(-) create mode 100644 czkawka_gui/src/connect_button_hardlink.rs diff --git a/README.md b/README.md index 05acacb..787b5e1 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,6 @@ So there is still is a lot of room for improvements. | Language | Rust | Python | Python/Obj-C | | OS | All | Linux only | All | | Framework | GTK 3 | PyGTK | Qt 5 (PyQt)/Cocoa | -| Ram Usage | Low | Medium | Very High | | Duplicate finder | • | • | • | | Empty files | • | • | | | Empty folders | • | • | | diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index 65e2359..06e1e0b 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -1354,7 +1354,7 @@ fn filter_hard_links(vec_file_entry: &[FileEntry]) -> Vec { identical } -fn make_hard_link(src: &PathBuf, dst: &PathBuf) -> io::Result<()> { +pub fn make_hard_link(src: &PathBuf, dst: &PathBuf) -> io::Result<()> { let dst_dir = dst.parent().ok_or_else(|| Error::new(ErrorKind::Other, "No parent"))?; let temp = tempfile::Builder::new().tempfile_in(dst_dir)?; fs::rename(dst, temp.path())?; diff --git a/czkawka_gui/czkawka.glade b/czkawka_gui/czkawka.glade index d9df678..32553d9 100644 --- a/czkawka_gui/czkawka.glade +++ b/czkawka_gui/czkawka.glade @@ -2107,6 +2107,50 @@ This program is free to use and will always be. 3 + + + True + True + True + + + True + False + center + 2 + + + True + False + text-x-generic-template + + + False + True + 0 + + + + + True + False + Hardlink + + + False + True + 1 + + + + + + + False + True + 4 + + False diff --git a/czkawka_gui/src/connect_button_hardlink.rs b/czkawka_gui/src/connect_button_hardlink.rs new file mode 100644 index 0000000..025534d --- /dev/null +++ b/czkawka_gui/src/connect_button_hardlink.rs @@ -0,0 +1,197 @@ +extern crate gtk; +use crate::gui_data::GuiData; +use crate::help_functions::*; +use crate::notebook_enums::*; +use czkawka_core::duplicate::make_hard_link; +use gtk::prelude::*; +use gtk::{TreeIter, TreePath}; +use std::path::PathBuf; + +pub fn connect_button_hardlink(gui_data: &GuiData) { + let gui_data = gui_data.clone(); + + let buttons_hardlink = gui_data.bottom_buttons.buttons_hardlink.clone(); + let notebook_main = gui_data.main_notebook.notebook_main.clone(); + + let tree_view_duplicate_finder = gui_data.main_notebook.tree_view_duplicate_finder.clone(); + let tree_view_similar_images_finder = gui_data.main_notebook.tree_view_similar_images_finder.clone(); + let tree_view_same_music_finder = gui_data.main_notebook.tree_view_same_music_finder.clone(); + + let image_preview_similar_images = gui_data.main_notebook.image_preview_similar_images.clone(); + + buttons_hardlink.connect_clicked(move |_| match to_notebook_main_enum(notebook_main.get_current_page().unwrap()) { + NotebookMainEnum::Duplicate => { + hardlink(tree_view_duplicate_finder.clone(), ColumnsDuplicates::Name as i32, ColumnsDuplicates::Path as i32, ColumnsDuplicates::Color as i32, &gui_data); + } + NotebookMainEnum::SameMusic => { + hardlink(tree_view_same_music_finder.clone(), ColumnsSameMusic::Name as i32, ColumnsSameMusic::Path as i32, ColumnsSameMusic::Color as i32, &gui_data); + } + NotebookMainEnum::SimilarImages => { + hardlink( + tree_view_similar_images_finder.clone(), + ColumnsSimilarImages::Name as i32, + ColumnsSimilarImages::Path as i32, + ColumnsSimilarImages::Color as i32, + &gui_data, + ); + image_preview_similar_images.hide(); + } + e => panic!("Not existent {:?}", e), + }); +} +fn hardlink(tree_view: gtk::TreeView, column_file_name: i32, column_path: i32, column_color: i32, gui_data: &GuiData) { + let text_view_errors = gui_data.text_view_errors.clone(); + reset_text_view(&text_view_errors); + + let list_store = get_list_store(&tree_view); + let selection = tree_view.get_selection(); + + let (selection_rows, tree_model) = selection.get_selected_rows(); + if selection_rows.is_empty() { + return; + } + + struct HardlinkData { + original_data: String, + files_to_hardlink: Vec, + } + let mut vec_tree_path_to_remove: Vec = Vec::new(); // List of hardlinked files without its root + let mut vec_hardlink_data: Vec = Vec::new(); + + let current_iter: TreeIter = tree_model.get_iter_first().unwrap(); // Hardlink button should be only visible when more than 1 element is visible, otherwise it needs to be fixed + let mut current_hardlink_data: Option = None; + let mut current_selected_index = 0; + loop { + if tree_model.get_value(¤t_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + if let Some(current_hardlink_data) = current_hardlink_data { + if !current_hardlink_data.files_to_hardlink.is_empty() { + vec_hardlink_data.push(current_hardlink_data); + } + } + + current_hardlink_data = None; + if !tree_model.iter_next(¤t_iter) { + panic!("HEADER, shouldn't be a last item."); + } + continue; + } + + if tree_model.get_path(¤t_iter).unwrap() == selection_rows[current_selected_index] { + let file_name = tree_model.get_value(¤t_iter, column_file_name).get::().unwrap().unwrap(); + let path = tree_model.get_value(¤t_iter, column_path).get::().unwrap().unwrap(); + let full_file_path = format!("{}/{}", path, file_name); + + if current_hardlink_data.is_some() { + vec_tree_path_to_remove.push(tree_model.get_path(¤t_iter).unwrap()); + let mut temp_data = current_hardlink_data.unwrap(); + temp_data.files_to_hardlink.push(full_file_path); + current_hardlink_data = Some(temp_data); + } else { + current_hardlink_data = Some(HardlinkData { + original_data: full_file_path, + files_to_hardlink: vec![], + }); + } + + if current_selected_index != selection_rows.len() - 1 { + current_selected_index += 1; + } else { + if let Some(current_hardlink_data) = current_hardlink_data { + if !current_hardlink_data.files_to_hardlink.is_empty() { + vec_hardlink_data.push(current_hardlink_data); + } + } + break; // There is no more selected items, so we just end checking + } + } + + if !tree_model.iter_next(¤t_iter) { + if let Some(current_hardlink_data) = current_hardlink_data { + if !current_hardlink_data.files_to_hardlink.is_empty() { + vec_hardlink_data.push(current_hardlink_data); + } + } + + break; + } + } + for hardlink_data in vec_hardlink_data { + for file_to_hardlink in hardlink_data.files_to_hardlink { + match make_hard_link(&PathBuf::from(&hardlink_data.original_data), &PathBuf::from(&file_to_hardlink)) { + Ok(_) => (), + Err(_) => { + add_text_to_text_view(&text_view_errors, format!("Failed to hardlink {}.", file_to_hardlink).as_str()); + continue; + } + } + } + println!(); + } + for tree_path in vec_tree_path_to_remove.iter().rev() { + list_store.remove(&tree_model.get_iter(tree_path).unwrap()); + } + + // Remove only child from header + if let Some(first_iter) = list_store.get_iter_first() { + let mut vec_tree_path_to_delete: Vec = Vec::new(); + let mut current_iter = first_iter; + if tree_model.get_value(¤t_iter, column_color).get::().unwrap().unwrap() != HEADER_ROW_COLOR { + panic!(); // First element should be header + }; + + let mut next_iter; + let mut next_next_iter; + 'main: loop { + if tree_model.get_value(¤t_iter, column_color).get::().unwrap().unwrap() != HEADER_ROW_COLOR { + panic!(); // First element should be header + }; + + next_iter = current_iter.clone(); + if !list_store.iter_next(&next_iter) { + // There is only single header left (H1 -> END) -> (NOTHING) + vec_tree_path_to_delete.push(list_store.get_path(¤t_iter).unwrap()); + break 'main; + } + + if tree_model.get_value(&next_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + // There are two headers each others(we remove just first) -> (H1 -> H2) -> (H2) + vec_tree_path_to_delete.push(list_store.get_path(¤t_iter).unwrap()); + current_iter = next_iter.clone(); + continue 'main; + } + + next_next_iter = next_iter.clone(); + if !list_store.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(list_store.get_path(¤t_iter).unwrap()); + vec_tree_path_to_delete.push(list_store.get_path(&next_iter).unwrap()); + break 'main; + } + + if tree_model.get_value(&next_next_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + // One child between two headers, we can remove them (H1 -> C1 -> H2) -> (H2) + vec_tree_path_to_delete.push(list_store.get_path(¤t_iter).unwrap()); + vec_tree_path_to_delete.push(list_store.get_path(&next_iter).unwrap()); + current_iter = next_next_iter.clone(); + continue 'main; + } + + loop { + // (H1 -> C1 -> C2 -> Cn -> END) -> (NO CHANGE, BECAUSE IS GOOD) + if !list_store.iter_next(&next_next_iter) { + break 'main; + } + // Move to next header + if tree_model.get_value(&next_next_iter, column_color).get::().unwrap().unwrap() == HEADER_ROW_COLOR { + current_iter = next_next_iter.clone(); + continue 'main; + } + } + } + for tree_path in vec_tree_path_to_delete.iter().rev() { + list_store.remove(&list_store.get_iter(&tree_path).unwrap()); + } + } + + selection.unselect_all(); +} diff --git a/czkawka_gui/src/connect_compute_results.rs b/czkawka_gui/src/connect_compute_results.rs index fd910a7..e83407a 100644 --- a/czkawka_gui/src/connect_compute_results.rs +++ b/czkawka_gui/src/connect_compute_results.rs @@ -223,11 +223,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("delete").unwrap() = true; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("select").unwrap() = true; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("symlink").unwrap() = true; + *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("hardlink").unwrap() = true; } else { *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("save").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("delete").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("select").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("symlink").unwrap() = false; + *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap().get_mut("hardlink").unwrap() = false; } set_buttons(&mut *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::Duplicate).unwrap(), &buttons_array, &buttons_names); } @@ -499,11 +501,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("delete").unwrap() = true; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("select").unwrap() = true; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("symlink").unwrap() = true; + *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("hardlink").unwrap() = true; } else { *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("save").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("delete").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("select").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("symlink").unwrap() = false; + *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap().get_mut("hardlink").unwrap() = false; } set_buttons(&mut *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SimilarImages).unwrap(), &buttons_array, &buttons_names); } @@ -665,11 +669,13 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver< *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("delete").unwrap() = true; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("select").unwrap() = true; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("symlink").unwrap() = true; + *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("hardlink").unwrap() = true; } else { *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("save").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("delete").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("select").unwrap() = false; *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("symlink").unwrap() = false; + *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap().get_mut("hardlink").unwrap() = false; } set_buttons(&mut *shared_buttons.borrow_mut().get_mut(&NotebookMainEnum::SameMusic).unwrap(), &buttons_array, &buttons_names); } diff --git a/czkawka_gui/src/gui_bottom_buttons.rs b/czkawka_gui/src/gui_bottom_buttons.rs index 6dc4e7e..5236f8f 100644 --- a/czkawka_gui/src/gui_bottom_buttons.rs +++ b/czkawka_gui/src/gui_bottom_buttons.rs @@ -8,9 +8,10 @@ pub struct GUIBottomButtons { pub buttons_delete: gtk::Button, pub buttons_save: gtk::Button, pub buttons_symlink: gtk::Button, + pub buttons_hardlink: gtk::Button, pub buttons_show_errors: gtk::Button, - pub buttons_names: [String; 5], - pub buttons_array: [Button; 5], + pub buttons_names: [String; 6], + pub buttons_array: [Button; 6], } impl GUIBottomButtons { @@ -20,17 +21,19 @@ impl GUIBottomButtons { let buttons_delete: gtk::Button = builder.get_object("buttons_delete").unwrap(); let buttons_save: gtk::Button = builder.get_object("buttons_save").unwrap(); let buttons_symlink: gtk::Button = builder.get_object("buttons_symlink").unwrap(); + let buttons_hardlink: gtk::Button = builder.get_object("buttons_hardlink").unwrap(); let buttons_show_errors: gtk::Button = builder.get_object("buttons_show_errors").unwrap(); - let buttons_names = ["search".to_string(), "select".to_string(), "delete".to_string(), "save".to_string(), "symlink".to_string()]; - let buttons_array = [buttons_search.clone(), buttons_select.clone(), buttons_delete.clone(), buttons_save.clone(), buttons_symlink.clone()]; + let buttons_names = ["search".to_string(), "select".to_string(), "delete".to_string(), "save".to_string(), "symlink".to_string(), "hardlink".to_string()]; + let buttons_array = [buttons_search.clone(), buttons_select.clone(), buttons_delete.clone(), buttons_save.clone(), buttons_symlink.clone(), buttons_hardlink.clone()]; Self { buttons_search, buttons_select, buttons_delete, buttons_save, buttons_symlink, + buttons_hardlink, buttons_show_errors, buttons_names, buttons_array, diff --git a/czkawka_gui/src/initialize_gui.rs b/czkawka_gui/src/initialize_gui.rs index 23c4255..ace7262 100644 --- a/czkawka_gui/src/initialize_gui.rs +++ b/czkawka_gui/src/initialize_gui.rs @@ -20,6 +20,7 @@ pub fn initialize_gui(gui_data: &mut GuiData) { let buttons_delete = gui_data.bottom_buttons.buttons_delete.clone(); let buttons_select = gui_data.bottom_buttons.buttons_select.clone(); let buttons_symlink = gui_data.bottom_buttons.buttons_symlink.clone(); + let buttons_hardlink = gui_data.bottom_buttons.buttons_hardlink.clone(); // Disable and show buttons - only search button should be visible buttons_search.show(); @@ -27,6 +28,7 @@ pub fn initialize_gui(gui_data: &mut GuiData) { buttons_delete.hide(); buttons_select.hide(); buttons_symlink.hide(); + buttons_hardlink.hide(); } //// Initialize main scrolled view with notebook diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index 9c5ca3f..4ea17a7 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -3,6 +3,7 @@ mod connect_about_buttons; mod connect_button_delete; +mod connect_button_hardlink; mod connect_button_save; mod connect_button_search; mod connect_button_select; @@ -42,6 +43,7 @@ use czkawka_core::*; extern crate gtk; use crate::connect_about_buttons::*; use crate::connect_button_delete::*; +use crate::connect_button_hardlink::*; use crate::connect_button_save::*; use crate::connect_button_search::*; use crate::connect_button_select::*; @@ -120,6 +122,7 @@ fn main() { connect_button_select(&gui_data); connect_button_stop(&gui_data); connect_button_symlink(&gui_data); + connect_button_hardlink(&gui_data); connect_notebook_tabs(&gui_data); connect_selection_of_directories(&gui_data); connect_popovers(&gui_data);