Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hardlinking support for GUI #276

Merged
merged 1 commit into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | • | • | |
Expand Down
2 changes: 1 addition & 1 deletion czkawka_core/src/duplicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1354,7 +1354,7 @@ fn filter_hard_links(vec_file_entry: &[FileEntry]) -> Vec<FileEntry> {
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())?;
Expand Down
44 changes: 44 additions & 0 deletions czkawka_gui/czkawka.glade
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,50 @@ This program is free to use and will always be.
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="buttons_hardlink">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="spacing">2</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">text-x-generic-template</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Hardlink</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
Expand Down
197 changes: 197 additions & 0 deletions czkawka_gui/src/connect_button_hardlink.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
}
let mut vec_tree_path_to_remove: Vec<TreePath> = Vec::new(); // List of hardlinked files without its root
let mut vec_hardlink_data: Vec<HardlinkData> = 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<HardlinkData> = None;
let mut current_selected_index = 0;
loop {
if tree_model.get_value(&current_iter, column_color).get::<String>().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(&current_iter) {
panic!("HEADER, shouldn't be a last item.");
}
continue;
}

if tree_model.get_path(&current_iter).unwrap() == selection_rows[current_selected_index] {
let file_name = tree_model.get_value(&current_iter, column_file_name).get::<String>().unwrap().unwrap();
let path = tree_model.get_value(&current_iter, column_path).get::<String>().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(&current_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(&current_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<gtk::TreePath> = Vec::new();
let mut current_iter = first_iter;
if tree_model.get_value(&current_iter, column_color).get::<String>().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(&current_iter, column_color).get::<String>().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(&current_iter).unwrap());
break 'main;
}

if tree_model.get_value(&next_iter, column_color).get::<String>().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(&current_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(&current_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::<String>().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(&current_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::<String>().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();
}
6 changes: 6 additions & 0 deletions czkawka_gui/src/connect_compute_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
11 changes: 7 additions & 4 deletions czkawka_gui/src/gui_bottom_buttons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions czkawka_gui/src/initialize_gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ 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();
buttons_save.hide();
buttons_delete.hide();
buttons_select.hide();
buttons_symlink.hide();
buttons_hardlink.hide();
}

//// Initialize main scrolled view with notebook
Expand Down
Loading