From 6fab1a9368da76e87b6415d8128f2e97f81d4764 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Mikrut?=
<41945903+qarmin@users.noreply.github.com>
Date: Mon, 7 Dec 2020 15:25:44 +0100
Subject: [PATCH] Add option to save file, store settings and load them (#108)
---
Cargo.lock | 58 ++-
README.md | 1 +
czkawka_gui/Cargo.toml | 5 +
czkawka_gui/czkawka.glade | 98 +++++
czkawka_gui/src/connect_notebook_tabs.rs | 3 +-
...rs => connect_selection_of_directories.rs} | 2 +-
czkawka_gui/src/connect_settings.rs | 31 ++
czkawka_gui/src/gui_data.rs | 26 +-
...tup_configuration.rs => initialize_gui.rs} | 46 +--
czkawka_gui/src/main.rs | 32 +-
czkawka_gui/src/saving_loading.rs | 354 ++++++++++++++++++
11 files changed, 580 insertions(+), 76 deletions(-)
rename czkawka_gui/src/{connect_upper_notebook.rs => connect_selection_of_directories.rs} (98%)
create mode 100644 czkawka_gui/src/connect_settings.rs
rename czkawka_gui/src/{startup_configuration.rs => initialize_gui.rs} (84%)
create mode 100644 czkawka_gui/src/saving_loading.rs
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
+
+
+
+ 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.");
+ }
+}