From c88d347e0093ba0af47b2fc8714e67cced36cc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= <41945903+qarmin@users.noreply.github.com> Date: Sat, 16 Apr 2022 18:31:01 +0200 Subject: [PATCH] Allow to set included/excluded directories by command line arguments (#677) --- .../src/connect_things/connect_settings.rs | 2 +- czkawka_gui/src/main.rs | 253 +++++++++--------- czkawka_gui/src/saving_loading.rs | 47 +++- instructions/Instruction.md | 10 + 4 files changed, 188 insertions(+), 124 deletions(-) diff --git a/czkawka_gui/src/connect_things/connect_settings.rs b/czkawka_gui/src/connect_things/connect_settings.rs index c28228b..430b81b 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 1c0ca34..afbe48a 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); - - // 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, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_empty_files, futures_receiver_empty_files): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_empty_folder, futures_receiver_empty_folder): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_big_file, futures_receiver_big_file): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_same_music, futures_receiver_same_music): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_similar_images, futures_receiver_similar_images): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_similar_videos, futures_receiver_similar_videos): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_temporary, futures_receiver_temporary): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_invalid_symlinks, futures_receiver_invalid_symlinks): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); - let (futures_sender_broken_files, futures_receiver_broken_files): ( - futures::channel::mpsc::UnboundedSender, - futures::channel::mpsc::UnboundedReceiver, - ) = 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, - ); - - // 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_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 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::>()); +} +fn build_ui(application: &Application, arguments: Vec) { + 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); + + // Futures progress report + let (futures_sender_duplicate_files, futures_receiver_duplicate_files): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_empty_files, futures_receiver_empty_files): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_empty_folder, futures_receiver_empty_folder): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_big_file, futures_receiver_big_file): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_same_music, futures_receiver_same_music): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_similar_images, futures_receiver_similar_images): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_similar_videos, futures_receiver_similar_videos): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_temporary, futures_receiver_temporary): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_invalid_symlinks, futures_receiver_invalid_symlinks): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = futures::channel::mpsc::unbounded(); + let (futures_sender_broken_files, futures_receiver_broken_files): ( + futures::channel::mpsc::UnboundedSender, + futures::channel::mpsc::UnboundedReceiver, + ) = 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, + arguments.clone(), + ); + + // 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_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(); + 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 3144cf9..79c9fa1 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, ) { 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 = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::IncludedDirectories).unwrap().clone(), included_directories); - let excluded_directories: Vec = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::ExcludedDirectories).unwrap().clone(), excluded_directories); + let mut included_directories: Vec = loaded_entries.get_vector_string(hashmap_ls.get(&LoadText::IncludedDirectories).unwrap().clone(), included_directories); + let mut excluded_directories: Vec = 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::>(); + 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::>(); + } + // 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 8ff6683..bff254c 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.