1
0
Fork 0
mirror of synced 2024-04-29 01:52:39 +12:00

Add empty folder support for GUI

This commit is contained in:
Rafał Mikrut 2020-09-22 11:24:55 +02:00
parent 85de5b412d
commit aba4e65fb8
5 changed files with 296 additions and 92 deletions

View file

@ -1,4 +1,6 @@
## Version 0.1.2 dev ## Version 0.1.2 dev
- Add basic search empty folders in GTK GUI
- Remember place where button are placed
## Version 0.1.1 - 20.09.2020r ## Version 0.1.1 - 20.09.2020r
- Added images to readme - Added images to readme

View file

@ -6,6 +6,8 @@ This is my first ever project in Rust so probably a lot of things are written in
## Done ## Done
- Rich instruction with examples - CLI(`cargo run --bin czkawka_cli`) - Rich instruction with examples - CLI(`cargo run --bin czkawka_cli`)
- GTK Frontend(Still WIP) - (`cargo run --bin czkawka_gui`) - GTK Frontend(Still WIP) - (`cargo run --bin czkawka_gui`)
- Basic layout
- Remembering of buttons between different tabs
- Orbtk Frontend(Still very early WIP) - (`cargo run --bin czkawka_gui_orbtk`) - Orbtk Frontend(Still very early WIP) - (`cargo run --bin czkawka_gui_orbtk`)
- Saving results to file - Saving results to file
- Duplicated file finding - Duplicated file finding
@ -20,12 +22,15 @@ This is my first ever project in Rust so probably a lot of things are written in
## TODO ## TODO
- Comments - a lot of things should be described - Comments - a lot of things should be described
- Github CI
- More unit tests - More unit tests
- Finding files with debug symbols - Finding files with debug symbols
- Maybe windows support, but this will need some refactoring in code - Maybe windows support, but this will need some refactoring in code
- Translation support - Translation support
- Add support for fast searching based on checking only first ~1MB of file. - Add support for fast searching based on checking only first ~1MB of file.
- GTK Gui
- Selection of records(don't know how to do this)
- Popups
- Choosing directories(include, excluded)
## Usage and requirements ## Usage and requirements
Rustc 1.46 works fine(not sure about a minimal version) Rustc 1.46 works fine(not sure about a minimal version)
@ -82,7 +87,7 @@ I checked my home directory without any folder exceptions(I removed all director
First run reads file entry and save it to cache so this step is mostly limited by disk performance, and with second run cache helps it so searching is a lot of faster. First run reads file entry and save it to cache so this step is mostly limited by disk performance, and with second run cache helps it so searching is a lot of faster.
Duplicate Checker(Version 0.1) Duplicate Checker(Version 0.1.0)
| App| Executing Time | | App| Executing Time |
|:----------:|:-------------:| |:----------:|:-------------:|

View file

@ -1,5 +1,5 @@
use crate::common::{Common, Messages}; use crate::common::{Common, Messages};
use std::collections::HashMap; use std::collections::BTreeMap;
use std::fs::{File, Metadata}; use std::fs::{File, Metadata};
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
@ -16,9 +16,10 @@ enum FolderEmptiness {
/// Struct assigned to each checked folder with parent path(used to ignore parent if children are not empty) and flag which shows if folder is empty /// Struct assigned to each checked folder with parent path(used to ignore parent if children are not empty) and flag which shows if folder is empty
#[derive(Clone)] #[derive(Clone)]
struct FolderEntry { pub struct FolderEntry {
parent_path: Option<String>, pub parent_path: Option<String>,
is_empty: FolderEmptiness, is_empty: FolderEmptiness,
pub modified_date: SystemTime,
} }
/// Struct to store most basics info about all folder /// Struct to store most basics info about all folder
@ -26,14 +27,14 @@ pub struct EmptyFolder {
information: Info, information: Info,
delete_folders: bool, delete_folders: bool,
text_messages: Messages, text_messages: Messages,
empty_folder_list: HashMap<String, FolderEntry>, // Path, FolderEntry empty_folder_list: BTreeMap<String, FolderEntry>, // Path, FolderEntry
included_directories: Vec<String>, included_directories: Vec<String>,
} }
/// Info struck with helpful information's about results /// Info struck with helpful information's about results
pub struct Info { pub struct Info {
number_of_checked_folders: usize, number_of_checked_folders: usize,
number_of_empty_folders: usize, pub number_of_empty_folders: usize,
} }
impl Info { impl Info {
pub fn new() -> Info { pub fn new() -> Info {
@ -62,9 +63,16 @@ impl EmptyFolder {
} }
} }
pub fn get_empty_folder_list(&self) -> &BTreeMap<String, FolderEntry> {
&self.empty_folder_list
}
pub fn get_text_messages(&self) -> &Messages { pub fn get_text_messages(&self) -> &Messages {
&self.text_messages &self.text_messages
} }
pub fn get_information(&self) -> &Info {
&self.information
}
/// Public function used by CLI to search for empty folders /// Public function used by CLI to search for empty folders
pub fn find_empty_folders(&mut self) { pub fn find_empty_folders(&mut self) {
@ -122,7 +130,7 @@ impl EmptyFolder {
/// Clean directory tree /// Clean directory tree
/// If directory contains only 2 empty folders, then this directory should be removed instead two empty folders inside because it will produce another empty folder. /// If directory contains only 2 empty folders, then this directory should be removed instead two empty folders inside because it will produce another empty folder.
fn optimize_folders(&mut self) { fn optimize_folders(&mut self) {
let mut new_directory_folders: HashMap<String, FolderEntry> = Default::default(); let mut new_directory_folders: BTreeMap<String, FolderEntry> = Default::default();
for entry in &self.empty_folder_list { for entry in &self.empty_folder_list {
match &entry.1.parent_path { match &entry.1.parent_path {
@ -145,7 +153,7 @@ impl EmptyFolder {
fn check_for_empty_folders(&mut self, initial_checking: bool) { fn check_for_empty_folders(&mut self, initial_checking: bool) {
let start_time: SystemTime = SystemTime::now(); let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<String> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector let mut folders_to_check: Vec<String> = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector
let mut folders_checked: HashMap<String, FolderEntry> = Default::default(); let mut folders_checked: BTreeMap<String, FolderEntry> = Default::default();
if initial_checking { if initial_checking {
// Add root folders for finding // Add root folders for finding
@ -155,21 +163,23 @@ impl EmptyFolder {
FolderEntry { FolderEntry {
parent_path: None, parent_path: None,
is_empty: FolderEmptiness::Maybe, is_empty: FolderEmptiness::Maybe,
modified_date: SystemTime::now(),
}, },
); );
folders_to_check.push(id.clone()); folders_to_check.push(id.clone());
} }
} else { } else {
// Add folders searched before // Add folders searched before
for id in &self.empty_folder_list { for (name, entry) in &self.empty_folder_list {
folders_checked.insert( folders_checked.insert(
id.0.clone(), name.clone(),
FolderEntry { FolderEntry {
parent_path: None, parent_path: None,
is_empty: FolderEmptiness::Maybe, is_empty: FolderEmptiness::Maybe,
modified_date: entry.modified_date,
}, },
); );
folders_to_check.push(id.0.clone()); folders_to_check.push(name.clone());
} }
} }
@ -206,6 +216,13 @@ impl EmptyFolder {
FolderEntry { FolderEntry {
parent_path: Option::from(current_folder.clone()), parent_path: Option::from(current_folder.clone()),
is_empty: FolderEmptiness::Maybe, is_empty: FolderEmptiness::Maybe,
modified_date: match metadata.modified() {
Ok(t) => t,
Err(_) => {
self.text_messages.warnings.push(format!("Failed to read modification date of folder {}", current_folder));
SystemTime::now()
}
},
}, },
); );
} else { } else {
@ -234,7 +251,7 @@ impl EmptyFolder {
} }
} else { } else {
// We need to check if parent of folder isn't also empty, because we wan't to delete only parent with two empty folders except this folders and at the end parent folder // We need to check if parent of folder isn't also empty, because we wan't to delete only parent with two empty folders except this folders and at the end parent folder
let mut new_folders_list: HashMap<String, FolderEntry> = Default::default(); let mut new_folders_list: BTreeMap<String, FolderEntry> = Default::default();
for entry in folders_checked { for entry in folders_checked {
if entry.1.is_empty != FolderEmptiness::No && self.empty_folder_list.contains_key(&entry.0) { if entry.1.is_empty != FolderEmptiness::No && self.empty_folder_list.contains_key(&entry.0) {
new_folders_list.insert(entry.0, entry.1); new_folders_list.insert(entry.0, entry.1);

View file

@ -32,11 +32,6 @@ Author: Rafał Mikrut
<!-- interface-name Czkawka --> <!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders etc. --> <!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders etc. -->
<!-- interface-authors Rafa\305\202 Mikrut --> <!-- interface-authors Rafa\305\202 Mikrut -->
<object class="GtkAdjustment" id="adjustment1">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkDialog" id="delete_confirmation_dialog"> <object class="GtkDialog" id="delete_confirmation_dialog">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="icon_name">applications-engineering</property> <property name="icon_name">applications-engineering</property>
@ -272,11 +267,13 @@ Author: Rafał Mikrut
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkEntry"> <object class="GtkEntry" id="duplicate_minimal_size">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="max_length">3</property>
<property name="text" translatable="yes">1</property> <property name="text" translatable="yes">1</property>
<property name="input_purpose">digits</property> <property name="caps_lock_warning">False</property>
<property name="input_purpose">number</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -319,20 +316,12 @@ Author: Rafał Mikrut
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkScrolledWindow" id="notebook_empty_folders_label"> <object class="GtkScrolledWindow" id="scrolled_window_empty_folder_finder">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="shadow_type">in</property> <property name="shadow_type">in</property>
<child> <child>
<object class="GtkTreeView"> <placeholder/>
<property name="visible">True</property>
<property name="can_focus">True</property>
<child internal-child="selection">
<object class="GtkTreeSelection">
<property name="mode">none</property>
</object>
</child>
</object>
</child> </child>
</object> </object>
<packing> <packing>

View file

@ -5,10 +5,13 @@ use humansize::{file_size_opts as options, FileSize};
extern crate gtk; extern crate gtk;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use czkawka_core::duplicate::CheckingMethod; use czkawka_core::duplicate::CheckingMethod;
use czkawka_core::empty_folder::EmptyFolder;
use duplicate::DuplicateFinder; use duplicate::DuplicateFinder;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Builder, TreeView, TreeViewColumn}; use gtk::{Builder, TreeView, TreeViewColumn};
use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
#[derive(Debug)] #[derive(Debug)]
@ -19,10 +22,6 @@ enum ColumnsDuplicate {
Modification, Modification,
} }
thread_local! {
pub static CHECK_TYPE: duplicate::CheckingMethod = duplicate::CheckingMethod::NONE;
}
fn main() { fn main() {
gtk::init().expect("Failed to initialize GTK."); gtk::init().expect("Failed to initialize GTK.");
@ -34,17 +33,36 @@ fn main() {
let main_window: gtk::Window = builder.get_object("main_window").unwrap(); let main_window: gtk::Window = builder.get_object("main_window").unwrap();
main_window.show_all(); main_window.show_all();
////////////////////////////////////////////////////////////////////////////////////////////////
// State
// Buttons State // Buttons State
// let shared_buttons: Rc<RefCell<_>> = Rc::new(RefCell::new( HashMap::<&str, bool>::new())); let shared_buttons: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::<String, HashMap<String, bool>>::new()));
shared_buttons.borrow_mut().clear();
let mut hashmap_buttons: HashMap<&str, bool> = Default::default();
for i in ["duplicate", "empty_folder"].iter() { for i in ["duplicate", "empty_folder"].iter() {
hashmap_buttons.insert(i, false); let mut temp_hashmap: HashMap<String, bool> = Default::default();
for j in ["search", "stop", "resume", "pause", "select", "delete", "save"].iter() {
if *j == "search" {
temp_hashmap.insert(j.to_string(), true);
} else {
temp_hashmap.insert(j.to_string(), false);
}
}
shared_buttons.borrow_mut().insert(i.to_string(), temp_hashmap);
} }
// Duplicate Finder state
let shared_duplication_state: Rc<RefCell<_>> = Rc::new(RefCell::new(DuplicateFinder::new()));
let shared_empty_folders_state: Rc<RefCell<_>> = Rc::new(RefCell::new(EmptyFolder::new()));
////////////////////////////////////////////////////////////////////////////////////////////////
// GUI Notepad Buttons // GUI Notepad Buttons
// GUI Duplicate Entry
let minimal_size_entry: gtk::Entry = builder.get_object("duplicate_minimal_size").unwrap();
// GUI Buttons // GUI Buttons
let buttons_search: gtk::Button = builder.get_object("buttons_search").unwrap(); let buttons_search: gtk::Button = builder.get_object("buttons_search").unwrap();
let buttons_stop: gtk::Button = builder.get_object("buttons_stop").unwrap(); let buttons_stop: gtk::Button = builder.get_object("buttons_stop").unwrap();
@ -66,6 +84,7 @@ fn main() {
for i in notebook_chooser_tool.get_children() { for i in notebook_chooser_tool.get_children() {
notebook_chooser_tool_children_names.push(i.get_buildable_name().unwrap().to_string()); notebook_chooser_tool_children_names.push(i.get_buildable_name().unwrap().to_string());
println!("{}", i.get_buildable_name().unwrap().to_string());
} }
// Entry // Entry
@ -73,28 +92,93 @@ fn main() {
// Scrolled window // Scrolled window
let scrolled_window_duplicate_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_duplicate_finder").unwrap(); let scrolled_window_duplicate_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_duplicate_finder").unwrap();
let scrolled_window_empty_folder_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_empty_folder_finder").unwrap();
{ {
// Set starting intro // Set starting information in bottom panel
// Duplicate Finder
info_entry.set_text("Duplicated Files"); info_entry.set_text("Duplicated Files");
// // Disable all unused buttons // Disable all unused buttons
buttons_search.show(); buttons_search.show();
buttons_save.hide(); buttons_save.hide();
buttons_delete.hide(); buttons_delete.hide();
} }
{ {
// Connect Notebook Tabs
{
let shared_buttons = shared_buttons.clone();
let buttons_search = buttons_search.clone();
let buttons_stop = buttons_stop.clone();
let buttons_resume = buttons_resume.clone();
let buttons_pause = buttons_pause.clone();
let buttons_select = buttons_select.clone();
let buttons_delete = buttons_delete.clone();
let buttons_save = buttons_save.clone();
let notebook_chooser_tool_children_names = notebook_chooser_tool_children_names.clone();
notebook_chooser_tool.connect_switch_page(move |_, _, number| {
let page: &str;
match notebook_chooser_tool_children_names.get(number as usize).unwrap().as_str() {
"notebook_duplicate_finder_label" => {
page = "duplicate";
}
"scrolled_window_empty_folder_finder" => {
page = "empty_folder";
}
e => {
panic!("Not existent page {}", e);
}
};
if *shared_buttons.borrow_mut().get_mut(page).unwrap().get_mut("search").unwrap() == true {
buttons_search.show();
} else {
buttons_search.hide();
}
if *shared_buttons.borrow_mut().get_mut(page).unwrap().get_mut("stop").unwrap() == true {
buttons_stop.show();
} else {
buttons_stop.hide();
}
if *shared_buttons.borrow_mut().get_mut(page).unwrap().get_mut("resume").unwrap() == true {
buttons_resume.show();
} else {
buttons_resume.hide();
}
if *shared_buttons.borrow_mut().get_mut(page).unwrap().get_mut("pause").unwrap() == true {
buttons_pause.show();
} else {
buttons_pause.hide();
}
if *shared_buttons.borrow_mut().get_mut(page).unwrap().get_mut("select").unwrap() == true {
buttons_select.show();
} else {
buttons_select.hide();
}
if *shared_buttons.borrow_mut().get_mut(page).unwrap().get_mut("delete").unwrap() == true {
buttons_delete.show();
} else {
buttons_delete.hide();
}
if *shared_buttons.borrow_mut().get_mut(page).unwrap().get_mut("save").unwrap() == true {
buttons_save.show();
} else {
buttons_save.hide();
}
});
}
// Connect Buttons // Connect Buttons
let buttons_search_clone = buttons_search.clone(); assert!(notebook_chooser_tool_children_names.contains(&"notebook_duplicate_finder_label".to_string()));
assert!(notebook_chooser_tool_children_names.contains(&"scrolled_window_empty_folder_finder".to_string()));
buttons_search.connect_clicked(move |_| { buttons_search.connect_clicked(move |_| {
assert!(notebook_chooser_tool_children_names.contains(&"notebook_duplicate_finder_label".to_string()));
assert!(notebook_chooser_tool_children_names.contains(&"notebook_empty_folders_label".to_string()));
match notebook_chooser_tool_children_names.get(notebook_chooser_tool.get_current_page().unwrap() as usize).unwrap().as_str() { match notebook_chooser_tool_children_names.get(notebook_chooser_tool.get_current_page().unwrap() as usize).unwrap().as_str() {
"notebook_duplicate_finder_label" => { "notebook_duplicate_finder_label" => {
// Find duplicates
// TODO Change to proper value // TODO Change to proper value
let mut df = DuplicateFinder::new(); let mut df = DuplicateFinder::new();
let check_method = duplicate::CheckingMethod::HASH; let check_method = duplicate::CheckingMethod::HASH;
@ -130,29 +214,52 @@ fn main() {
info_entry.set_text(format!("Found {} duplicates files in {} groups which took {}.", duplicates_number, duplicates_group, duplicates_size.file_size(options::BINARY).unwrap()).as_str()); info_entry.set_text(format!("Found {} duplicates files in {} groups which took {}.", duplicates_number, duplicates_group, duplicates_size.file_size(options::BINARY).unwrap()).as_str());
// Set Scrolled window // Create GUI
{
// Remove scrolled window from before - BUG - when doing it when view is scrolled, then scroll button disappears
for i in &scrolled_window_duplicate_finder.get_children() {
scrolled_window_duplicate_finder.remove(i);
}
//let results =df. let col_types: [glib::types::Type; 3] = [glib::types::Type::String, glib::types::Type::String, glib::types::Type::String];
let list_store: gtk::ListStore = gtk::ListStore::new(&col_types);
// Remove scrolled window from before - BUG - when doing it when view is scrolled, then scroll button disappears let mut tree_view_duplicate_finder: gtk::TreeView = TreeView::with_model(&list_store);
for i in &scrolled_window_duplicate_finder.get_children() {
scrolled_window_duplicate_finder.remove(i);
}
let col_types: [glib::types::Type; 3] = [glib::types::Type::String, glib::types::Type::String, glib::types::Type::String]; create_tree_view_duplicates(&mut tree_view_duplicate_finder);
let list_store: gtk::ListStore = gtk::ListStore::new(&col_types);
let mut tree_view_duplicate_finder: gtk::TreeView = TreeView::with_model(&list_store); let col_indices = [0, 1, 2];
create_tree_view_duplicates(&mut tree_view_duplicate_finder); match check_method {
CheckingMethod::HASH => {
let hashmap = df.get_files_sorted_by_hash();
let col_indices = [0, 1, 2]; for (size, vectors_vector) in hashmap {
match check_method { for vector in vectors_vector {
CheckingMethod::HASH => { let values: [&dyn ToValue; 3] = [
let hashmap = df.get_files_sorted_by_hash(); &(vector.len().to_string() + " x " + size.to_string().as_str()),
&("(".to_string() + ((vector.len() - 1) as u64 * *size as u64).to_string().as_str() + ")"),
&"Bytes lost".to_string(),
];
list_store.set(&list_store.append(), &col_indices, &values);
for entry in vector {
let path = &entry.path;
let index = path.rfind('/').unwrap();
for (size, vectors_vector) in hashmap { let values: [&dyn ToValue; 3] = [
for vector in vectors_vector { &(path[index + 1..].to_string()),
&(path[..index].to_string()),
&(NaiveDateTime::from_timestamp(entry.modified_date.duration_since(UNIX_EPOCH).expect("Invalid file date").as_secs() as i64, 0).to_string()),
];
list_store.set(&list_store.append(), &col_indices, &values);
}
}
}
}
CheckingMethod::SIZE => {
let hashmap = df.get_files_sorted_by_size();
for (size, vector) in hashmap {
let values: [&dyn ToValue; 3] = [ let values: [&dyn ToValue; 3] = [
&(vector.len().to_string() + " x " + size.to_string().as_str()), &(vector.len().to_string() + " x " + size.to_string().as_str()),
&("(".to_string() + ((vector.len() - 1) as u64 * *size as u64).to_string().as_str() + ")"), &("(".to_string() + ((vector.len() - 1) as u64 * *size as u64).to_string().as_str() + ")"),
@ -172,44 +279,97 @@ fn main() {
} }
} }
} }
} CheckingMethod::NONE => {
CheckingMethod::SIZE => { panic!();
let hashmap = df.get_files_sorted_by_size();
for (size, vector) in hashmap {
let values: [&dyn ToValue; 3] = [
&(vector.len().to_string() + " x " + size.to_string().as_str()),
&("(".to_string() + ((vector.len() - 1) as u64 * *size as u64).to_string().as_str() + ")"),
&"Bytes lost".to_string(),
];
list_store.set(&list_store.append(), &col_indices, &values);
for entry in vector {
let path = &entry.path;
let index = path.rfind('/').unwrap();
let values: [&dyn ToValue; 3] = [
&(path[index + 1..].to_string()),
&(path[..index].to_string()),
&(NaiveDateTime::from_timestamp(entry.modified_date.duration_since(UNIX_EPOCH).expect("Invalid file date").as_secs() as i64, 0).to_string()),
];
list_store.set(&list_store.append(), &col_indices, &values);
}
} }
} }
CheckingMethod::NONE => {
panic!(); scrolled_window_duplicate_finder.add(&tree_view_duplicate_finder);
} scrolled_window_duplicate_finder.show_all();
} }
scrolled_window_duplicate_finder.add(&tree_view_duplicate_finder); // Set state
scrolled_window_duplicate_finder.show_all(); {
*shared_duplication_state.borrow_mut() = df;
// Buttons if duplicates_size > 0 {
buttons_search_clone.show(); buttons_save.show();
buttons_save.hide(); buttons_delete.show();
buttons_delete.hide(); *shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("save").unwrap() = true;
*shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("delete").unwrap() = true;
} else {
buttons_save.hide();
buttons_delete.hide();
*shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("save").unwrap() = false;
*shared_buttons.borrow_mut().get_mut("duplicate").unwrap().get_mut("delete").unwrap() = false;
}
}
}
"scrolled_window_empty_folder_finder" => {
// Find empty folders
// TODO Change to proper value
let mut ef = EmptyFolder::new();
ef.set_include_directory("/home/rafal/Pulpit".to_string());
ef.set_delete_folder(false);
ef.find_empty_folders();
let information = ef.get_information();
let empty_folder_number: usize = information.number_of_empty_folders;
info_entry.set_text(format!("Found {} empty folders.", empty_folder_number).as_str());
// Create GUI
{
// Remove scrolled window from before - BUG - when doing it when view is scrolled, then scroll button disappears
for i in &scrolled_window_empty_folder_finder.get_children() {
scrolled_window_empty_folder_finder.remove(i);
}
let col_types: [glib::types::Type; 3] = [glib::types::Type::String, glib::types::Type::String, glib::types::Type::String];
let list_store: gtk::ListStore = gtk::ListStore::new(&col_types);
let mut tree_view_empty_folder_finder: gtk::TreeView = TreeView::with_model(&list_store);
create_tree_view_empty_folders(&mut tree_view_empty_folder_finder);
let col_indices = [0, 1, 2];
let hashmap = ef.get_empty_folder_list();
for (name, entry) in hashmap {
let name: String = name[..(name.len() - 1)].to_string();
let index = name.rfind('/').unwrap();
let values: [&dyn ToValue; 3] = [
&(name[index + 1..].to_string()),
&(name[..index].to_string()),
&(NaiveDateTime::from_timestamp(entry.modified_date.duration_since(UNIX_EPOCH).expect("Invalid file date").as_secs() as i64, 0).to_string()),
];
list_store.set(&list_store.append(), &col_indices, &values);
}
scrolled_window_empty_folder_finder.add(&tree_view_empty_folder_finder);
scrolled_window_empty_folder_finder.show_all();
}
// Set state
{
*shared_empty_folders_state.borrow_mut() = ef;
if empty_folder_number > 0 {
buttons_save.show();
buttons_delete.show();
*shared_buttons.borrow_mut().get_mut("empty_folder").unwrap().get_mut("save").unwrap() = true;
*shared_buttons.borrow_mut().get_mut("empty_folder").unwrap().get_mut("delete").unwrap() = true;
} else {
buttons_save.hide();
buttons_delete.hide();
*shared_buttons.borrow_mut().get_mut("empty_folder").unwrap().get_mut("save").unwrap() = false;
*shared_buttons.borrow_mut().get_mut("empty_folder").unwrap().get_mut("delete").unwrap() = false;
}
}
} }
"notebook_empty_folders_label" => {}
e => panic!("Not existent {}", e), e => panic!("Not existent {}", e),
} }
}); });
@ -254,3 +414,34 @@ pub fn create_tree_view_duplicates(tree_view_duplicate_finder: &mut gtk::TreeVie
tree_view_duplicate_finder.set_vexpand(true); tree_view_duplicate_finder.set_vexpand(true);
} }
pub fn create_tree_view_empty_folders(tree_view_empty_folder_finder: &mut gtk::TreeView) {
let renderer = gtk::CellRendererText::new();
let name_column: gtk::TreeViewColumn = TreeViewColumn::new();
name_column.pack_start(&renderer, true);
name_column.set_title("Folder Name");
name_column.set_resizable(true);
name_column.set_min_width(50);
name_column.add_attribute(&renderer, "text", ColumnsDuplicate::Name as i32);
tree_view_empty_folder_finder.append_column(&name_column);
let renderer = gtk::CellRendererText::new();
let path_column: gtk::TreeViewColumn = TreeViewColumn::new();
path_column.pack_start(&renderer, true);
path_column.set_title("Path");
path_column.set_resizable(true);
path_column.set_min_width(100);
path_column.add_attribute(&renderer, "text", ColumnsDuplicate::Path as i32);
tree_view_empty_folder_finder.append_column(&path_column);
let renderer = gtk::CellRendererText::new();
let modification_date_column: gtk::TreeViewColumn = TreeViewColumn::new();
modification_date_column.pack_start(&renderer, true);
modification_date_column.set_title("Modification Date");
modification_date_column.set_resizable(true);
modification_date_column.set_min_width(100);
modification_date_column.add_attribute(&renderer, "text", ColumnsDuplicate::Modification as i32);
tree_view_empty_folder_finder.append_column(&modification_date_column);
tree_view_empty_folder_finder.set_vexpand(true);
}