diff --git a/Cargo.lock b/Cargo.lock index a950544..97998e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" +checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4" [[package]] name = "approx" @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" +checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" [[package]] name = "cfg-if" @@ -438,6 +438,7 @@ dependencies = [ "chrono", "crossbeam-channel 0.4.4", "czkawka_core", + "directories-next", "futures", "gdk", "gio", @@ -511,6 +512,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "3.0.1" @@ -531,6 +542,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "discard" version = "1.0.4" @@ -1099,15 +1121,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "libloading" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1090080fe06ec2648d0da3881d9453d97e71a45f00eb179af7fdd7e3f686fdb0" +checksum = "e9367bdfa836b7e3cf895867f7a570283444da90562980ec2263d6e1569b16bc" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -1474,9 +1496,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3741934be594d77de1c8461ebcbbe866f585ea616a9753aa78f2bdc69f0e4579" +checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" dependencies = [ "num-traits", ] @@ -2020,18 +2042,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", @@ -2040,9 +2062,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" dependencies = [ "itoa", "ryu", @@ -2063,9 +2085,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" [[package]] name = "spin_sleep" diff --git a/README.md b/README.md index 5087178..3e97ef4 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ But the most important thing for me was to learn Rust and create a program usefu ## Features - Written in memory safe Rust - Amazingly fast - due using more or less advanced algorithms and multithreading support +- Free, Open Source without ads - CLI frontend, very fast and powerful with rich help - GUI GTK frontend - uses modern GTK 3 and looks similar to FSlint - Light/Dark theme match the appearance of the system(Linux only) diff --git a/czkawka_gui/Cargo.toml b/czkawka_gui/Cargo.toml index 95bb7c5..07ad547 100644 --- a/czkawka_gui/Cargo.toml +++ b/czkawka_gui/Cargo.toml @@ -17,10 +17,15 @@ glib = "0.10.1" humansize = "1" chrono = "0.4" +# Used for sending stop signal across threads crossbeam-channel = "0.4.4" +# To get informations about progress futures = "0.3.8" +# For saving/loading config files to specific directories +directories-next = "2.0.0" + # For opening files open = "1.4.0" diff --git a/czkawka_gui/czkawka.glade b/czkawka_gui/czkawka.glade index 79813f4..52212ad 100644 --- a/czkawka_gui/czkawka.glade +++ b/czkawka_gui/czkawka.glade @@ -881,6 +881,104 @@ Author: RafaƂ Mikrut False + + + True + False + 5 + 5 + 5 + 5 + True + + + Save configuration at exit + True + True + False + True + True + + + 0 + 0 + + + + + Save current configuration + True + True + True + + + 1 + 0 + + + + + Load configuration + True + True + True + + + 3 + 0 + + + + + Reset configuration + True + True + True + + + 2 + 0 + + + + + Load configuration at start + True + True + False + True + True + + + 0 + 1 + + + + + + + + + + + + + + 4 + + + + + True + False + Settings + + + 4 + False + + False diff --git a/czkawka_gui/src/connect_notebook_tabs.rs b/czkawka_gui/src/connect_notebook_tabs.rs index c2be347..f4cc1a6 100644 --- a/czkawka_gui/src/connect_notebook_tabs.rs +++ b/czkawka_gui/src/connect_notebook_tabs.rs @@ -34,13 +34,12 @@ pub fn connect_notebook_tabs(gui_data: &GuiData) { set_buttons(&mut *shared_buttons.borrow_mut().get_mut(page).unwrap(), &buttons_array, &buttons_names); // Upper notebook { - //let upper_notebooks_labels = [/*"general",*/"included_directories","excluded_directories","excluded_items","allowed_extensions"]; let mut hashmap: HashMap<&str, &str> = Default::default(); - //hashmap.insert("notebook_upper_general","general"); hashmap.insert("notebook_upper_included_directories", "included_directories"); hashmap.insert("notebook_upper_excluded_directories", "excluded_directories"); hashmap.insert("notebook_upper_excluded_items", "excluded_items"); hashmap.insert("notebook_upper_allowed_extensions", "allowed_extensions"); + hashmap.insert("notebook_upper_settings", "settings"); for tab in ¬ebook_upper_children_names { let name = hashmap.get(tab.as_str()).unwrap().to_string(); diff --git a/czkawka_gui/src/connect_upper_notebook.rs b/czkawka_gui/src/connect_selection_of_directories.rs similarity index 98% rename from czkawka_gui/src/connect_upper_notebook.rs rename to czkawka_gui/src/connect_selection_of_directories.rs index e52f8db..03ad16c 100644 --- a/czkawka_gui/src/connect_upper_notebook.rs +++ b/czkawka_gui/src/connect_selection_of_directories.rs @@ -2,7 +2,7 @@ extern crate gtk; use crate::gui_data::GuiData; use gtk::prelude::*; -pub fn connect_upper_notebook(gui_data: &GuiData) { +pub fn connect_selection_of_directories(gui_data: &GuiData) { // Add included directory { let scrolled_window_included_directories = gui_data.scrolled_window_included_directories.clone(); diff --git a/czkawka_gui/src/connect_settings.rs b/czkawka_gui/src/connect_settings.rs new file mode 100644 index 0000000..603c846 --- /dev/null +++ b/czkawka_gui/src/connect_settings.rs @@ -0,0 +1,31 @@ +extern crate gtk; +use crate::gui_data::GuiData; +use crate::saving_loading::{load_configuration, reset_configuration, save_configuration}; +use gtk::prelude::*; + +pub fn connect_settings(gui_data: &GuiData) { + // Connect save configuration button + { + let gui_data = gui_data.clone(); + let button_settings_save_configuration = gui_data.button_settings_save_configuration.clone(); + button_settings_save_configuration.connect_clicked(move |_| { + save_configuration(&gui_data, true); + }); + } + // Connect load configuration button + { + let gui_data = gui_data.clone(); + let button_settings_load_configuration = gui_data.button_settings_load_configuration.clone(); + button_settings_load_configuration.connect_clicked(move |_| { + load_configuration(&gui_data, true); + }); + } + // Connect reset configuration button + { + let gui_data = gui_data.clone(); + let button_settings_reset_configuration = gui_data.button_settings_reset_configuration.clone(); + button_settings_reset_configuration.connect_clicked(move |_| { + reset_configuration(&gui_data, true); + }); + } +} diff --git a/czkawka_gui/src/gui_data.rs b/czkawka_gui/src/gui_data.rs index 7e11fb7..51d18e5 100644 --- a/czkawka_gui/src/gui_data.rs +++ b/czkawka_gui/src/gui_data.rs @@ -26,7 +26,7 @@ pub struct GuiData { // States pub main_notebooks_labels: [String; 8], - pub upper_notebooks_labels: [String; 4], + pub upper_notebooks_labels: [String; 5], pub buttons_labels: [String; 4], // Buttons state pub shared_buttons: Rc>>>, @@ -145,6 +145,14 @@ pub struct GuiData { pub grid_progress_stages: gtk::Grid, pub button_stop_in_dialog: gtk::Button, + + //// Settings + pub check_button_settings_save_at_exit: gtk::CheckButton, + pub check_button_settings_load_at_start: gtk::CheckButton, + + pub button_settings_save_configuration: gtk::Button, + pub button_settings_load_configuration: gtk::Button, + pub button_settings_reset_configuration: gtk::Button, //// Threads // Used for sending stop signal to thread @@ -176,10 +184,11 @@ impl GuiData { "same_music".to_string(), ]; let upper_notebooks_labels = [ - /*"general",*/ "included_directories".to_string(), + "included_directories".to_string(), "excluded_directories".to_string(), "excluded_items".to_string(), "allowed_extensions".to_string(), + "settings".to_string(), ]; let buttons_labels = ["search".to_string(), "select".to_string(), "delete".to_string(), "save".to_string()]; @@ -337,6 +346,14 @@ impl GuiData { let button_stop_in_dialog: gtk::Button = builder.get_object("button_stop_in_dialog").unwrap(); + //// Settings + let check_button_settings_save_at_exit: gtk::CheckButton = builder.get_object("check_button_settings_save_at_exit").unwrap(); + let check_button_settings_load_at_start: gtk::CheckButton = builder.get_object("check_button_settings_load_at_start").unwrap(); + + let button_settings_save_configuration: gtk::Button = builder.get_object("button_settings_save_configuration").unwrap(); + let button_settings_load_configuration: gtk::Button = builder.get_object("button_settings_load_configuration").unwrap(); + let button_settings_reset_configuration: gtk::Button = builder.get_object("button_settings_reset_configuration").unwrap(); + //// Threads // Types of messages to send to main thread where gui can be draw. @@ -426,6 +443,11 @@ impl GuiData { label_stage, grid_progress_stages, button_stop_in_dialog, + check_button_settings_save_at_exit, + check_button_settings_load_at_start, + button_settings_save_configuration, + button_settings_load_configuration, + button_settings_reset_configuration, stop_sender, stop_receiver, } diff --git a/czkawka_gui/src/startup_configuration.rs b/czkawka_gui/src/initialize_gui.rs similarity index 84% rename from czkawka_gui/src/startup_configuration.rs rename to czkawka_gui/src/initialize_gui.rs index 0302426..1cf5c4f 100644 --- a/czkawka_gui/src/startup_configuration.rs +++ b/czkawka_gui/src/initialize_gui.rs @@ -4,9 +4,8 @@ use crate::gui_data::*; use crate::help_functions::*; use gtk::prelude::*; use gtk::{SelectionMode, TreeView}; -use std::env; -pub fn startup_configuration(gui_data: &GuiData) { +pub fn initialize_gui(gui_data: &GuiData) { //// Setup default look(duplicate finder) { let entry_info = gui_data.entry_info.clone(); @@ -24,7 +23,6 @@ pub fn startup_configuration(gui_data: &GuiData) { let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone(); let scrolled_window_included_directories = gui_data.scrolled_window_included_directories.clone(); let scrolled_window_excluded_directories = gui_data.scrolled_window_excluded_directories.clone(); - let entry_excluded_items = gui_data.entry_excluded_items.clone(); entry_info.set_text("Duplicated Files"); @@ -201,7 +199,7 @@ pub fn startup_configuration(gui_data: &GuiData) { // Set Included Directory { - let col_types: [glib::types::Type; 2] = [glib::types::Type::String, glib::types::Type::String]; + let col_types: [glib::types::Type; 1] = [glib::types::Type::String]; let list_store: gtk::ListStore = gtk::ListStore::new(&col_types); let mut tree_view_included_directory: gtk::TreeView = TreeView::with_model(&list_store); @@ -210,32 +208,12 @@ pub fn startup_configuration(gui_data: &GuiData) { create_tree_view_directories(&mut tree_view_included_directory); - let col_indices = [0, 1]; - - let current_dir: String = match env::current_dir() { - Ok(t) => t.to_str().unwrap().to_string(), - Err(_) => { - if cfg!(target_family = "unix") { - println!("Failed to read current directory, setting /home instead"); - "/home".to_string() - } else if cfg!(target_family = "windows") { - println!("Failed to read current directory, setting C:\\ instead"); - "C:\\".to_string() - } else { - "".to_string() - } - } - }; - - let values: [&dyn ToValue; 2] = [¤t_dir, &(MAIN_ROW_COLOR.to_string())]; - list_store.set(&list_store.append(), &col_indices, &values); - scrolled_window_included_directories.add(&tree_view_included_directory); scrolled_window_included_directories.show_all(); } // Set Excluded Directory { - let col_types: [glib::types::Type; 2] = [glib::types::Type::String, glib::types::Type::String]; + let col_types: [glib::types::Type; 1] = [glib::types::Type::String]; let list_store: gtk::ListStore = gtk::ListStore::new(&col_types); let mut tree_view_excluded_directory: gtk::TreeView = TreeView::with_model(&list_store); @@ -244,26 +222,8 @@ pub fn startup_configuration(gui_data: &GuiData) { create_tree_view_directories(&mut tree_view_excluded_directory); - let col_indices = [0, 1]; - - if cfg!(target_family = "unix") { - for i in ["/proc", "/dev", "/sys", "/run", "/snap"].iter() { - let values: [&dyn ToValue; 2] = [&i, &(MAIN_ROW_COLOR.to_string())]; - list_store.set(&list_store.append(), &col_indices, &values); - } - } - scrolled_window_excluded_directories.add(&tree_view_excluded_directory); scrolled_window_excluded_directories.show_all(); } - // Set Excluded Items - { - if cfg!(target_family = "unix") { - entry_excluded_items.set_text("*/.git/*,*/node_modules/*,*/lost+found/*,*/Trash/*"); - } - if cfg!(target_family = "windows") { - entry_excluded_items.set_text("*/.git/*,*/node_modules/*,*/lost+found/*,*:/windows/*"); - } - } } } diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index cd9fc0d..6be45b5 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -7,12 +7,14 @@ mod connect_compute_results; mod connect_notebook_tabs; mod connect_popovers; mod connect_progress_window; -mod connect_upper_notebook; +mod connect_selection_of_directories; +mod connect_settings; mod create_tree_view; mod double_click_opening; mod gui_data; mod help_functions; -mod startup_configuration; +mod initialize_gui; +mod saving_loading; use czkawka_core::*; @@ -26,9 +28,11 @@ use crate::connect_compute_results::*; use crate::connect_notebook_tabs::*; use crate::connect_popovers::*; use crate::connect_progress_window::*; -use crate::connect_upper_notebook::*; +use crate::connect_selection_of_directories::*; +use crate::connect_settings::*; use crate::gui_data::*; -use crate::startup_configuration::*; +use crate::initialize_gui::*; +use crate::saving_loading::{load_configuration, reset_configuration, save_configuration}; use gtk::prelude::*; use std::{env, process}; @@ -66,7 +70,10 @@ fn main() { let (futures_sender_temporary, futures_receiver_temporary): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); let (futures_sender_zeroed, futures_receiver_zeroed): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); - startup_configuration(&gui_data); + initialize_gui(&gui_data); + reset_configuration(&gui_data, false); // Fallback for invalid loading setting project + load_configuration(&gui_data, false); + connect_button_delete(&gui_data); connect_button_save(&gui_data); connect_button_search( @@ -84,7 +91,7 @@ fn main() { connect_button_select(&gui_data); connect_button_stop(&gui_data); connect_notebook_tabs(&gui_data); - connect_upper_notebook(&gui_data); + connect_selection_of_directories(&gui_data); connect_popovers(&gui_data); connect_compute_results(&gui_data, glib_stop_receiver); connect_progress_window( @@ -98,12 +105,17 @@ fn main() { futures_receiver_temporary, futures_receiver_zeroed, ); + connect_settings(&gui_data); // Quit the program when X in main window was clicked - gui_data.window_main.connect_delete_event(|_, _| { - gtk::main_quit(); - Inhibit(false) - }); + { + let window_main = gui_data.window_main.clone(); + window_main.connect_delete_event(move |_, _| { + save_configuration(&gui_data, false); // Save configuration at exit + gtk::main_quit(); + Inhibit(false) + }); + } // We start the gtk main loop. gtk::main(); diff --git a/czkawka_gui/src/saving_loading.rs b/czkawka_gui/src/saving_loading.rs new file mode 100644 index 0000000..7e4171c --- /dev/null +++ b/czkawka_gui/src/saving_loading.rs @@ -0,0 +1,354 @@ +use crate::gui_data::*; +use crate::help_functions::{get_list_store, ColumnsDirectory}; +use directories_next::ProjectDirs; +use gtk::prelude::*; +use gtk::{EntryExt, GtkListStoreExt, ToggleButtonExt}; +use std::fs::File; +use std::io::Write; +use std::path::Path; +use std::{env, fs}; + +const SAVE_FILE_NAME: &str = "czkawka_gui_config.txt"; + +pub fn save_configuration(gui_data: &GuiData, manual_execution: bool) { + let check_button_settings_save_at_exit = gui_data.check_button_settings_save_at_exit.clone(); + let text_view_errors = gui_data.text_view_errors.clone(); + + if !manual_execution && !check_button_settings_save_at_exit.get_active() { + // When check button is deselected, not save configuration at exit + return; + } + if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") { + // Lin: /home/alice/.config/barapp + // Win: C:\Users\Alice\AppData\Roaming\Foo Corp\Bar App\config + // Mac: /Users/Alice/Library/Application Support/com.Foo-Corp.Bar-App + + let config_dir = proj_dirs.config_dir(); + if config_dir.exists() { + if !config_dir.is_dir() { + text_view_errors + .get_buffer() + .unwrap() + .set_text(format!("Cannot create save file inside {} because this isn't a folder.", config_dir.display()).as_str()); + return; + } + } else if fs::create_dir(config_dir).is_err() { + text_view_errors.get_buffer().unwrap().set_text(format!("Failed configuration to create configuration folder {}", config_dir.display()).as_str()); + return; + } + + let mut data_to_save: Vec = Vec::new(); + + //// Included Directories + data_to_save.push("--included_directories:".to_string()); + let scrolled_window_included_directories = gui_data.scrolled_window_included_directories.clone(); + let list_store = get_list_store(&scrolled_window_included_directories); + if let Some(iter) = list_store.get_iter_first() { + loop { + data_to_save.push(list_store.get_value(&iter, ColumnsDirectory::Path as i32).get::().unwrap().unwrap()); + if !list_store.iter_next(&iter) { + break; + } + } + } + + //// Excluded Directories + data_to_save.push("--excluded_directories:".to_string()); + let scrolled_window_excluded_directories = gui_data.scrolled_window_excluded_directories.clone(); + let list_store = get_list_store(&scrolled_window_excluded_directories); + if let Some(iter) = list_store.get_iter_first() { + loop { + data_to_save.push(list_store.get_value(&iter, ColumnsDirectory::Path as i32).get::().unwrap().unwrap()); + if !list_store.iter_next(&iter) { + break; + } + } + } + + //// Excluded Items + data_to_save.push("--excluded_items:".to_string()); + let entry_excluded_items = gui_data.entry_excluded_items.clone(); + for item in entry_excluded_items.get_text().split(',') { + if item.trim().is_empty() { + continue; + } + data_to_save.push(item.to_string()); + } + + //// Allowed extensions + data_to_save.push("--allowed_extensions:".to_string()); + let entry_allowed_extensions = gui_data.entry_allowed_extensions.clone(); + for extension in entry_allowed_extensions.get_text().split(',') { + if extension.trim().is_empty() { + continue; + } + data_to_save.push(extension.to_string()); + } + + //// Save at exit + data_to_save.push("--save_at_exit:".to_string()); + let check_button_settings_save_at_exit = gui_data.check_button_settings_save_at_exit.clone(); + data_to_save.push(check_button_settings_save_at_exit.get_active().to_string()); + + //// Load at start + data_to_save.push("--load_at_start:".to_string()); + let check_button_settings_load_at_start = gui_data.check_button_settings_load_at_start.clone(); + data_to_save.push(check_button_settings_load_at_start.get_active().to_string()); + + // Creating/Opening config file + + let config_file = config_dir.join(Path::new(SAVE_FILE_NAME)); + + let mut config_file_handler = match File::create(&config_file) { + Ok(t) => t, + Err(_) => { + text_view_errors.get_buffer().unwrap().set_text(format!("Failed to create config file {}", config_file.display()).as_str()); + return; + } + }; + + for data in data_to_save { + match writeln!(config_file_handler, "{}", data) { + Ok(_) => { + text_view_errors.get_buffer().unwrap().set_text(format!("Saved configuration to file {}", config_file.display()).as_str()); + } + Err(_) => { + text_view_errors.get_buffer().unwrap().set_text(format!("Failed to save configuration data to file {}", config_file.display()).as_str()); + return; + } + } + } + } else { + text_view_errors.get_buffer().unwrap().set_text("Failed to get home directory, so can't save file."); + } +} + +enum TypeOfLoadedData { + None, + IncludedDirectories, + ExcludedDirectories, + ExcludedItems, + AllowedExtensions, + LoadingAtStart, + SavingAtExit, +} + +pub fn load_configuration(gui_data: &GuiData, manual_execution: bool) { + let text_view_errors = gui_data.text_view_errors.clone(); + if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") { + // Lin: /home/alice/.config/barapp + // Win: C:\Users\Alice\AppData\Roaming\Foo Corp\Bar App\config + // Mac: /Users/Alice/Library/Application Support/com.Foo-Corp.Bar-App + + let config_dir = proj_dirs.config_dir(); + let config_file = config_dir.join(Path::new(SAVE_FILE_NAME)); + if !config_file.exists() || !config_file.is_file() { + if manual_execution { + // Don't show errors when there is no configuration file when starting app + text_view_errors.get_buffer().unwrap().set_text(format!("Cannot load configuration from file {:?}.", config_file.display()).as_str()); + } + return; + } + + // Loading Data + let loaded_data: String = match fs::read_to_string(&config_file) { + Ok(t) => t, + Err(_) => { + text_view_errors.get_buffer().unwrap().set_text(format!("Failed to read data from file {:?}.", config_file).as_str()); + return; + } + }; + + // Parsing Data + + let mut included_directories: Vec = Vec::new(); + let mut excluded_directories: Vec = Vec::new(); + let mut excluded_items: Vec = Vec::new(); + let mut allowed_extensions: Vec = Vec::new(); + let mut loading_at_start: bool = true; + let mut saving_at_exit: bool = true; + + let mut current_type = TypeOfLoadedData::None; + for (line_number, line) in loaded_data.replace("\r\n", "\n").split('\n').enumerate() { + let line: String = line.trim().to_string(); + if line.trim().is_empty() { + continue; // Empty line, so we just skip it + } + if line.starts_with("--included_directories") { + current_type = TypeOfLoadedData::IncludedDirectories; + } else if line.starts_with("--excluded_directories") { + current_type = TypeOfLoadedData::ExcludedDirectories; + } else if line.starts_with("--excluded_items") { + current_type = TypeOfLoadedData::ExcludedItems; + } else if line.starts_with("--allowed_extensions") { + current_type = TypeOfLoadedData::AllowedExtensions; + } else if line.starts_with("--load_at_start") { + current_type = TypeOfLoadedData::LoadingAtStart; + } else if line.starts_with("--save_at_exit") { + current_type = TypeOfLoadedData::SavingAtExit; + } else { + match current_type { + TypeOfLoadedData::None => { + text_view_errors + .get_buffer() + .unwrap() + .set_text(format!("Found orphan data in line {} \"\"\"{}\"\"\" when loading file {:?}", line_number, line, config_file).as_str()); + return; + } + TypeOfLoadedData::IncludedDirectories => { + included_directories.push(line); + } + TypeOfLoadedData::ExcludedDirectories => { + excluded_directories.push(line); + } + TypeOfLoadedData::ExcludedItems => { + excluded_items.push(line); + } + TypeOfLoadedData::AllowedExtensions => { + allowed_extensions.push(line); + } + TypeOfLoadedData::LoadingAtStart => { + let line = line.to_lowercase(); + if line == "1" || line == "true" { + loading_at_start = true; + } else if line == "0" || line == "false" { + loading_at_start = false; + } else { + text_view_errors + .get_buffer() + .unwrap() + .set_text(format!("Found invalid data in line {} \"\"\"{}\"\"\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str()); + } + } + TypeOfLoadedData::SavingAtExit => { + let line = line.to_lowercase(); + if line == "1" || line == "true" { + saving_at_exit = true; + } else if line == "0" || line == "false" { + saving_at_exit = false; + } else { + text_view_errors + .get_buffer() + .unwrap() + .set_text(format!("Found invalid data in line {} \"\"\"{}\"\"\" isn't proper value(0/1/true/false) when loading file {:?}", line_number, line, config_file).as_str()); + } + } + } + } + } + + // Setting data + if manual_execution || loading_at_start { + //// Included Directories + let scrolled_window_included_directories = gui_data.scrolled_window_included_directories.clone(); + let list_store = get_list_store(&scrolled_window_included_directories); + list_store.clear(); + + let col_indices = [0]; + + for directory in included_directories { + let values: [&dyn ToValue; 1] = [&directory]; + list_store.set(&list_store.append(), &col_indices, &values); + } + + //// Exclude Directories + let scrolled_window_excluded_directories = gui_data.scrolled_window_excluded_directories.clone(); + let list_store = get_list_store(&scrolled_window_excluded_directories); + list_store.clear(); + + let col_indices = [0]; + + for directory in excluded_directories { + let values: [&dyn ToValue; 1] = [&directory]; + list_store.set(&list_store.append(), &col_indices, &values); + } + + //// Excluded Items + let entry_excluded_items = gui_data.entry_excluded_items.clone(); + entry_excluded_items.set_text(excluded_items.iter().map(|e| e.to_string() + ",").collect::().as_str()); + + //// Allowed extensions + let entry_allowed_extensions = gui_data.entry_allowed_extensions.clone(); + entry_allowed_extensions.set_text(allowed_extensions.iter().map(|e| e.to_string() + ",").collect::().as_str()); + + //// Buttons + gui_data.check_button_settings_load_at_start.set_active(loading_at_start); + gui_data.check_button_settings_save_at_exit.set_active(saving_at_exit); + } else { + gui_data.check_button_settings_load_at_start.set_active(false); + } + + if manual_execution { + text_view_errors.get_buffer().unwrap().set_text(format!("Properly loaded configuration from file {:?}", config_file).as_str()); + } + } else { + text_view_errors.get_buffer().unwrap().set_text("Failed to get home directory, so can't load file."); + } +} + +pub fn reset_configuration(gui_data: &GuiData, manual_clearing: bool) { + // TODO Maybe add popup dialog to confirm resetting + let text_view_errors = gui_data.text_view_errors.clone(); + // Resetting included directories + { + let col_indices = [0]; + let scrolled_window_included_directories = gui_data.scrolled_window_included_directories.clone(); + let list_store = get_list_store(&scrolled_window_included_directories); + list_store.clear(); + + let current_dir: String = match env::current_dir() { + Ok(t) => t.to_str().unwrap().to_string(), + Err(_) => { + if cfg!(target_family = "unix") { + println!("Failed to read current directory, setting /home instead"); + "/home".to_string() + } else if cfg!(target_family = "windows") { + println!("Failed to read current directory, setting C:\\ instead"); + "C:\\".to_string() + } else { + "".to_string() + } + } + }; + + let values: [&dyn ToValue; 1] = [¤t_dir]; + list_store.set(&list_store.append(), &col_indices, &values); + } + // Resetting excluded directories + { + let col_indices = [0]; + let scrolled_window_excluded_directories = gui_data.scrolled_window_excluded_directories.clone(); + let list_store = get_list_store(&scrolled_window_excluded_directories); + list_store.clear(); + if cfg!(target_family = "unix") { + for i in ["/proc", "/dev", "/sys", "/run", "/snap"].iter() { + let values: [&dyn ToValue; 1] = [&i]; + list_store.set(&list_store.append(), &col_indices, &values); + } + } + } + // Resetting excluded items + { + let entry_excluded_items = gui_data.entry_excluded_items.clone(); + if cfg!(target_family = "unix") { + entry_excluded_items.set_text("*/.git/*,*/node_modules/*,*/lost+found/*,*/Trash/*"); + } + if cfg!(target_family = "windows") { + entry_excluded_items.set_text("*/.git/*,*/node_modules/*,*/lost+found/*,*:/windows/*"); + } + } + // Resetting allowed extensions + { + let entry_allowed_extensions = gui_data.entry_allowed_extensions.clone(); + entry_allowed_extensions.set_text(""); + } + + // Set settings + { + gui_data.check_button_settings_save_at_exit.set_active(true); + gui_data.check_button_settings_load_at_start.set_active(true); + } + if manual_clearing { + text_view_errors.get_buffer().unwrap().set_text("Current configuration was cleared."); + } +}