Repository: qarmin/czkawka (language: rust)

Issue:
gui: specify target path by parameter instead of loading last used path?
I just stumbled upon your project by reading the comments of https://aur.archlinux.org/packages/pygtk which is a dependency of https://aur.archlinux.org/packages/fslint

Your project looks very nice and I love the additional features compared to fslint (and speed improvements are also very nice!)

But one essential feature I miss (or didn't find yet?) in czkawka is that the target path is loaded from previous run instead of using the working directory or beeing able to specify the target path by a parameter.


Discussion / hints:
Ah, okey so I found that if I set `--load_at_start` `~.config/czkawka/czkawka_gui_config.txt` to false it will use the working directory as included directory. but it will restore the default list of excluded directories and items, and those prevent me from using it on external disks which are mounted to `/run/media/user/...` and `/run` is one of the directories excluded by default..
okey so to workaround that problem I've removed those default values that take effect when not loading the config, see also https://github.com/qarmin/czkawka/pull/433 

Fix patch:
diff --git a/czkawka_gui/src/connect_things/connect_settings.rs b/czkawka_gui/src/connect_things/connect_settings.rs
index c28228b7a..430b81b57 100644
--- a/czkawka_gui/src/connect_things/connect_settings.rs
+++ b/czkawka_gui/src/connect_things/connect_settings.rs
@@ -53,7 +53,7 @@ pub fn connect_settings(gui_data: &GuiData) {
         let button_settings_load_configuration = gui_data.settings.button_settings_load_configuration.clone();
         let scrolled_window_errors = gui_data.scrolled_window_errors.clone();
         button_settings_load_configuration.connect_clicked(move |_| {
-            load_configuration(true, &upper_notebook, &main_notebook, &settings, &text_view_errors, &scrolled_window_errors);
+            load_configuration(true, &upper_notebook, &main_notebook, &settings, &text_view_errors, &scrolled_window_errors, Vec::new());
         });
     }
     // Connect reset configuration button
diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs
index 1c0ca349c..afbe48ac4 100644
--- a/czkawka_gui/src/main.rs
+++ b/czkawka_gui/src/main.rs
@@ -5,7 +5,11 @@
 #![allow(clippy::type_complexity)]
 #![allow(clippy::needless_late_init)]
 
+use gtk::gio::ApplicationFlags;
 use gtk::prelude::*;
+use gtk::Application;
+use std::env;
+use std::ffi::OsString;
 
 use czkawka_core::*;
 
@@ -55,125 +59,134 @@ mod taskbar_progress_win;
 mod tests;
 
 fn main() {
-    let application = gtk::Application::builder().build();
-    application.connect_activate(move |application| {
-        let mut gui_data: GuiData = GuiData::new_with_application(application);
+    let application = gtk::Application::new(None, ApplicationFlags::HANDLES_OPEN | ApplicationFlags::HANDLES_COMMAND_LINE);
+    application.connect_command_line(move |app, cmdline| {
+        build_ui(app, cmdline.arguments());
+        0
+    });
+    application.run_with_args(&env::args().collect::<Vec<_>>());
+}
+fn build_ui(application: &Application, arguments: Vec<OsString>) {
+    let mut gui_data: GuiData = GuiData::new_with_application(application);
 
-        // Used for getting data from thread
-        let (glib_stop_sender, glib_stop_receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
+    // Used for getting data from thread
+    let (glib_stop_sender, glib_stop_receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
 
-        // Futures progress report
-        let (futures_sender_duplicate_files, futures_receiver_duplicate_files): (
-            futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_empty_files, futures_receiver_empty_files): (
-            futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_empty_folder, futures_receiver_empty_folder): (
-            futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_big_file, futures_receiver_big_file): (
-            futures::channel::mpsc::UnboundedSender<big_file::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<big_file::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_same_music, futures_receiver_same_music): (
-            futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_similar_images, futures_receiver_similar_images): (
-            futures::channel::mpsc::UnboundedSender<similar_images::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<similar_images::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_similar_videos, futures_receiver_similar_videos): (
-            futures::channel::mpsc::UnboundedSender<similar_videos::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<similar_videos::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_temporary, futures_receiver_temporary): (
-            futures::channel::mpsc::UnboundedSender<temporary::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<temporary::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_invalid_symlinks, futures_receiver_invalid_symlinks): (
-            futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
-        let (futures_sender_broken_files, futures_receiver_broken_files): (
-            futures::channel::mpsc::UnboundedSender<broken_files::ProgressData>,
-            futures::channel::mpsc::UnboundedReceiver<broken_files::ProgressData>,
-        ) = futures::channel::mpsc::unbounded();
+    // Futures progress report
+    let (futures_sender_duplicate_files, futures_receiver_duplicate_files): (
+        futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_empty_files, futures_receiver_empty_files): (
+        futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_empty_folder, futures_receiver_empty_folder): (
+        futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_big_file, futures_receiver_big_file): (
+        futures::channel::mpsc::UnboundedSender<big_file::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<big_file::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_same_music, futures_receiver_same_music): (
+        futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_similar_images, futures_receiver_similar_images): (
+        futures::channel::mpsc::UnboundedSender<similar_images::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<similar_images::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_similar_videos, futures_receiver_similar_videos): (
+        futures::channel::mpsc::UnboundedSender<similar_videos::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<similar_videos::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_temporary, futures_receiver_temporary): (
+        futures::channel::mpsc::UnboundedSender<temporary::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<temporary::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_invalid_symlinks, futures_receiver_invalid_symlinks): (
+        futures::channel::mpsc::UnboundedSender<common_dir_traversal::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<common_dir_traversal::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
+    let (futures_sender_broken_files, futures_receiver_broken_files): (
+        futures::channel::mpsc::UnboundedSender<broken_files::ProgressData>,
+        futures::channel::mpsc::UnboundedReceiver<broken_files::ProgressData>,
+    ) = futures::channel::mpsc::unbounded();
 
-        initialize_gui(&mut gui_data);
-        validate_notebook_data(&gui_data); // Must be run after initialization of gui, to check if everything was properly setup
-        reset_configuration(false, &gui_data.upper_notebook, &gui_data.main_notebook, &gui_data.settings, &gui_data.text_view_errors); // Fallback for invalid loading setting project
-        load_system_language(&gui_data); // Check for default system language, must be loaded after initializing GUI and before loading settings from file
-        load_configuration(
-            false,
-            &gui_data.upper_notebook,
-            &gui_data.main_notebook,
-            &gui_data.settings,
-            &gui_data.text_view_errors,
-            &gui_data.scrolled_window_errors,
-        );
+    initialize_gui(&mut gui_data);
+    validate_notebook_data(&gui_data); // Must be run after initialization of gui, to check if everything was properly setup
+    reset_configuration(false, &gui_data.upper_notebook, &gui_data.main_notebook, &gui_data.settings, &gui_data.text_view_errors); // Fallback for invalid loading setting project
+    load_system_language(&gui_data); // Check for default system language, must be loaded after initializing GUI and before loading settings from file
+    load_configuration(
+        false,
+        &gui_data.upper_notebook,
+        &gui_data.main_notebook,
+        &gui_data.settings,
+        &gui_data.text_view_errors,
+        &gui_data.scrolled_window_errors,
+        arguments.clone(),
+    );
 
-        // Needs to run when entire GUI is initialized
-        connect_change_language(&gui_data);
+    // Needs to run when entire GUI is initialized
+    connect_change_language(&gui_data);
 
-        connect_button_delete(&gui_data);
-        connect_button_save(&gui_data);
-        connect_button_search(
-            &gui_data,
-            glib_stop_sender,
-            futures_sender_duplicate_files,
-            futures_sender_empty_files,
-            futures_sender_empty_folder,
-            futures_sender_big_file,
-            futures_sender_same_music,
-            futures_sender_similar_images,
-            futures_sender_similar_videos,
-            futures_sender_temporary,
-            futures_sender_invalid_symlinks,
-            futures_sender_broken_files,
-        );
-        connect_button_select(&gui_data);
-        connect_button_stop(&gui_data);
-        connect_button_hardlink_symlink(&gui_data);
-        connect_button_move(&gui_data);
-        connect_button_compare(&gui_data);
+    connect_button_delete(&gui_data);
+    connect_button_save(&gui_data);
+    connect_button_search(
+        &gui_data,
+        glib_stop_sender,
+        futures_sender_duplicate_files,
+        futures_sender_empty_files,
+        futures_sender_empty_folder,
+        futures_sender_big_file,
+        futures_sender_same_music,
+        futures_sender_similar_images,
+        futures_sender_similar_videos,
+        futures_sender_temporary,
+        futures_sender_invalid_symlinks,
+        futures_sender_broken_files,
+    );
+    connect_button_select(&gui_data);
+    connect_button_stop(&gui_data);
+    connect_button_hardlink_symlink(&gui_data);
+    connect_button_move(&gui_data);
+    connect_button_compare(&gui_data);
 
-        connect_duplicate_combo_box(&gui_data);
-        connect_notebook_tabs(&gui_data);
-        connect_selection_of_directories(&gui_data);
-        connect_popovers(&gui_data);
-        connect_compute_results(&gui_data, glib_stop_receiver);
-        connect_progress_window(
-            &gui_data,
-            futures_receiver_duplicate_files,
-            futures_receiver_empty_files,
-            futures_receiver_empty_folder,
-            futures_receiver_big_file,
-            futures_receiver_same_music,
-            futures_receiver_similar_images,
-            futures_receiver_similar_videos,
-            futures_receiver_temporary,
-            futures_receiver_invalid_symlinks,
-            futures_receiver_broken_files,
-        );
-        connect_show_hide_ui(&gui_data);
-        connect_settings(&gui_data);
-        connect_button_about(&gui_data);
-        connect_about_buttons(&gui_data);
-        connect_similar_image_size_change(&gui_data);
+    connect_duplicate_combo_box(&gui_data);
+    connect_notebook_tabs(&gui_data);
+    connect_selection_of_directories(&gui_data);
+    connect_popovers(&gui_data);
+    connect_compute_results(&gui_data, glib_stop_receiver);
+    connect_progress_window(
+        &gui_data,
+        futures_receiver_duplicate_files,
+        futures_receiver_empty_files,
+        futures_receiver_empty_folder,
+        futures_receiver_big_file,
+        futures_receiver_same_music,
+        futures_receiver_similar_images,
+        futures_receiver_similar_videos,
+        futures_receiver_temporary,
+        futures_receiver_invalid_symlinks,
+        futures_receiver_broken_files,
+    );
+    connect_show_hide_ui(&gui_data);
+    connect_settings(&gui_data);
+    connect_button_about(&gui_data);
+    connect_about_buttons(&gui_data);
+    connect_similar_image_size_change(&gui_data);
 
-        let window_main = gui_data.window_main.clone();
-        let taskbar_state = gui_data.taskbar_state.clone();
-        window_main.connect_delete_event(move |_, _| {
-            save_configuration(false, &gui_data.upper_notebook, &gui_data.main_notebook, &gui_data.settings, &gui_data.text_view_errors); // Save configuration at exit
-            taskbar_state.borrow_mut().release();
-            Inhibit(false)
-        });
+    let window_main = gui_data.window_main.clone();
+    let taskbar_state = gui_data.taskbar_state.clone();
+    let used_additional_arguments = arguments.len() > 1;
+    window_main.connect_delete_event(move |_, _| {
+        // Not save configuration when using non default arguments
+        if !used_additional_arguments {
+            save_configuration(false, &gui_data.upper_notebook, &gui_data.main_notebook, &gui_data.settings, &gui_data.text_view_errors);
+            // Save configuration at exit
+        }
+        taskbar_state.borrow_mut().release();
+        Inhibit(false)
     });
-
-    application.run();
 }
diff --git a/czkawka_gui/src/saving_loading.rs b/czkawka_gui/src/saving_loading.rs
index 3144cf93f..79c9fa19e 100644
--- a/czkawka_gui/src/saving_loading.rs
+++ b/czkawka_gui/src/saving_loading.rs
@@ -1,4 +1,5 @@
 use std::collections::HashMap;
+use std::ffi::OsString;
 use std::fs::File;
 use std::io::{Read, Write};
 use std::path::{Path, PathBuf};
@@ -20,7 +21,6 @@ use crate::help_functions::*;
 use crate::language_functions::{get_language_from_combo_box_text, LANGUAGES_ALL};
 use crate::localizer_core::generate_translation_hashmap;
 
-// TODO organize this better, add specific functions that will allow to load from files specific strings
 const SAVE_FILE_NAME: &str = "czkawka_gui_config_4.txt";
 
 const DEFAULT_SAVE_ON_EXIT: bool = true;
@@ -662,6 +662,7 @@ pub fn load_configuration(
     settings: &GuiSettings,
     text_view_errors: &TextView,
     scrolled_window_errors: &ScrolledWindow,
+    arguments: Vec<OsString>,
 ) {
     let text_view_errors = text_view_errors.clone();
 
@@ -681,8 +682,8 @@ pub fn load_configuration(
     // Loading data from hashmaps
     let (hashmap_ls, _hashmap_sl) = create_hash_map();
 
-    let included_directories: Vec<String> = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::IncludedDirectories).unwrap().clone(), included_directories);
-    let excluded_directories: Vec<String> = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::ExcludedDirectories).unwrap().clone(), excluded_directories);
+    let mut included_directories: Vec<String> = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::IncludedDirectories).unwrap().clone(), included_directories);
+    let mut excluded_directories: Vec<String> = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::ExcludedDirectories).unwrap().clone(), excluded_directories);
     let excluded_items: String = loaded_entries.get_string(
         hashmap_ls.get(&LoadText::ExcludedItems).unwrap().clone(),
         upper_notebook.entry_excluded_items.text().to_string(),
@@ -752,6 +753,46 @@ pub fn load_configuration(
     // Setting data
     if manual_execution || loading_at_start {
         {
+            // Handle here arguments that were added to app e.g. czkawka_gui /home --/home/roman
+            if loading_at_start && arguments.len() > 1 {
+                let iter_i = arguments.iter().skip(1);
+                let iter_e = iter_i.clone();
+                included_directories = iter_i
+                    .filter_map(|e| {
+                        let r = e.to_string_lossy().to_string();
+                        if !r.starts_with("--") {
+                            let path = Path::new(&r);
+                            if !path.exists() {
+                                return None;
+                            }
+                            match path.canonicalize() {
+                                Ok(r) => Some(r.to_string_lossy().to_string()),
+                                Err(_) => None,
+                            }
+                        } else {
+                            None
+                        }
+                    })
+                    .collect::<Vec<_>>();
+                excluded_directories = iter_e
+                    .filter_map(|e| {
+                        let r = e.to_string_lossy().to_string();
+                        if let Some(r) = r.strip_prefix("--") {
+                            let path = Path::new(&r);
+                            if !path.exists() {
+                                return None;
+                            }
+                            match path.canonicalize() {
+                                Ok(r) => Some(r.to_string_lossy().to_string()),
+                                Err(_) => None,
+                            }
+                        } else {
+                            None
+                        }
+                    })
+                    .collect::<Vec<_>>();
+            }
+
             // Include Directories
             let tree_view_included_directories = upper_notebook.tree_view_included_directories.clone();
             let list_store = get_list_store(&tree_view_included_directories);
diff --git a/instructions/Instruction.md b/instructions/Instruction.md
index 8ff66838b..bff254c8b 100644
--- a/instructions/Instruction.md
+++ b/instructions/Instruction.md
@@ -36,6 +36,16 @@ To open folder containing selected file, just click twice on it with right mouse
 
 To invert a selection of files, click on a file with the middle mouse button and it will invert the selection of the other files in the same group.
 
+### Adding directories 
+
+By default current path is loaded to included directory and excluded directories are filled with default paths.
+
+It is possible to override this, by adding arguments when opening app e.g. `czkawka_gui /home /usr --/home/rafal --/home/zaba` which means that `/home` and `/usr` directories will be checked and `/home/rafal` and `/home/zaba` will be excluded.
+
+When using additional command line arguments, saving at exit option become disabled, so this current info about directories will not be saved until user save it manually.
+
+Both relative and absolute path are supported, so used can use both `../home` and `/home`.
+
 ## CLI
 Czkawka CLI frontend is great to automate some tasks like removing empty directories.
 


Test patch: