diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 64fce7c..0c7e765 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,8 +8,16 @@ assignees: '' --- **Desktop (please complete the following information):** - - Czkawka version [e.g. 6.0.0 cli/gui]: - - OS version [e.g Ubuntu 22.04, Windows 11]: + +- Czkawka version: +- OS version: +- Terminal output[optional]: + + **Bug Description** ... diff --git a/Cargo.lock b/Cargo.lock index baf6cf8..cf50471 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,6 +525,7 @@ version = "6.0.0" dependencies = [ "clap", "czkawka_core", + "fun_time", "handsome_logger", "image_hasher", "log", @@ -544,6 +545,7 @@ dependencies = [ "crossbeam-channel", "directories-next", "ffmpeg_cmdline_utils", + "fun_time", "futures", "hamming", "handsome_logger", @@ -585,6 +587,7 @@ dependencies = [ "czkawka_core", "directories-next", "fs_extra", + "fun_time", "futures", "gdk4", "glib", @@ -604,6 +607,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -735,25 +773,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "exr" version = "1.71.0" @@ -934,6 +961,28 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "fun_time" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af29f347b6ae4821c45565e1238231caf5b57a951bd011222752ba0f5a47eae" +dependencies = [ + "fun_time_derive", + "log", +] + +[[package]] +name = "fun_time_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e992455767376a16164ee4cc69bd799530c1c41c701bdd8c8a541ec6095c078e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "futures" version = "0.3.28" @@ -1500,6 +1549,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -1704,9 +1759,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libheif-rs" @@ -1742,9 +1797,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "locale_config" @@ -1942,9 +1997,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -2238,9 +2293,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -3228,9 +3283,9 @@ dependencies = [ [[package]] name = "trash" -version = "3.0.6" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3663fb8f476d674b9c61d1d2796acec725bef6bec4b41402a904252a25971e" +checksum = "f7b23f2b0cf93f537bbe90cbb59ea9176cc8ce9b010a36dcd5b726facd82825e" dependencies = [ "chrono", "libc", diff --git a/czkawka_cli/Cargo.toml b/czkawka_cli/Cargo.toml index ca7f0bc..741927a 100644 --- a/czkawka_cli/Cargo.toml +++ b/czkawka_cli/Cargo.toml @@ -17,6 +17,7 @@ image_hasher = "1.2" log = "0.4.20" handsome_logger = "0.8" +fun_time = { version = "0.3.1", features = ["log"] } czkawka_core = { path = "../czkawka_core", version = "6.0.0", features = [] } [features] diff --git a/czkawka_cli/src/commands.rs b/czkawka_cli/src/commands.rs index 946b0a7..2f042e6 100644 --- a/czkawka_cli/src/commands.rs +++ b/czkawka_cli/src/commands.rs @@ -3,7 +3,8 @@ use std::path::PathBuf; use image_hasher::{FilterType, HashAlg}; use czkawka_core::common_dir_traversal::CheckingMethod; -use czkawka_core::duplicate::{DeleteMethod, HashType}; +use czkawka_core::common_tool::DeleteMethod; +use czkawka_core::duplicate::HashType; use czkawka_core::same_music::MusicSimilarity; use czkawka_core::similar_images::SimilarityPreset; use czkawka_core::CZKAWKA_VERSION; diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index 044390a..d31cb68 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -1,25 +1,24 @@ #![allow(clippy::needless_late_init)] -use std::process; - use clap::Parser; +use log::error; use commands::Commands; use czkawka_core::bad_extensions::BadExtensions; -use czkawka_core::big_file::{self, BigFile, SearchMode}; -use czkawka_core::broken_files::{self, BrokenFiles}; -use czkawka_core::common::{set_number_of_threads, setup_logger}; -use czkawka_core::common_tool::CommonData; -#[allow(unused_imports)] // It is used in release for print_results(). +use czkawka_core::big_file::{BigFile, SearchMode}; +use czkawka_core::broken_files::BrokenFiles; +use czkawka_core::common::{print_version_mode, set_number_of_threads, setup_logger}; +use czkawka_core::common_tool::{CommonData, DeleteMethod}; +#[allow(unused_imports)] // It is used in release for print_results_to_output(). use czkawka_core::common_traits::*; use czkawka_core::duplicate::DuplicateFinder; -use czkawka_core::empty_files::{self, EmptyFiles}; +use czkawka_core::empty_files::EmptyFiles; use czkawka_core::empty_folder::EmptyFolder; -use czkawka_core::invalid_symlinks::{self, InvalidSymlinks}; +use czkawka_core::invalid_symlinks::InvalidSymlinks; use czkawka_core::same_music::SameMusic; use czkawka_core::similar_images::{return_similarity_from_similarity_preset, test_image_conversion_speed, SimilarImages}; use czkawka_core::similar_videos::SimilarVideos; -use czkawka_core::temporary::{self, Temporary}; +use czkawka_core::temporary::Temporary; use crate::commands::{ Args, BadExtensionsArgs, BiggestFilesArgs, BrokenFilesArgs, DuplicatesArgs, EmptyFilesArgs, EmptyFoldersArgs, InvalidSymlinksArgs, SameMusicArgs, SimilarImagesArgs, @@ -32,9 +31,11 @@ fn main() { let command = Args::parse().command; setup_logger(true); + print_version_mode(); - #[cfg(debug_assertions)] - println!("{command:?}"); + if cfg!(debug_assertions) { + println!("{command:?}"); + } match command { Commands::Duplicates(duplicates_args) => duplicates(duplicates_args), @@ -100,14 +101,14 @@ fn duplicates(duplicates: DuplicatesArgs) { df.find_duplicates(None, None); if let Some(file_name) = file_to_save.file_name() { - if !df.save_results_to_file(file_name) { - df.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = df.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - df.print_results(); + if !cfg!(debug_assertions) { + df.print_results_to_output(); + } df.get_text_messages().print_messages(); } @@ -137,14 +138,14 @@ fn empty_folders(empty_folders: EmptyFoldersArgs) { ef.find_empty_folders(None, None); if let Some(file_name) = file_to_save.file_name() { - if !ef.save_results_to_file(file_name) { - ef.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = ef.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - ef.print_results(); + if !cfg!(debug_assertions) { + ef.print_results_to_output(); + } ef.get_text_messages().print_messages(); } @@ -177,7 +178,7 @@ fn biggest_files(biggest_files: BiggestFilesArgs) { #[cfg(target_family = "unix")] bf.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - bf.set_delete_method(big_file::DeleteMethod::Delete); + bf.set_delete_method(DeleteMethod::Delete); } if smallest_mode { bf.set_search_mode(SearchMode::SmallestFiles); @@ -186,14 +187,14 @@ fn biggest_files(biggest_files: BiggestFilesArgs) { bf.find_big_files(None, None); if let Some(file_name) = file_to_save.file_name() { - if !bf.save_results_to_file(file_name) { - bf.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = bf.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - bf.print_results(); + if !cfg!(debug_assertions) { + bf.print_results_to_output(); + } bf.get_text_messages().print_messages(); } @@ -224,20 +225,20 @@ fn empty_files(empty_files: EmptyFilesArgs) { ef.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - ef.set_delete_method(empty_files::DeleteMethod::Delete); + ef.set_delete_method(DeleteMethod::Delete); } ef.find_empty_files(None, None); if let Some(file_name) = file_to_save.file_name() { - if !ef.save_results_to_file(file_name) { - ef.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = ef.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - ef.print_results(); + if !cfg!(debug_assertions) { + ef.print_results_to_output(); + } ef.get_text_messages().print_messages(); } @@ -266,20 +267,20 @@ fn temporary(temporary: TemporaryArgs) { tf.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - tf.set_delete_method(temporary::DeleteMethod::Delete); + tf.set_delete_method(DeleteMethod::Delete); } tf.find_temporary_files(None, None); if let Some(file_name) = file_to_save.file_name() { - if !tf.save_results_to_file(file_name) { - tf.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = tf.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - tf.print_results(); + if !cfg!(debug_assertions) { + tf.print_results_to_output(); + } tf.get_text_messages().print_messages(); } @@ -322,14 +323,14 @@ fn similar_images(similar_images: SimilarImagesArgs) { sf.find_similar_images(None, None); if let Some(file_name) = file_to_save.file_name() { - if !sf.save_results_to_file(file_name) { - sf.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = sf.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - sf.print_results(); + if !cfg!(debug_assertions) { + sf.print_results_to_output(); + } sf.get_text_messages().print_messages(); } @@ -370,14 +371,14 @@ fn same_music(same_music: SameMusicArgs) { mf.find_same_music(None, None); if let Some(file_name) = file_to_save.file_name() { - if !mf.save_results_to_file(file_name) { - mf.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = mf.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - mf.print_results(); + if !cfg!(debug_assertions) { + mf.print_results_to_output(); + } mf.get_text_messages().print_messages(); } @@ -407,20 +408,20 @@ fn invalid_symlinks(invalid_symlinks: InvalidSymlinksArgs) { #[cfg(target_family = "unix")] ifs.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - ifs.set_delete_method(invalid_symlinks::DeleteMethod::Delete); + ifs.set_delete_method(DeleteMethod::Delete); } ifs.find_invalid_links(None, None); if let Some(file_name) = file_to_save.file_name() { - if !ifs.save_results_to_file(file_name) { - ifs.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = ifs.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - ifs.print_results(); + if !cfg!(debug_assertions) { + ifs.print_results_to_output(); + } ifs.get_text_messages().print_messages(); } @@ -451,20 +452,20 @@ fn broken_files(broken_files: BrokenFilesArgs) { br.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); if delete_files { - br.set_delete_method(broken_files::DeleteMethod::Delete); + br.set_delete_method(DeleteMethod::Delete); } br.find_broken_files(None, None); if let Some(file_name) = file_to_save.file_name() { - if !br.save_results_to_file(file_name) { - br.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = br.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - br.print_results(); + if !cfg!(debug_assertions) { + br.print_results_to_output(); + } br.get_text_messages().print_messages(); } @@ -502,14 +503,14 @@ fn similar_videos(similar_videos: SimilarVideosArgs) { vr.find_similar_videos(None, None); if let Some(file_name) = file_to_save.file_name() { - if !vr.save_results_to_file(file_name) { - vr.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = vr.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - vr.print_results(); + if !cfg!(debug_assertions) { + vr.print_results_to_output(); + } vr.get_text_messages().print_messages(); } @@ -538,16 +539,16 @@ fn bad_extensions(bad_extensions: BadExtensionsArgs) { #[cfg(target_family = "unix")] be.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems); + be.find_bad_extensions_files(None, None); + if let Some(file_name) = file_to_save.file_name() { - if !be.save_results_to_file(file_name) { - be.get_text_messages().print_messages(); - process::exit(1); + if let Err(e) = be.print_results_to_file(file_name) { + error!("Failed to save results to file {e}"); } } - be.find_bad_extensions_files(None, None); - - #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed - be.print_results(); + if !cfg!(debug_assertions) { + be.print_results_to_output(); + } be.get_text_messages().print_messages(); } diff --git a/czkawka_core/Cargo.toml b/czkawka_core/Cargo.toml index 2f05a15..8e68231 100644 --- a/czkawka_core/Cargo.toml +++ b/czkawka_core/Cargo.toml @@ -81,6 +81,7 @@ state = "0.6" log = "0.4.20" handsome_logger = "0.8" +fun_time = { version = "0.3.1", features = ["log"] } [features] default = [] diff --git a/czkawka_core/src/bad_extensions.rs b/czkawka_core/src/bad_extensions.rs index 5ca437f..d018847 100644 --- a/czkawka_core/src/bad_extensions.rs +++ b/czkawka_core/src/bad_extensions.rs @@ -1,15 +1,14 @@ use std::collections::{BTreeSet, HashMap}; -use std::fs::File; use std::io::prelude::*; -use std::io::BufWriter; use std::mem; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Arc; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; -use log::{debug, info}; +use log::debug; use mime_guess::get_mime_extensions; use rayon::prelude::*; @@ -168,7 +167,6 @@ pub struct BadFileEntry { pub proper_extensions: String, } -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_files_with_bad_extension: usize, @@ -193,14 +191,8 @@ impl BadExtensions { } } + #[fun_time(message = "find_bad_extensions_files")] pub fn find_bad_extensions_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding files with bad extensions"); - let start_time = std::time::Instant::now(); - self.find_bad_extensions_files_internal(stop_receiver, progress_sender); - info!("Ended finding files with bad extensions which took {:?}", start_time.elapsed()); - } - - fn find_bad_extensions_files_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); if !self.check_files(stop_receiver, progress_sender) { self.common_data.stopped_search = true; @@ -213,8 +205,8 @@ impl BadExtensions { self.debug_print(); } + #[fun_time(message = "check_files")] fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_files - start"); let result = DirTraversalBuilder::new() .root_dirs(self.common_data.directories.included_directories.clone()) .group_by(|_fe| ()) @@ -228,12 +220,10 @@ impl BadExtensions { .recursive_search(self.common_data.recursive_search) .build() .run(); - debug!("check_files - collected files"); - let res = match result { + + match result { DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { - if let Some(files_to_check) = grouped_file_entries.get(&()) { - self.files_to_check = files_to_check.clone(); - } + self.files_to_check = grouped_file_entries.into_values().flatten().collect(); self.common_data.text_messages.warnings.extend(warnings); true @@ -242,13 +232,11 @@ impl BadExtensions { unreachable!() } DirTraversalResult::Stopped => false, - }; - debug!("check_files - end"); - res + } } + #[fun_time(message = "look_for_bad_extensions_files")] fn look_for_bad_extensions_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("look_for_bad_extensions_files - start"); let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = prepare_thread_handler_common(progress_sender, 1, 1, self.files_to_check.len(), CheckingMethod::None, self.get_cd().tool_type); @@ -274,13 +262,12 @@ impl BadExtensions { self.information.number_of_files_with_bad_extension = self.bad_extensions_files.len(); - // Clean unused data - self.files_to_check = Default::default(); + debug!("Found {} files with invalid extension.", self.information.number_of_files_with_bad_extension); - debug!("look_for_bad_extensions_files - end"); true } + #[fun_time(message = "verify_extensions")] fn verify_extensions( &self, files_to_check: Vec, @@ -289,8 +276,7 @@ impl BadExtensions { check_was_stopped: &AtomicBool, hashmap_workarounds: &HashMap<&str, Vec<&str>>, ) -> Vec { - debug!("verify_extensions - start"); - let res = files_to_check + files_to_check .into_par_iter() .map(|file_entry| { atomic_counter.fetch_add(1, Ordering::Relaxed); @@ -339,9 +325,7 @@ impl BadExtensions { .while_some() .filter(Option::is_some) .map(Option::unwrap) - .collect::>(); - debug!("verify_extensions - end"); - res + .collect::>() } fn get_and_validate_extension(&self, file_entry: &FileEntry, proper_extension: &str) -> Option { @@ -417,12 +401,8 @@ impl Default for BadExtensions { } impl DebugPrint for BadExtensions { - #[allow(dead_code)] - #[allow(unreachable_code)] - /// Debugging printing - only available on debug build fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } println!("---------------DEBUG PRINT---------------"); @@ -431,55 +411,20 @@ impl DebugPrint for BadExtensions { } } -impl SaveResults for BadExtensions { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( +impl PrintResults for BadExtensions { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { + writeln!( writer, "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } + )?; + writeln!(writer, "Found {} files with invalid extension.\n", self.information.number_of_files_with_bad_extension)?; - if !self.bad_extensions_files.is_empty() { - writeln!(writer, "Found {} files with invalid extension.", self.information.number_of_files_with_bad_extension).unwrap(); - for file_entry in &self.bad_extensions_files { - writeln!(writer, "{} ----- {}", file_entry.path.display(), file_entry.proper_extensions).unwrap(); - } - } else { - write!(writer, "Not found any files with invalid extension.").unwrap(); - } - - true - } -} - -impl PrintResults for BadExtensions { - /// Print information's about duplicated entries - /// Only needed for CLI - fn print_results(&self) { - println!("Found {} files with invalid extension.\n", self.information.number_of_files_with_bad_extension); for file_entry in &self.bad_extensions_files { - println!("{} ----- {}", file_entry.path.display(), file_entry.proper_extensions); + writeln!(writer, "{} ----- {}", file_entry.path.display(), file_entry.proper_extensions)?; } + + Ok(()) } } diff --git a/czkawka_core/src/big_file.rs b/czkawka_core/src/big_file.rs index e16d5a7..33ade77 100644 --- a/czkawka_core/src/big_file.rs +++ b/czkawka_core/src/big_file.rs @@ -1,21 +1,22 @@ use std::collections::BTreeMap; use std::fs; -use std::fs::{DirEntry, File, Metadata}; -use std::io::{BufWriter, Write}; +use std::fs::{DirEntry, Metadata}; +use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; use humansize::{format_size, BINARY}; -use log::{debug, info}; +use log::debug; use rayon::prelude::*; use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads, split_path}; use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData, ToolType}; -use crate::common_tool::{CommonData, CommonToolData}; -use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; +use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; +use crate::common_traits::{DebugPrint, PrintResults}; #[derive(Clone, Debug)] pub struct FileEntry { @@ -30,25 +31,16 @@ pub enum SearchMode { SmallestFiles, } -#[derive(Eq, PartialEq, Clone, Debug, Copy)] -pub enum DeleteMethod { - None, - Delete, -} - -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_real_files: usize, } -/// Struct with required information's to work pub struct BigFile { common_data: CommonToolData, information: Info, big_files: Vec<(u64, FileEntry)>, number_of_files_to_check: usize, - delete_method: DeleteMethod, search_mode: SearchMode, } @@ -59,19 +51,12 @@ impl BigFile { information: Info::default(), big_files: Default::default(), number_of_files_to_check: 50, - delete_method: DeleteMethod::None, search_mode: SearchMode::BiggestFiles, } } + #[fun_time(message = "find_big_files")] pub fn find_big_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding big files"); - let start_time = std::time::Instant::now(); - self.find_big_files_internal(stop_receiver, progress_sender); - info!("Ended finding big files which took {:?}", start_time.elapsed()); - } - - fn find_big_files_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); if !self.look_for_big_files(stop_receiver, progress_sender) { self.common_data.stopped_search = true; @@ -81,8 +66,8 @@ impl BigFile { self.debug_print(); } + #[fun_time(message = "look_for_big_files")] fn look_for_big_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("look_for_big_files - start"); let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector let mut old_map: BTreeMap> = Default::default(); @@ -94,6 +79,7 @@ impl BigFile { let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 0, 0, 0, CheckingMethod::None, self.common_data.tool_type); + debug!("Starting to search for big files"); while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); @@ -148,11 +134,12 @@ impl BigFile { } } + debug!("Collected {} files", old_map.len()); + send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); self.extract_n_biggest_files(old_map); - debug!("look_for_big_files - end"); true } @@ -193,8 +180,8 @@ impl BigFile { fe_result.push((fe.size, fe)); } + #[fun_time(message = "extract_n_biggest_files")] pub fn extract_n_biggest_files(&mut self, old_map: BTreeMap>) { - debug!("extract_n_biggest_files - start"); let iter: Box>; if self.search_mode == SearchMode::SmallestFiles { iter = Box::new(old_map.into_iter()); @@ -222,12 +209,10 @@ impl BigFile { break; } } - debug!("extract_n_biggest_files - end"); } - /// Function to delete files, from filed Vector fn delete_files(&mut self) { - match self.delete_method { + match self.common_data.delete_method { DeleteMethod::Delete => { for (_, file_entry) in &self.big_files { if fs::remove_file(&file_entry.path).is_err() { @@ -238,6 +223,7 @@ impl BigFile { DeleteMethod::None => { //Just do nothing } + _ => unreachable!(), } } } @@ -249,12 +235,8 @@ impl Default for BigFile { } impl DebugPrint for BigFile { - #[allow(dead_code)] - #[allow(unreachable_code)] - /// Debugging printing - only available on debug build fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } @@ -266,62 +248,28 @@ impl DebugPrint for BigFile { } } -impl SaveResults for BigFile { - /// Saving results to provided file - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( +impl PrintResults for BigFile { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { + writeln!( writer, "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } + )?; if self.information.number_of_real_files != 0 { if self.search_mode == SearchMode::BiggestFiles { - write!(writer, "{} the biggest files.\n\n", self.information.number_of_real_files).unwrap(); + writeln!(writer, "{} the biggest files.\n\n", self.information.number_of_real_files)?; } else { - write!(writer, "{} the smallest files.\n\n", self.information.number_of_real_files).unwrap(); + writeln!(writer, "{} the smallest files.\n\n", self.information.number_of_real_files)?; } for (size, file_entry) in &self.big_files { - writeln!(writer, "{} ({}) - {}", format_size(*size, BINARY), size, file_entry.path.display()).unwrap(); + writeln!(writer, "{} ({}) - {}", format_size(*size, BINARY), size, file_entry.path.display())?; } } else { write!(writer, "Not found any files.").unwrap(); } - true - } -} - -impl PrintResults for BigFile { - fn print_results(&self) { - if self.search_mode == SearchMode::BiggestFiles { - println!("{} the biggest files.\n\n", self.information.number_of_real_files); - } else { - println!("{} the smallest files.\n\n", self.information.number_of_real_files); - } - for (size, file_entry) in &self.big_files { - println!("{} ({}) - {}", format_size(*size, BINARY), size, file_entry.path.display()); - } + Ok(()) } } @@ -347,10 +295,6 @@ impl BigFile { &self.information } - pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { - self.delete_method = delete_method; - } - pub fn set_number_of_files_to_check(&mut self, number_of_files_to_check: usize) { self.number_of_files_to_check = number_of_files_to_check; } diff --git a/czkawka_core/src/broken_files.rs b/czkawka_core/src/broken_files.rs index 92c1521..9ee8ad9 100644 --- a/czkawka_core/src/broken_files.rs +++ b/czkawka_core/src/broken_files.rs @@ -1,15 +1,16 @@ use std::collections::BTreeMap; use std::fs::{DirEntry, File, Metadata}; use std::io::prelude::*; -use std::io::BufWriter; + use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::{fs, mem, panic}; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; -use log::{debug, info}; +use log::debug; use pdf::file::FileOptions; use pdf::object::ParseOptions; use pdf::PdfError; @@ -23,15 +24,9 @@ use crate::common::{ }; use crate::common_cache::{get_broken_files_cache_file, load_cache_from_file_generalized_by_path, save_cache_to_file_generalized}; use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData, ToolType}; -use crate::common_tool::{CommonData, CommonToolData}; +use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; use crate::common_traits::*; -#[derive(Eq, PartialEq, Clone, Debug, Copy)] -pub enum DeleteMethod { - None, - Delete, -} - #[derive(Clone, Serialize, Deserialize)] pub struct FileEntry { pub path: PathBuf, @@ -73,7 +68,6 @@ bitflags! { } } -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_broken_files: usize, @@ -84,7 +78,6 @@ pub struct BrokenFiles { information: Info, files_to_check: BTreeMap, broken_files: Vec, - delete_method: DeleteMethod, checked_types: CheckedTypes, } @@ -94,20 +87,13 @@ impl BrokenFiles { common_data: CommonToolData::new(ToolType::BrokenFiles), information: Info::default(), files_to_check: Default::default(), - delete_method: DeleteMethod::None, broken_files: Default::default(), checked_types: CheckedTypes::PDF | CheckedTypes::AUDIO | CheckedTypes::IMAGE | CheckedTypes::ARCHIVE, } } + #[fun_time(message = "find_broken_files")] pub fn find_broken_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding broken files"); - let start_time = std::time::Instant::now(); - self.find_broken_files_internal(stop_receiver, progress_sender); - info!("Ended finding broken files which took {:?}", start_time.elapsed()); - } - - pub fn find_broken_files_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); if !self.check_files(stop_receiver, progress_sender) { self.common_data.stopped_search = true; @@ -121,24 +107,8 @@ impl BrokenFiles { self.debug_print(); } - pub const fn get_broken_files(&self) -> &Vec { - &self.broken_files - } - - pub fn set_checked_types(&mut self, checked_types: CheckedTypes) { - self.checked_types = checked_types; - } - - pub const fn get_information(&self) -> &Info { - &self.information - } - - pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { - self.delete_method = delete_method; - } - + #[fun_time(message = "check_files")] fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_files - start"); let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector // Add root folders for finding @@ -149,6 +119,7 @@ impl BrokenFiles { let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 0, 1, 0, CheckingMethod::None, self.common_data.tool_type); + debug!("check_files - starting to collect files"); while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); @@ -191,6 +162,7 @@ impl BrokenFiles { (dir_result, warnings, fe_result) }) .collect(); + debug!("check_files - collected files"); // Advance the frontier folders_to_check.clear(); @@ -206,10 +178,9 @@ impl BrokenFiles { } send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); - - debug!("check_files - end"); true } + fn get_file_entry( &self, metadata: &Metadata, @@ -348,8 +319,8 @@ impl BrokenFiles { } } + #[fun_time(message = "load_cache")] fn load_cache(&mut self) -> (BTreeMap, BTreeMap, BTreeMap) { - debug!("load_cache - start (using cache {})", self.common_data.use_cache); let loaded_hash_map; let mut records_already_cached: BTreeMap = Default::default(); @@ -372,17 +343,17 @@ impl BrokenFiles { loaded_hash_map = Default::default(); non_cached_files_to_check = files_to_check; } - debug!("load_cache - end"); (loaded_hash_map, records_already_cached, non_cached_files_to_check) } + #[fun_time(message = "look_for_broken_files")] fn look_for_broken_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("look_for_broken_files - start"); let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.load_cache(); let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 1, 1, non_cached_files_to_check.len(), CheckingMethod::None, self.common_data.tool_type); + debug!("look_for_broken_files - started finding for broken files"); let mut vec_file_entry: Vec = non_cached_files_to_check .into_par_iter() .map(|(_, file_entry)| { @@ -404,6 +375,7 @@ impl BrokenFiles { .filter(Option::is_some) .map(Option::unwrap) .collect::>(); + debug!("look_for_broken_files - ended finding for broken files"); send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); @@ -418,15 +390,14 @@ impl BrokenFiles { .collect(); self.information.number_of_broken_files = self.broken_files.len(); - + debug!("Found {} broken files.", self.information.number_of_broken_files); // Clean unused data self.files_to_check = Default::default(); - debug!("look_for_broken_files - end"); true } + #[fun_time(message = "save_to_cache")] fn save_to_cache(&mut self, vec_file_entry: &[FileEntry], loaded_hash_map: BTreeMap) { - debug!("save_to_cache - start, using cache {}", self.common_data.use_cache); if self.common_data.use_cache { // Must save all results to file, old loaded from file with all currently counted results let mut all_results: BTreeMap = Default::default(); @@ -441,12 +412,11 @@ impl BrokenFiles { let messages = save_cache_to_file_generalized(&get_broken_files_cache_file(), &all_results, self.common_data.save_also_as_json, 0); self.get_text_messages_mut().extend_with_another_messages(messages); } - debug!("save_to_cache - end"); } - /// Function to delete files, from filed Vector + #[fun_time(message = "delete_files")] fn delete_files(&mut self) { - match self.delete_method { + match self.common_data.delete_method { DeleteMethod::Delete => { for file_entry in &self.broken_files { if fs::remove_file(&file_entry.path).is_err() { @@ -457,10 +427,26 @@ impl BrokenFiles { DeleteMethod::None => { //Just do nothing } + _ => { + unreachable!() + } } } } +impl BrokenFiles { + pub const fn get_broken_files(&self) -> &Vec { + &self.broken_files + } + + pub fn set_checked_types(&mut self, checked_types: CheckedTypes) { + self.checked_types = checked_types; + } + + pub const fn get_information(&self) -> &Info { + &self.information + } +} impl Default for BrokenFiles { fn default() -> Self { Self::new() @@ -468,70 +454,32 @@ impl Default for BrokenFiles { } impl DebugPrint for BrokenFiles { - #[allow(dead_code)] - #[allow(unreachable_code)] - /// Debugging printing - only available on debug build fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } - println!("---------------DEBUG PRINT---------------"); - println!("Delete Method - {:?}", self.delete_method); self.debug_print_common(); - println!("-----------------------------------------"); - } -} - -impl SaveResults for BrokenFiles { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( - writer, - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", - self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } - - if !self.broken_files.is_empty() { - writeln!(writer, "Found {} broken files.", self.information.number_of_broken_files).unwrap(); - for file_entry in &self.broken_files { - writeln!(writer, "{} - {}", file_entry.path.display(), file_entry.error_string).unwrap(); - } - } else { - write!(writer, "Not found any broken files.").unwrap(); - } - - true } } impl PrintResults for BrokenFiles { - /// Print information's about duplicated entries - /// Only needed for CLI - fn print_results(&self) { - println!("Found {} broken files.\n", self.information.number_of_broken_files); - for file_entry in &self.broken_files { - println!("{} - {}", file_entry.path.display(), file_entry.error_string); + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { + writeln!( + writer, + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items + )?; + + if !self.broken_files.is_empty() { + writeln!(writer, "Found {} broken files.", self.information.number_of_broken_files)?; + for file_entry in &self.broken_files { + writeln!(writer, "{} - {}", file_entry.path.display(), file_entry.error_string)?; + } + } else { + write!(writer, "Not found any broken files.")?; } + + Ok(()) } } diff --git a/czkawka_core/src/common.rs b/czkawka_core/src/common.rs index 0f5d2fb..3a74397 100644 --- a/czkawka_core/src/common.rs +++ b/czkawka_core/src/common.rs @@ -11,13 +11,14 @@ use std::{fs, thread}; #[cfg(feature = "heif")] use anyhow::Result; use directories_next::ProjectDirs; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; use handsome_logger::{ColorChoice, ConfigBuilder, TerminalMode}; use image::{DynamicImage, ImageBuffer, Rgb}; use imagepipe::{ImageSource, Pipeline}; #[cfg(feature = "heif")] use libheif_rs::{ColorSpace, HeifContext, RgbChroma}; -use log::{debug, LevelFilter, Record}; +use log::{info, LevelFilter, Record}; // #[cfg(feature = "heif")] // use libheif_rs::LibHeif; @@ -25,6 +26,7 @@ use crate::common_dir_traversal::{CheckingMethod, ProgressData, ToolType}; use crate::common_directory::Directories; use crate::common_items::ExcludedItems; use crate::common_traits::ResultEntry; +use crate::CZKAWKA_VERSION; static NUMBER_OF_THREADS: state::InitCell = state::InitCell::new(); @@ -39,7 +41,7 @@ pub fn get_number_of_threads() -> usize { fn filtering_messages(record: &Record) -> bool { if let Some(module_path) = record.module_path() { - !["symphonia", "i18n_embed"].iter().any(|&x| module_path.contains(x)) + module_path.starts_with("czkawka") } else { true } @@ -52,6 +54,14 @@ pub fn setup_logger(disabled_printing: bool) { handsome_logger::TermLogger::init(config, TerminalMode::Mixed, ColorChoice::Always).unwrap(); } +pub fn print_version_mode() { + info!( + "Czkawka version: {}, was compiled with {} mode", + CZKAWKA_VERSION, + if cfg!(debug_assertions) { "debug" } else { "release" } + ); +} + pub fn set_default_number_of_threads() { set_number_of_threads(num_cpus::get()); } @@ -66,7 +76,6 @@ pub fn set_number_of_threads(thread_number: usize) { rayon::ThreadPoolBuilder::new().num_threads(get_number_of_threads()).build_global().unwrap(); } -/// Class for common functions used across other class/functions pub const RAW_IMAGE_EXTENSIONS: &[&str] = &[ ".mrw", ".arw", ".srf", ".sr2", ".mef", ".orf", ".srw", ".erf", ".kdc", ".kdc", ".dcs", ".rw2", ".raf", ".dcr", ".dng", ".pef", ".crw", ".iiq", ".3fr", ".nrw", ".nef", ".mos", ".cr2", ".ari", @@ -225,17 +234,6 @@ pub fn create_crash_message(library_name: &str, file_path: &str, home_library_ur } impl Common { - /// Printing time which took between start and stop point and prints also function name - #[allow(unused_variables)] - pub fn print_time(start_time: SystemTime, end_time: SystemTime, function_name: &str) { - #[cfg(debug_assertions)] - println!( - "Execution of function \"{}\" took {:?}", - function_name, - end_time.duration_since(start_time).expect("Time cannot go reverse.") - ); - } - pub fn delete_multiple_entries(entries: &[String]) -> Vec { let mut path: &Path; let mut warnings: Vec = Vec::new(); @@ -265,8 +263,6 @@ impl Common { warning } - /// Function to check if directory match expression - pub fn regex_check(expression: &str, directory: impl AsRef) -> bool { if expression == "*" { return true; @@ -441,11 +437,10 @@ pub fn prepare_thread_handler_common( (progress_thread_sender, progress_thread_run, atomic_counter, check_was_stopped) } +#[fun_time(message = "send_info_and_wait_for_ending_all_threads")] pub fn send_info_and_wait_for_ending_all_threads(progress_thread_run: &Arc, progress_thread_handle: JoinHandle<()>) { - debug!("Sending info to stop all threads"); progress_thread_run.store(false, Ordering::Relaxed); progress_thread_handle.join().unwrap(); - debug!("All threads stopped"); } #[cfg(test)] diff --git a/czkawka_core/src/common_cache.rs b/czkawka_core/src/common_cache.rs index b5268a7..6cce94d 100644 --- a/czkawka_core/src/common_cache.rs +++ b/czkawka_core/src/common_cache.rs @@ -3,6 +3,7 @@ use crate::common_messages::Messages; use crate::common_traits::ResultEntry; use crate::duplicate::HashType; use crate::similar_images::{convert_algorithm_to_string, convert_filters_to_string}; +use fun_time::fun_time; use image::imageops::FilterType; use image_hasher::HashAlg; use log::debug; @@ -40,11 +41,11 @@ pub fn get_duplicate_cache_file(type_of_hash: &HashType, is_prehash: bool) -> St format!("cache_duplicates_{type_of_hash:?}{prehash_str}_61.bin") } +#[fun_time(message = "save_cache_to_file_generalized")] pub fn save_cache_to_file_generalized(cache_file_name: &str, hashmap: &BTreeMap, save_also_as_json: bool, minimum_file_size: u64) -> Messages where T: Serialize + ResultEntry + Sized + Send + Sync, { - debug!("Saving cache to file {} (or also json alternative) - {} results", cache_file_name, hashmap.len()); let mut text_messages = Messages::new(); if let Some(((file_handler, cache_file), (file_handler_json, cache_file_json))) = common::open_cache_folder(cache_file_name, true, save_also_as_json, &mut text_messages.warnings) @@ -83,6 +84,7 @@ where text_messages } +#[fun_time(message = "load_cache_from_file_generalized_by_path")] pub fn load_cache_from_file_generalized_by_path(cache_file_name: &str, delete_outdated_cache: bool, used_files: &BTreeMap) -> (Messages, Option>) where for<'a> T: Deserialize<'a> + ResultEntry + Sized + Send + Sync + Clone, @@ -102,6 +104,7 @@ where (text_messages, Some(map_loaded_entries)) } +#[fun_time(message = "load_cache_from_file_generalized_by_size")] pub fn load_cache_from_file_generalized_by_size( cache_file_name: &str, delete_outdated_cache: bool, @@ -132,6 +135,7 @@ where (text_messages, Some(map_loaded_entries)) } +#[fun_time(message = "load_cache_from_file_generalized_by_path_from_size")] pub fn load_cache_from_file_generalized_by_path_from_size( cache_file_name: &str, delete_outdated_cache: bool, @@ -162,11 +166,11 @@ where (text_messages, Some(map_loaded_entries)) } +#[fun_time(message = "load_cache_from_file_generalized")] fn load_cache_from_file_generalized(cache_file_name: &str, delete_outdated_cache: bool, used_files: &BTreeMap) -> (Messages, Option>) where for<'a> T: Deserialize<'a> + ResultEntry + Sized + Send + Sync + Clone, { - debug!("Loading cache from file {} (or json alternative)", cache_file_name); let mut text_messages = Messages::new(); if let Some(((file_handler, cache_file), (file_handler_json, cache_file_json))) = common::open_cache_folder(cache_file_name, false, true, &mut text_messages.warnings) { @@ -198,8 +202,10 @@ where }; } - // Don't load cache data if destination file not exists - debug!("Starting to removing outdated cache entries"); + debug!( + "Starting removing outdated cache entries (removing non existent files from cache - {})", + delete_outdated_cache + ); let initial_number_of_entries = vec_loaded_entries.len(); vec_loaded_entries = vec_loaded_entries .into_par_iter() diff --git a/czkawka_core/src/common_dir_traversal.rs b/czkawka_core/src/common_dir_traversal.rs index 0a3f661..3cc59b9 100644 --- a/czkawka_core/src/common_dir_traversal.rs +++ b/czkawka_core/src/common_dir_traversal.rs @@ -6,7 +6,9 @@ use std::sync::atomic::Ordering; use std::time::UNIX_EPOCH; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; +use log::debug; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -338,6 +340,7 @@ where F: Fn(&FileEntry) -> T, T: Ord + PartialOrd, { + #[fun_time(message = "run(collecting files/dirs)")] pub fn run(self) -> DirTraversalResult { let mut all_warnings = vec![]; let mut grouped_file_entries: BTreeMap> = BTreeMap::new(); @@ -497,6 +500,12 @@ where send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); + debug!( + "Collected {} files, {} folders", + grouped_file_entries.values().map(Vec::len).sum::(), + folder_entries.len() + ); + match collect { Collect::Files | Collect::InvalidSymlinks => DirTraversalResult::SuccessFiles { grouped_file_entries, diff --git a/czkawka_core/src/common_directory.rs b/czkawka_core/src/common_directory.rs index 23e74e2..f113a9c 100644 --- a/czkawka_core/src/common_directory.rs +++ b/czkawka_core/src/common_directory.rs @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf}; use std::{fs, os::unix::fs::MetadataExt}; use crate::common::Common; +use crate::common_messages::Messages; use crate::flc; use crate::localizer_core::generate_translation_hashmap; @@ -25,15 +26,12 @@ impl Directories { self.reference_directories = reference_directory; } - /// Setting included directories, at least one must be provided or scan won't start - pub fn set_included_directory(&mut self, included_directory: Vec) -> (Vec, Vec, Vec) { - let messages: Vec = Vec::new(); - let mut errors: Vec = Vec::new(); - let mut warnings: Vec = Vec::new(); + pub fn set_included_directory(&mut self, included_directory: Vec) -> Messages { + let mut messages: Messages = Messages::new(); if included_directory.is_empty() { - errors.push(flc!("core_missing_no_chosen_included_directory")); - return (messages, warnings, errors); + messages.errors.push(flc!("core_missing_no_chosen_included_directory")); + return messages; } let directories: Vec = included_directory; @@ -41,7 +39,7 @@ impl Directories { let mut checked_directories: Vec = Vec::new(); for directory in directories { if directory.to_string_lossy().contains('*') { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_wildcard_no_supported", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -50,7 +48,7 @@ impl Directories { #[cfg(not(target_family = "windows"))] if directory.is_relative() { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_relative_path", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -58,7 +56,7 @@ impl Directories { } #[cfg(target_family = "windows")] if directory.is_relative() && !directory.starts_with("\\") { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_relative_path", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -66,14 +64,14 @@ impl Directories { } if !directory.exists() { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_must_exists", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); continue; } if !directory.is_dir() { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_must_be_directory", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -83,23 +81,20 @@ impl Directories { } if checked_directories.is_empty() { - warnings.push(flc!("core_included_directory_zero_valid_directories")); - return (messages, warnings, errors); + messages.warnings.push(flc!("core_included_directory_zero_valid_directories")); + return messages; } self.included_directories = checked_directories; - (messages, warnings, errors) + messages } - /// Setting absolute path to exclude from search - pub fn set_excluded_directory(&mut self, excluded_directory: Vec) -> (Vec, Vec, Vec) { - let messages: Vec = Vec::new(); - let mut errors: Vec = Vec::new(); - let mut warnings: Vec = Vec::new(); + pub fn set_excluded_directory(&mut self, excluded_directory: Vec) -> Messages { + let mut messages: Messages = Messages::new(); if excluded_directory.is_empty() { - return (messages, warnings, errors); + return messages; } let directories: Vec = excluded_directory; @@ -108,11 +103,11 @@ impl Directories { for directory in directories { let directory_as_string = directory.to_string_lossy(); if directory_as_string == "/" { - errors.push(flc!("core_excluded_directory_pointless_slash")); + messages.errors.push(flc!("core_excluded_directory_pointless_slash")); break; } if directory_as_string.contains('*') { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_wildcard_no_supported", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -120,7 +115,7 @@ impl Directories { } #[cfg(not(target_family = "windows"))] if directory.is_relative() { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_relative_path", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -128,7 +123,7 @@ impl Directories { } #[cfg(target_family = "windows")] if directory.is_relative() && !directory.starts_with("\\") { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_relative_path", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -140,7 +135,7 @@ impl Directories { continue; } if !directory.is_dir() { - warnings.push(flc!( + messages.warnings.push(flc!( "core_directory_must_be_directory", generate_translation_hashmap(vec![("path", directory.display().to_string())]) )); @@ -150,7 +145,7 @@ impl Directories { } self.excluded_directories = checked_directories; - (messages, warnings, errors) + messages } #[cfg(target_family = "unix")] @@ -158,11 +153,8 @@ impl Directories { self.exclude_other_filesystems = Some(exclude_other_filesystems); } - /// Remove unused entries when included or excluded overlaps with each other or are duplicated etc. - pub fn optimize_directories(&mut self, recursive_search: bool) -> (Vec, Vec, Vec) { - let messages: Vec = Vec::new(); - let mut errors: Vec = Vec::new(); - let warnings: Vec = Vec::new(); + pub fn optimize_directories(&mut self, recursive_search: bool) -> Messages { + let mut messages: Messages = Messages::new(); let mut optimized_included: Vec = Vec::new(); let mut optimized_excluded: Vec = Vec::new(); @@ -291,8 +283,8 @@ impl Directories { } if self.included_directories.is_empty() { - errors.push(flc!("core_directory_overlap")); - return (messages, warnings, errors); + messages.errors.push(flc!("core_directory_overlap")); + return messages; } // Not needed, but better is to have sorted everything @@ -305,7 +297,7 @@ impl Directories { for d in &self.included_directories { match fs::metadata(d) { Ok(m) => self.included_dev_ids.push(m.dev()), - Err(_) => errors.push(flc!( + Err(_) => messages.errors.push(flc!( "core_directory_unable_to_get_device_id", generate_translation_hashmap(vec![("path", d.display().to_string())]) )), @@ -313,14 +305,13 @@ impl Directories { } } - (messages, warnings, errors) + messages } pub fn is_in_referenced_directory(&self, path: &Path) -> bool { self.reference_directories.iter().any(|e| path.starts_with(e)) } - /// Checks whether a specified directory is excluded from searching pub fn is_excluded(&self, path: impl AsRef) -> bool { let path = path.as_ref(); #[cfg(target_family = "windows")] @@ -334,8 +325,6 @@ impl Directories { self.exclude_other_filesystems.unwrap_or(false) } - /// Checks whether a specified directory is on other filesystems rather then include - /// directories #[cfg(target_family = "unix")] pub fn is_on_other_filesystems(&self, path: impl AsRef) -> Result { let path = path.as_ref(); diff --git a/czkawka_core/src/common_extensions.rs b/czkawka_core/src/common_extensions.rs index daa6479..2bfec8c 100644 --- a/czkawka_core/src/common_extensions.rs +++ b/czkawka_core/src/common_extensions.rs @@ -1,3 +1,5 @@ +use crate::common_messages::Messages; + #[derive(Debug, Clone, Default)] pub struct Extensions { file_extensions: Vec, @@ -9,13 +11,11 @@ impl Extensions { } /// List of allowed extensions, only files with this extensions will be checking if are duplicates /// After, extensions cannot contains any dot, commas etc. - pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String) -> (Vec, Vec, Vec) { - let mut messages = Vec::new(); - let mut warnings = Vec::new(); - let errors = Vec::new(); + pub fn set_allowed_extensions(&mut self, mut allowed_extensions: String) -> Messages { + let mut messages = Messages::new(); if allowed_extensions.trim().is_empty() { - return (messages, warnings, errors); + return messages; } allowed_extensions = allowed_extensions.replace("IMAGE", "jpg,kra,gif,png,bmp,tiff,hdr,svg"); allowed_extensions = allowed_extensions.replace("VIDEO", "mp4,flv,mkv,webm,vob,ogv,gifv,avi,mov,wmv,mpg,m4v,m4p,mpeg,3gp"); @@ -33,12 +33,12 @@ impl Extensions { } if extension[1..].contains('.') { - warnings.push(format!("{extension} is not valid extension because contains dot inside")); + messages.warnings.push(format!("{extension} is not valid extension because contains dot inside")); continue; } if extension[1..].contains(' ') { - warnings.push(format!("{extension} is not valid extension because contains empty space inside")); + messages.warnings.push(format!("{extension} is not valid extension because contains empty space inside")); continue; } @@ -48,9 +48,11 @@ impl Extensions { } if self.file_extensions.is_empty() { - messages.push("No valid extensions were provided, so allowing all extensions by default.".to_string()); + messages + .messages + .push("No valid extensions were provided, so allowing all extensions by default.".to_string()); } - (messages, warnings, errors) + messages } pub fn matches_filename(&self, file_name: &str) -> bool { diff --git a/czkawka_core/src/common_items.rs b/czkawka_core/src/common_items.rs index 5c5b3cd..f6ecf8c 100644 --- a/czkawka_core/src/common_items.rs +++ b/czkawka_core/src/common_items.rs @@ -16,8 +16,7 @@ impl ExcludedItems { pub fn new() -> Self { Default::default() } - /// Setting excluded items which needs to contains * wildcard - /// Are a lot of slower than absolute path, so it should be used to heavy + pub fn set_excluded_items(&mut self, excluded_items: Vec) -> (Vec, Vec, Vec) { let messages: Vec = Vec::new(); let mut warnings: Vec = Vec::new(); @@ -54,7 +53,6 @@ impl ExcludedItems { (messages, warnings, errors) } - /// Checks whether a specified path is excluded from searching pub fn is_excluded(&self, path: impl AsRef) -> bool { #[cfg(target_family = "windows")] let path = Common::normalize_windows_path(path); diff --git a/czkawka_core/src/common_tool.rs b/czkawka_core/src/common_tool.rs index 30ecaf9..d7b843e 100644 --- a/czkawka_core/src/common_tool.rs +++ b/czkawka_core/src/common_tool.rs @@ -14,7 +14,7 @@ pub struct CommonToolData { pub(crate) allowed_extensions: Extensions, pub(crate) excluded_items: ExcludedItems, pub(crate) recursive_search: bool, - // delete_method: DeleteMethod, // ? + pub(crate) delete_method: DeleteMethod, pub(crate) maximal_file_size: u64, pub(crate) minimal_file_size: u64, pub(crate) stopped_search: bool, @@ -24,6 +24,18 @@ pub struct CommonToolData { pub(crate) use_reference_folders: bool, } +#[derive(Eq, PartialEq, Clone, Debug, Copy, Default)] +pub enum DeleteMethod { + #[default] + None, + Delete, // Just delete items + AllExceptNewest, + AllExceptOldest, + OneOldest, + OneNewest, + HardLink, +} + impl CommonToolData { pub fn new(tool_type: ToolType) -> Self { Self { @@ -33,6 +45,7 @@ impl CommonToolData { allowed_extensions: Extensions::new(), excluded_items: ExcludedItems::new(), recursive_search: true, + delete_method: DeleteMethod::None, maximal_file_size: u64::MAX, minimal_file_size: 8192, stopped_search: false, @@ -128,22 +141,25 @@ pub trait CommonData { self.get_cd().use_reference_folders } + fn set_delete_method(&mut self, delete_method: DeleteMethod) { + self.get_cd_mut().delete_method = delete_method; + } + fn get_delete_method(&self) -> DeleteMethod { + self.get_cd().delete_method + } + fn set_included_directory(&mut self, included_directory: Vec) { - let (messages, warnings, errors) = self.get_cd_mut().directories.set_included_directory(included_directory); - self.get_cd_mut().text_messages.extend_messages_with(messages, warnings, errors); + let messages = self.get_cd_mut().directories.set_included_directory(included_directory); + self.get_cd_mut().text_messages.extend_with_another_messages(messages); } fn set_excluded_directory(&mut self, excluded_directory: Vec) { - let (messages, warnings, errors) = self.get_cd_mut().directories.set_excluded_directory(excluded_directory); - self.get_cd_mut().text_messages.messages.extend(messages); - self.get_cd_mut().text_messages.warnings.extend(warnings); - self.get_cd_mut().text_messages.errors.extend(errors); + let messages = self.get_cd_mut().directories.set_excluded_directory(excluded_directory); + self.get_cd_mut().text_messages.extend_with_another_messages(messages); } fn set_allowed_extensions(&mut self, allowed_extensions: String) { - let (messages, warnings, errors) = self.get_cd_mut().allowed_extensions.set_allowed_extensions(allowed_extensions); - self.get_cd_mut().text_messages.messages.extend(messages); - self.get_cd_mut().text_messages.warnings.extend(warnings); - self.get_cd_mut().text_messages.errors.extend(errors); + let messages = self.get_cd_mut().allowed_extensions.set_allowed_extensions(allowed_extensions); + self.get_cd_mut().text_messages.extend_with_another_messages(messages); } fn set_excluded_items(&mut self, excluded_items: Vec) { @@ -155,8 +171,8 @@ pub trait CommonData { fn optimize_dirs_before_start(&mut self) { let recursive_search = self.get_cd().recursive_search; - let (messages, warnings, errors) = self.get_cd_mut().directories.optimize_directories(recursive_search); - self.get_cd_mut().text_messages.extend_messages_with(messages, warnings, errors); + let messages = self.get_cd_mut().directories.optimize_directories(recursive_search); + self.get_cd_mut().text_messages.extend_with_another_messages(messages); } fn debug_print_common(&self) { @@ -172,6 +188,7 @@ pub trait CommonData { println!("Use cache: {:?}", self.get_cd().use_cache); println!("Delete outdated cache: {:?}", self.get_cd().delete_outdated_cache); println!("Save also as json: {:?}", self.get_cd().save_also_as_json); + println!("Delete method: {:?}", self.get_cd().delete_method); println!("---------------DEBUG PRINT MESSAGES---------------"); println!("Errors size - {}", self.get_cd().text_messages.errors.len()); diff --git a/czkawka_core/src/common_traits.rs b/czkawka_core/src/common_traits.rs index 60953ae..0ba9ebc 100644 --- a/czkawka_core/src/common_traits.rs +++ b/czkawka_core/src/common_traits.rs @@ -1,15 +1,36 @@ +use fun_time::fun_time; +use std::fs::File; +use std::io::{BufWriter, Write}; use std::path::Path; pub trait DebugPrint { fn debug_print(&self); } -pub trait SaveResults { - fn save_results_to_file(&mut self, file_name: &str) -> bool; -} - pub trait PrintResults { - fn print_results(&self); + fn write_results(&self, writer: &mut T) -> std::io::Result<()>; + + #[fun_time(message = "print_results_to_output")] + fn print_results_to_output(&self) { + let stdout = std::io::stdout(); + let mut handle = stdout.lock(); + self.write_results(&mut handle).unwrap(); + handle.flush().unwrap(); + } + + #[fun_time(message = "print_results_to_file")] + fn print_results_to_file(&self, file_name: &str) -> std::io::Result<()> { + let file_name: String = match file_name { + "" => "results.txt".to_string(), + k => k.to_string(), + }; + + let file_handler = File::create(file_name)?; + let mut writer = BufWriter::new(file_handler); + self.write_results(&mut writer)?; + writer.flush()?; + Ok(()) + } } pub trait ResultEntry { diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index 5ef7699..48c1463 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -1,9 +1,8 @@ -use std::collections::BTreeMap; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::fs::File; use std::hash::Hasher; use std::io::prelude::*; -use std::io::{self, BufWriter, Error, ErrorKind}; +use std::io::{self, Error, ErrorKind}; #[cfg(target_family = "unix")] use std::os::unix::fs::MetadataExt; use std::path::Path; @@ -11,9 +10,10 @@ use std::sync::atomic::Ordering; use std::{fs, mem}; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; use humansize::{format_size, BINARY}; -use log::{debug, info}; +use log::debug; use rayon::prelude::*; use xxhash_rust::xxh3::Xxh3; @@ -21,7 +21,7 @@ use crate::common::{prepare_thread_handler_common, send_info_and_wait_for_ending use crate::common_cache::{get_duplicate_cache_file, load_cache_from_file_generalized_by_size, save_cache_to_file_generalized}; use crate::common_dir_traversal::{CheckingMethod, DirTraversalBuilder, DirTraversalResult, FileEntry, ProgressData, ToolType}; use crate::common_messages::Messages; -use crate::common_tool::{CommonData, CommonToolData}; +use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; use crate::common_traits::*; const TEMP_HARDLINK_FILE: &str = "rzeczek.rxrxrxl"; @@ -44,17 +44,6 @@ impl HashType { } } -#[derive(Eq, PartialEq, Clone, Debug, Copy, Default)] -pub enum DeleteMethod { - #[default] - None, - AllExceptNewest, - AllExceptOldest, - OneOldest, - OneNewest, - HardLink, -} - #[derive(Default)] pub struct Info { pub number_of_groups_by_size: usize, @@ -89,7 +78,6 @@ pub struct DuplicateFinder { // File Size, next grouped by file size, next grouped by hash files_with_identical_hashes_referenced: BTreeMap)>>, check_method: CheckingMethod, - delete_method: DeleteMethod, hash_type: HashType, ignore_hard_links: bool, dryrun: bool, @@ -113,25 +101,18 @@ impl DuplicateFinder { files_with_identical_size_referenced: Default::default(), files_with_identical_hashes_referenced: Default::default(), check_method: CheckingMethod::None, - delete_method: DeleteMethod::None, ignore_hard_links: true, hash_type: HashType::Blake3, dryrun: false, use_prehash_cache: true, - minimal_cache_file_size: 1024 * 1024 / 4, // By default cache only >= 256 KB files + minimal_cache_file_size: 1024 * 256, // By default cache only >= 256 KB files minimal_prehash_cache_file_size: 0, case_sensitive_name_comparison: false, } } + #[fun_time(message = "find_duplicates")] pub fn find_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding duplicates"); - let start_time = std::time::Instant::now(); - self.find_duplicates_internal(stop_receiver, progress_sender); - info!("Ended finding duplicates which took {:?}", start_time.elapsed()); - } - - fn find_duplicates_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty(); @@ -170,6 +151,695 @@ impl DuplicateFinder { self.debug_print(); } + #[fun_time(message = "check_files_name")] + fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { + let group_by_func = if self.case_sensitive_name_comparison { + |fe: &FileEntry| fe.path.file_name().unwrap().to_string_lossy().to_string() + } else { + |fe: &FileEntry| fe.path.file_name().unwrap().to_string_lossy().to_lowercase() + }; + + let result = DirTraversalBuilder::new() + .root_dirs(self.common_data.directories.included_directories.clone()) + .group_by(group_by_func) + .stop_receiver(stop_receiver) + .progress_sender(progress_sender) + .checking_method(CheckingMethod::Name) + .directories(self.common_data.directories.clone()) + .allowed_extensions(self.common_data.allowed_extensions.clone()) + .excluded_items(self.common_data.excluded_items.clone()) + .recursive_search(self.common_data.recursive_search) + .minimal_file_size(self.common_data.minimal_file_size) + .maximal_file_size(self.common_data.maximal_file_size) + .build() + .run(); + + match result { + DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { + self.common_data.text_messages.warnings.extend(warnings); + + // Create new BTreeMap without single size entries(files have not duplicates) + self.files_with_identical_names = grouped_file_entries.into_iter().filter(|(_name, vector)| vector.len() > 1).collect(); + + // Reference - only use in size, because later hash will be counted differently + if self.common_data.use_reference_folders { + let vec = mem::take(&mut self.files_with_identical_names) + .into_iter() + .filter_map(|(_name, vec_file_entry)| { + let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry + .into_iter() + .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); + for (fe, vec_fe) in vec { + self.files_with_identical_names_referenced.insert(fe.path.to_string_lossy().to_string(), (fe, vec_fe)); + } + } + self.calculate_name_stats(); + + true + } + DirTraversalResult::SuccessFolders { .. } => { + unreachable!() + } + DirTraversalResult::Stopped => false, + } + } + + fn calculate_name_stats(&mut self) { + if self.common_data.use_reference_folders { + for (_fe, vector) in self.files_with_identical_names_referenced.values() { + self.information.number_of_duplicated_files_by_name += vector.len(); + self.information.number_of_groups_by_name += 1; + } + } else { + for vector in self.files_with_identical_names.values() { + self.information.number_of_duplicated_files_by_name += vector.len() - 1; + self.information.number_of_groups_by_name += 1; + } + } + } + + #[fun_time(message = "check_files_size_name")] + fn check_files_size_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { + let group_by_func = if self.case_sensitive_name_comparison { + |fe: &FileEntry| (fe.size, fe.path.file_name().unwrap().to_string_lossy().to_string()) + } else { + |fe: &FileEntry| (fe.size, fe.path.file_name().unwrap().to_string_lossy().to_lowercase()) + }; + + let result = DirTraversalBuilder::new() + .root_dirs(self.common_data.directories.included_directories.clone()) + .group_by(group_by_func) + .stop_receiver(stop_receiver) + .progress_sender(progress_sender) + .checking_method(CheckingMethod::Name) + .directories(self.common_data.directories.clone()) + .allowed_extensions(self.common_data.allowed_extensions.clone()) + .excluded_items(self.common_data.excluded_items.clone()) + .recursive_search(self.common_data.recursive_search) + .minimal_file_size(self.common_data.minimal_file_size) + .maximal_file_size(self.common_data.maximal_file_size) + .build() + .run(); + + match result { + DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { + self.common_data.text_messages.warnings.extend(warnings); + + self.files_with_identical_size_names = grouped_file_entries.into_iter().filter(|(_name, vector)| vector.len() > 1).collect(); + + // Reference - only use in size, because later hash will be counted differently + if self.common_data.use_reference_folders { + let vec = mem::take(&mut self.files_with_identical_size_names) + .into_iter() + .filter_map(|(_size, vec_file_entry)| { + let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry + .into_iter() + .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); + for (fe, vec_fe) in vec { + self.files_with_identical_size_names_referenced + .insert((fe.size, fe.path.to_string_lossy().to_string()), (fe, vec_fe)); + } + } + self.calculate_size_name_stats(); + + true + } + DirTraversalResult::SuccessFolders { .. } => { + unreachable!() + } + DirTraversalResult::Stopped => false, + } + } + + fn calculate_size_name_stats(&mut self) { + if self.common_data.use_reference_folders { + for ((size, _name), (_fe, vector)) in &self.files_with_identical_size_names_referenced { + self.information.number_of_duplicated_files_by_size_name += vector.len(); + self.information.number_of_groups_by_size_name += 1; + self.information.lost_space_by_size += (vector.len() as u64) * size; + } + } else { + for ((size, _name), vector) in &self.files_with_identical_size_names { + self.information.number_of_duplicated_files_by_size_name += vector.len() - 1; + self.information.number_of_groups_by_size_name += 1; + self.information.lost_space_by_size += (vector.len() as u64 - 1) * size; + } + } + } + + #[fun_time(message = "check_files_size")] + fn check_files_size(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { + let max_stage = match self.check_method { + CheckingMethod::Size => 0, + CheckingMethod::Hash => 2, + _ => panic!(), + }; + let result = DirTraversalBuilder::new() + .root_dirs(self.common_data.directories.included_directories.clone()) + .group_by(|fe| fe.size) + .stop_receiver(stop_receiver) + .progress_sender(progress_sender) + .checking_method(self.check_method) + .max_stage(max_stage) + .directories(self.common_data.directories.clone()) + .allowed_extensions(self.common_data.allowed_extensions.clone()) + .excluded_items(self.common_data.excluded_items.clone()) + .recursive_search(self.common_data.recursive_search) + .minimal_file_size(self.common_data.minimal_file_size) + .maximal_file_size(self.common_data.maximal_file_size) + .build() + .run(); + + match result { + DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { + self.common_data.text_messages.warnings.extend(warnings); + + for (size, vec) in grouped_file_entries { + if vec.len() <= 1 { + continue; + } + + let vector = if self.ignore_hard_links { filter_hard_links(&vec) } else { vec }; + + if vector.len() > 1 { + self.files_with_identical_size.insert(size, vector); + } + } + + self.filter_reference_folders_by_size(); + self.calculate_size_stats(); + + debug!( + "check_file_size - after calculating size stats/duplicates, found in {} groups, {} files with same size | referenced {} groups, {} files", + self.files_with_identical_size.len(), + self.files_with_identical_size.values().map(Vec::len).sum::(), + self.files_with_identical_size_referenced.len(), + self.files_with_identical_size_referenced.values().map(|(_fe, vec)| vec.len()).sum::() + ); + + true + } + DirTraversalResult::SuccessFolders { .. } => { + unreachable!() + } + DirTraversalResult::Stopped => false, + } + } + + fn calculate_size_stats(&mut self) { + if self.common_data.use_reference_folders { + for (size, (_fe, vector)) in &self.files_with_identical_size_referenced { + self.information.number_of_duplicated_files_by_size += vector.len(); + self.information.number_of_groups_by_size += 1; + self.information.lost_space_by_size += (vector.len() as u64) * size; + } + } else { + for (size, vector) in &self.files_with_identical_size { + self.information.number_of_duplicated_files_by_size += vector.len() - 1; + self.information.number_of_groups_by_size += 1; + self.information.lost_space_by_size += (vector.len() as u64 - 1) * size; + } + } + } + + #[fun_time(message = "filter_reference_folders_by_size")] + fn filter_reference_folders_by_size(&mut self) { + if self.common_data.use_reference_folders && self.check_method == CheckingMethod::Size { + let vec = mem::take(&mut self.files_with_identical_size) + .into_iter() + .filter_map(|(_size, vec_file_entry)| { + let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry + .into_iter() + .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + None + } else { + Some((files_from_referenced_folders.pop().unwrap(), normal_files)) + } + }) + .collect::)>>(); + for (fe, vec_fe) in vec { + self.files_with_identical_size_referenced.insert(fe.size, (fe, vec_fe)); + } + } + } + + #[fun_time(message = "prehash_load_cache_at_start")] + fn prehash_load_cache_at_start(&mut self) -> (BTreeMap>, BTreeMap>, BTreeMap>) { + // Cache algorithm + // - Load data from cache + // - Convert from BT> to BT + // - Save to proper values + let loaded_hash_map; + let mut records_already_cached: BTreeMap> = Default::default(); + let mut non_cached_files_to_check: BTreeMap> = Default::default(); + + if self.use_prehash_cache { + let (messages, loaded_items) = load_cache_from_file_generalized_by_size::( + &get_duplicate_cache_file(&self.hash_type, true), + self.get_delete_outdated_cache(), + &self.files_with_identical_size, + ); + self.get_text_messages_mut().extend_with_another_messages(messages); + loaded_hash_map = loaded_items.unwrap_or_default(); + + debug!("prehash_load_cache_at_start - started diff between loaded and prechecked files"); + for (size, mut vec_file_entry) in mem::take(&mut self.files_with_identical_size) { + if let Some(cached_vec_file_entry) = loaded_hash_map.get(&size) { + // TODO maybe hashset is not needed when using < 4 elements + let cached_path_entries = cached_vec_file_entry.iter().map(|e| &e.path).collect::>(); + for file_entry in vec_file_entry { + if cached_path_entries.contains(&file_entry.path) { + records_already_cached.entry(size).or_default().push(file_entry); + } else { + non_cached_files_to_check.entry(size).or_default().push(file_entry); + } + } + } else { + non_cached_files_to_check.entry(size).or_default().append(&mut vec_file_entry); + } + } + + debug!( + "prehash_load_cache_at_start - completed diff between loaded and prechecked files, {}({}) - non cached, {}({}) - already cached", + non_cached_files_to_check.values().map(Vec::len).sum::(), + format_size(non_cached_files_to_check.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), + records_already_cached.values().map(Vec::len).sum::(), + format_size(records_already_cached.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), + ); + } else { + loaded_hash_map = Default::default(); + mem::swap(&mut self.files_with_identical_size, &mut non_cached_files_to_check); + } + (loaded_hash_map, records_already_cached, non_cached_files_to_check) + } + + #[fun_time(message = "prehash_save_cache_at_exit")] + fn prehash_save_cache_at_exit(&mut self, loaded_hash_map: BTreeMap>, pre_hash_results: &Vec<(u64, BTreeMap>, Vec)>) { + if self.use_prehash_cache { + // All results = records already cached + computed results + let mut save_cache_to_hashmap: BTreeMap = Default::default(); + + for (size, vec_file_entry) in loaded_hash_map { + if size >= self.minimal_prehash_cache_file_size { + for file_entry in vec_file_entry { + save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone()); + } + } + } + + for (size, hash_map, _errors) in pre_hash_results { + if *size >= self.minimal_prehash_cache_file_size { + for vec_file_entry in hash_map.values() { + for file_entry in vec_file_entry { + save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone()); + } + } + } + } + + let messages = save_cache_to_file_generalized( + &get_duplicate_cache_file(&self.hash_type, true), + &save_cache_to_hashmap, + self.common_data.save_also_as_json, + self.minimal_prehash_cache_file_size, + ); + self.get_text_messages_mut().extend_with_another_messages(messages); + } + } + + #[fun_time(message = "prehashing")] + fn prehashing( + &mut self, + stop_receiver: Option<&Receiver<()>>, + progress_sender: Option<&UnboundedSender>, + pre_checked_map: &mut BTreeMap>, + ) -> Option<()> { + let check_type = self.hash_type; + let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = prepare_thread_handler_common( + progress_sender, + 1, + 2, + self.files_with_identical_size.values().map(Vec::len).sum(), + self.check_method, + self.common_data.tool_type, + ); + + let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.prehash_load_cache_at_start(); + + debug!("Starting calculating prehash"); + #[allow(clippy::type_complexity)] + let pre_hash_results: Vec<(u64, BTreeMap>, Vec)> = non_cached_files_to_check + .par_iter() + .map(|(size, vec_file_entry)| { + let mut hashmap_with_hash: BTreeMap> = Default::default(); + let mut errors: Vec = Vec::new(); + let mut buffer = [0u8; 1024 * 2]; + + atomic_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed); + if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + check_was_stopped.store(true, Ordering::Relaxed); + return None; + } + for file_entry in vec_file_entry { + match hash_calculation(&mut buffer, file_entry, &check_type, 0) { + Ok(hash_string) => { + hashmap_with_hash.entry(hash_string.clone()).or_default().push(file_entry.clone()); + } + Err(s) => errors.push(s), + } + } + Some((*size, hashmap_with_hash, errors)) + }) + .while_some() + .collect(); + debug!("Completed calculating prehash"); + + send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); + + // Check if user aborted search(only from GUI) + if check_was_stopped.load(Ordering::Relaxed) { + return None; + } + + // Add data from cache + for (size, vec_file_entry) in &records_already_cached { + pre_checked_map.entry(*size).or_default().append(&mut vec_file_entry.clone()); + } + + // Check results + for (size, hash_map, errors) in &pre_hash_results { + if !errors.is_empty() { + self.common_data.text_messages.warnings.append(&mut errors.clone()); + } + for vec_file_entry in hash_map.values() { + if vec_file_entry.len() > 1 { + pre_checked_map.entry(*size).or_default().append(&mut vec_file_entry.clone()); + } + } + } + + self.prehash_save_cache_at_exit(loaded_hash_map, &pre_hash_results); + + Some(()) + } + + #[fun_time(message = "full_hashing_load_cache_at_start")] + fn full_hashing_load_cache_at_start( + &mut self, + mut pre_checked_map: BTreeMap>, + ) -> (BTreeMap>, BTreeMap>, BTreeMap>) { + let loaded_hash_map; + let mut records_already_cached: BTreeMap> = Default::default(); + let mut non_cached_files_to_check: BTreeMap> = Default::default(); + + if self.common_data.use_cache { + debug!("full_hashing_load_cache_at_start - using cache"); + let (messages, loaded_items) = + load_cache_from_file_generalized_by_size::(&get_duplicate_cache_file(&self.hash_type, false), self.get_delete_outdated_cache(), &pre_checked_map); + self.get_text_messages_mut().extend_with_another_messages(messages); + loaded_hash_map = loaded_items.unwrap_or_default(); + + debug!("full_hashing_load_cache_at_start - started diff between loaded and prechecked files"); + for (size, mut vec_file_entry) in pre_checked_map { + if let Some(cached_vec_file_entry) = loaded_hash_map.get(&size) { + // TODO maybe hashset is not needed when using < 4 elements + let cached_path_entries = cached_vec_file_entry.iter().map(|e| &e.path).collect::>(); + for file_entry in vec_file_entry { + if cached_path_entries.contains(&file_entry.path) { + records_already_cached.entry(size).or_default().push(file_entry); + } else { + non_cached_files_to_check.entry(size).or_default().push(file_entry); + } + } + } else { + non_cached_files_to_check.entry(size).or_default().append(&mut vec_file_entry); + } + } + + debug!( + "full_hashing_load_cache_at_start - completed diff between loaded and prechecked files - {}({}) non cached, {}({}) already cached", + non_cached_files_to_check.len(), + format_size(non_cached_files_to_check.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), + records_already_cached.len(), + format_size(records_already_cached.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), + ); + } else { + debug!("full_hashing_load_cache_at_start - not using cache"); + loaded_hash_map = Default::default(); + mem::swap(&mut pre_checked_map, &mut non_cached_files_to_check); + } + (loaded_hash_map, records_already_cached, non_cached_files_to_check) + } + + #[fun_time(message = "full_hashing_save_cache_at_exit")] + fn full_hashing_save_cache_at_exit( + &mut self, + records_already_cached: BTreeMap>, + full_hash_results: &mut Vec<(u64, BTreeMap>, Vec)>, + loaded_hash_map: BTreeMap>, + ) { + if !self.common_data.use_cache { + return; + } + 'main: for (size, vec_file_entry) in records_already_cached { + // Check if size already exists, if exists we must to change it outside because cannot have mut and non mut reference to full_hash_results + for (full_size, full_hashmap, _errors) in &mut (*full_hash_results) { + if size == *full_size { + for file_entry in vec_file_entry { + full_hashmap.entry(file_entry.hash.clone()).or_default().push(file_entry); + } + continue 'main; + } + } + // Size doesn't exists add results to files + let mut temp_hashmap: BTreeMap> = Default::default(); + for file_entry in vec_file_entry { + temp_hashmap.entry(file_entry.hash.clone()).or_default().push(file_entry); + } + full_hash_results.push((size, temp_hashmap, Vec::new())); + } + + // Must save all results to file, old loaded from file with all currently counted results + let mut all_results: BTreeMap = Default::default(); + for (_size, vec_file_entry) in loaded_hash_map { + for file_entry in vec_file_entry { + all_results.insert(file_entry.path.to_string_lossy().to_string(), file_entry); + } + } + for (_size, hashmap, _errors) in full_hash_results { + for vec_file_entry in hashmap.values() { + for file_entry in vec_file_entry { + all_results.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone()); + } + } + } + + let messages = save_cache_to_file_generalized( + &get_duplicate_cache_file(&self.hash_type, false), + &all_results, + self.common_data.save_also_as_json, + self.minimal_cache_file_size, + ); + self.get_text_messages_mut().extend_with_another_messages(messages); + } + + #[fun_time(message = "full_hashing")] + fn full_hashing( + &mut self, + stop_receiver: Option<&Receiver<()>>, + progress_sender: Option<&UnboundedSender>, + pre_checked_map: BTreeMap>, + ) -> Option<()> { + let check_type = self.hash_type; + + let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = prepare_thread_handler_common( + progress_sender, + 2, + 2, + pre_checked_map.values().map(Vec::len).sum(), + self.check_method, + self.common_data.tool_type, + ); + + ///////////////////////////////////////////////////////////////////////////// HASHING START + + let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.full_hashing_load_cache_at_start(pre_checked_map); + + debug!("Starting full hashing of {} files", non_cached_files_to_check.values().map(Vec::len).sum::()); + let mut full_hash_results: Vec<(u64, BTreeMap>, Vec)> = non_cached_files_to_check + .into_par_iter() + .map(|(size, vec_file_entry)| { + let mut hashmap_with_hash: BTreeMap> = Default::default(); + let mut errors: Vec = Vec::new(); + let mut buffer = [0u8; 1024 * 16]; + + atomic_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed); + for mut file_entry in vec_file_entry { + if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + check_was_stopped.store(true, Ordering::Relaxed); + return None; + } + + match hash_calculation(&mut buffer, &file_entry, &check_type, u64::MAX) { + Ok(hash_string) => { + file_entry.hash = hash_string.clone(); + hashmap_with_hash.entry(hash_string.clone()).or_default().push(file_entry); + } + Err(s) => errors.push(s), + } + } + Some((size, hashmap_with_hash, errors)) + }) + .while_some() + .collect(); + debug!("Finished full hashing"); + + self.full_hashing_save_cache_at_exit(records_already_cached, &mut full_hash_results, loaded_hash_map); + + send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); + + // Break if stop was clicked after saving to cache + if check_was_stopped.load(Ordering::Relaxed) { + return None; + } + + for (size, hash_map, mut errors) in full_hash_results { + self.common_data.text_messages.warnings.append(&mut errors); + for (_hash, vec_file_entry) in hash_map { + if vec_file_entry.len() > 1 { + self.files_with_identical_hashes.entry(size).or_default().push(vec_file_entry); + } + } + } + + Some(()) + } + + #[fun_time(message = "hash_reference_folders")] + fn hash_reference_folders(&mut self) { + // Reference - only use in size, because later hash will be counted differently + if self.common_data.use_reference_folders { + let vec = mem::take(&mut self.files_with_identical_hashes) + .into_iter() + .filter_map(|(_size, vec_vec_file_entry)| { + let mut all_results_with_same_size = Vec::new(); + for vec_file_entry in vec_vec_file_entry { + let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry + .into_iter() + .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); + + if files_from_referenced_folders.is_empty() || normal_files.is_empty() { + continue; + } + all_results_with_same_size.push((files_from_referenced_folders.pop().unwrap(), normal_files)); + } + if all_results_with_same_size.is_empty() { + None + } else { + Some(all_results_with_same_size) + } + }) + .collect::)>>>(); + for vec_of_vec in vec { + self.files_with_identical_hashes_referenced.insert(vec_of_vec[0].0.size, vec_of_vec); + } + } + + if self.common_data.use_reference_folders { + for (size, vector_vectors) in &self.files_with_identical_hashes_referenced { + for (_fe, vector) in vector_vectors { + self.information.number_of_duplicated_files_by_hash += vector.len(); + self.information.number_of_groups_by_hash += 1; + self.information.lost_space_by_hash += (vector.len() as u64) * size; + } + } + } else { + for (size, vector_vectors) in &self.files_with_identical_hashes { + for vector in vector_vectors { + self.information.number_of_duplicated_files_by_hash += vector.len() - 1; + self.information.number_of_groups_by_hash += 1; + self.information.lost_space_by_hash += (vector.len() as u64 - 1) * size; + } + } + } + } + + #[fun_time(message = "check_files_hash")] + fn check_files_hash(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { + assert_eq!(self.check_method, CheckingMethod::Hash); + + let mut pre_checked_map: BTreeMap> = Default::default(); + let ret = self.prehashing(stop_receiver, progress_sender, &mut pre_checked_map); + if ret.is_none() { + return false; + } + + let ret = self.full_hashing(stop_receiver, progress_sender, pre_checked_map); + if ret.is_none() { + return false; + } + + self.hash_reference_folders(); + + // Clean unused data + self.files_with_identical_size = Default::default(); + + true + } + + #[fun_time(message = "delete_files")] + fn delete_files(&mut self) { + if self.common_data.delete_method == DeleteMethod::None { + return; + } + + match self.check_method { + CheckingMethod::Name => { + for vector in self.files_with_identical_names.values() { + let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); + } + } + CheckingMethod::SizeName => { + for vector in self.files_with_identical_size_names.values() { + let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); + } + } + CheckingMethod::Hash => { + for vector_vectors in self.files_with_identical_hashes.values() { + for vector in vector_vectors { + let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); + } + } + } + CheckingMethod::Size => { + for vector in self.files_with_identical_size.values() { + let _tuple: (u64, usize, usize) = delete_files(vector, &self.common_data.delete_method, &mut self.common_data.text_messages, self.dryrun); + } + } + _ => panic!(), + } + } +} + +impl DuplicateFinder { pub fn set_case_sensitive_name_comparison(&mut self, case_sensitive_name_comparison: bool) { self.case_sensitive_name_comparison = case_sensitive_name_comparison; } @@ -226,10 +896,6 @@ impl DuplicateFinder { self.check_method = check_method; } - pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { - self.delete_method = delete_method; - } - pub fn get_use_reference(&self) -> bool { self.common_data.use_reference_folders } @@ -249,726 +915,6 @@ impl DuplicateFinder { pub fn get_files_with_identical_size_names_referenced(&self) -> &BTreeMap<(u64, String), (FileEntry, Vec)> { &self.files_with_identical_size_names_referenced } - - fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_files_name - starting checking for same names"); - let group_by_func = if self.case_sensitive_name_comparison { - |fe: &FileEntry| fe.path.file_name().unwrap().to_string_lossy().to_string() - } else { - |fe: &FileEntry| fe.path.file_name().unwrap().to_string_lossy().to_lowercase() - }; - - let result = DirTraversalBuilder::new() - .root_dirs(self.common_data.directories.included_directories.clone()) - .group_by(group_by_func) - .stop_receiver(stop_receiver) - .progress_sender(progress_sender) - .checking_method(CheckingMethod::Name) - .directories(self.common_data.directories.clone()) - .allowed_extensions(self.common_data.allowed_extensions.clone()) - .excluded_items(self.common_data.excluded_items.clone()) - .recursive_search(self.common_data.recursive_search) - .minimal_file_size(self.common_data.minimal_file_size) - .maximal_file_size(self.common_data.maximal_file_size) - .build() - .run(); - debug!("check_files_name - after finding file sizes"); - let res = match result { - DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { - self.files_with_identical_names = grouped_file_entries; - self.common_data.text_messages.warnings.extend(warnings); - - // Create new BTreeMap without single size entries(files have not duplicates) - let mut new_map: BTreeMap> = Default::default(); - - for (name, vector) in &self.files_with_identical_names { - if vector.len() > 1 { - new_map.insert(name.clone(), vector.clone()); - } - } - self.files_with_identical_names = new_map; - - // Reference - only use in size, because later hash will be counted differently - if self.common_data.use_reference_folders { - let vec = mem::take(&mut self.files_with_identical_names) - .into_iter() - .filter_map(|(_name, vec_file_entry)| { - let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry - .into_iter() - .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); - - if files_from_referenced_folders.is_empty() || normal_files.is_empty() { - None - } else { - Some((files_from_referenced_folders.pop().unwrap(), normal_files)) - } - }) - .collect::)>>(); - for (fe, vec_fe) in vec { - self.files_with_identical_names_referenced.insert(fe.path.to_string_lossy().to_string(), (fe, vec_fe)); - } - } - self.calculate_name_stats(); - - true - } - DirTraversalResult::SuccessFolders { .. } => { - unreachable!() - } - DirTraversalResult::Stopped => false, - }; - debug!("check_files_name - finished checking for same names"); - res - } - - fn calculate_name_stats(&mut self) { - if self.common_data.use_reference_folders { - for (_fe, vector) in self.files_with_identical_names_referenced.values() { - self.information.number_of_duplicated_files_by_name += vector.len(); - self.information.number_of_groups_by_name += 1; - } - } else { - for vector in self.files_with_identical_names.values() { - self.information.number_of_duplicated_files_by_name += vector.len() - 1; - self.information.number_of_groups_by_name += 1; - } - } - } - - fn check_files_size_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_files_size_name - starting checking for same size and name"); - let group_by_func = if self.case_sensitive_name_comparison { - |fe: &FileEntry| (fe.size, fe.path.file_name().unwrap().to_string_lossy().to_string()) - } else { - |fe: &FileEntry| (fe.size, fe.path.file_name().unwrap().to_string_lossy().to_lowercase()) - }; - - let result = DirTraversalBuilder::new() - .root_dirs(self.common_data.directories.included_directories.clone()) - .group_by(group_by_func) - .stop_receiver(stop_receiver) - .progress_sender(progress_sender) - .checking_method(CheckingMethod::Name) - .directories(self.common_data.directories.clone()) - .allowed_extensions(self.common_data.allowed_extensions.clone()) - .excluded_items(self.common_data.excluded_items.clone()) - .recursive_search(self.common_data.recursive_search) - .minimal_file_size(self.common_data.minimal_file_size) - .maximal_file_size(self.common_data.maximal_file_size) - .build() - .run(); - debug!("check_files_size_name - after finding file sizes"); - let res = match result { - DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { - self.files_with_identical_size_names = grouped_file_entries; - self.common_data.text_messages.warnings.extend(warnings); - - // Create new BTreeMap without single size entries(files have not duplicates) - let mut new_map: BTreeMap<(u64, String), Vec> = Default::default(); - - for (name_size, vector) in &self.files_with_identical_size_names { - if vector.len() > 1 { - new_map.insert(name_size.clone(), vector.clone()); - } - } - self.files_with_identical_size_names = new_map; - - // Reference - only use in size, because later hash will be counted differently - if self.common_data.use_reference_folders { - let vec = mem::take(&mut self.files_with_identical_size_names) - .into_iter() - .filter_map(|(_size, vec_file_entry)| { - let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry - .into_iter() - .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); - - if files_from_referenced_folders.is_empty() || normal_files.is_empty() { - None - } else { - Some((files_from_referenced_folders.pop().unwrap(), normal_files)) - } - }) - .collect::)>>(); - for (fe, vec_fe) in vec { - self.files_with_identical_size_names_referenced - .insert((fe.size, fe.path.to_string_lossy().to_string()), (fe, vec_fe)); - } - } - self.calculate_size_name_stats(); - - true - } - DirTraversalResult::SuccessFolders { .. } => { - unreachable!() - } - DirTraversalResult::Stopped => false, - }; - debug!("check_files_size_name - finished checking for same size and name"); - res - } - - fn calculate_size_name_stats(&mut self) { - if self.common_data.use_reference_folders { - for ((size, _name), (_fe, vector)) in &self.files_with_identical_size_names_referenced { - self.information.number_of_duplicated_files_by_size_name += vector.len(); - self.information.number_of_groups_by_size_name += 1; - self.information.lost_space_by_size += (vector.len() as u64) * size; - } - } else { - for ((size, _name), vector) in &self.files_with_identical_size_names { - self.information.number_of_duplicated_files_by_size_name += vector.len() - 1; - self.information.number_of_groups_by_size_name += 1; - self.information.lost_space_by_size += (vector.len() as u64 - 1) * size; - } - } - } - - /// Read file length and puts it to different boxes(each for different lengths) - /// If in box is only 1 result, then it is removed - fn check_files_size(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_file_size - start"); - let max_stage = match self.check_method { - CheckingMethod::Size => 0, - CheckingMethod::Hash => 2, - _ => panic!(), - }; - let result = DirTraversalBuilder::new() - .root_dirs(self.common_data.directories.included_directories.clone()) - .group_by(|fe| fe.size) - .stop_receiver(stop_receiver) - .progress_sender(progress_sender) - .checking_method(self.check_method) - .max_stage(max_stage) - .directories(self.common_data.directories.clone()) - .allowed_extensions(self.common_data.allowed_extensions.clone()) - .excluded_items(self.common_data.excluded_items.clone()) - .recursive_search(self.common_data.recursive_search) - .minimal_file_size(self.common_data.minimal_file_size) - .maximal_file_size(self.common_data.maximal_file_size) - .build() - .run(); - debug!("check_file_size - after finding file sizes"); - let res = match result { - DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { - self.files_with_identical_size = grouped_file_entries; - self.common_data.text_messages.warnings.extend(warnings); - - // Create new BTreeMap without single size entries(files have not duplicates) - let old_map: BTreeMap> = mem::take(&mut self.files_with_identical_size); - - for (size, vec) in old_map { - if vec.len() <= 1 { - continue; - } - - let vector = if self.ignore_hard_links { filter_hard_links(&vec) } else { vec }; - - if vector.len() > 1 { - self.files_with_identical_size.insert(size, vector); - } - } - - self.filter_reference_folders_by_size(); - self.calculate_size_stats(); - - true - } - DirTraversalResult::SuccessFolders { .. } => { - unreachable!() - } - DirTraversalResult::Stopped => false, - }; - debug!( - "check_file_size - after calculating size stats/duplicates, found in {} groups, {} files with same size | referenced {} groups, {} files", - self.files_with_identical_size.len(), - self.files_with_identical_size.values().map(Vec::len).sum::(), - self.files_with_identical_size_referenced.len(), - self.files_with_identical_size_referenced.values().map(|(_fe, vec)| vec.len()).sum::() - ); - res - } - - fn calculate_size_stats(&mut self) { - if self.common_data.use_reference_folders { - for (size, (_fe, vector)) in &self.files_with_identical_size_referenced { - self.information.number_of_duplicated_files_by_size += vector.len(); - self.information.number_of_groups_by_size += 1; - self.information.lost_space_by_size += (vector.len() as u64) * size; - } - } else { - for (size, vector) in &self.files_with_identical_size { - self.information.number_of_duplicated_files_by_size += vector.len() - 1; - self.information.number_of_groups_by_size += 1; - self.information.lost_space_by_size += (vector.len() as u64 - 1) * size; - } - } - } - - /// This step check for references, only when checking for size. - /// This is needed, because later reference folders looks for hashes, not size - fn filter_reference_folders_by_size(&mut self) { - if self.common_data.use_reference_folders && self.check_method == CheckingMethod::Size { - let vec = mem::take(&mut self.files_with_identical_size) - .into_iter() - .filter_map(|(_size, vec_file_entry)| { - let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry - .into_iter() - .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); - - if files_from_referenced_folders.is_empty() || normal_files.is_empty() { - None - } else { - Some((files_from_referenced_folders.pop().unwrap(), normal_files)) - } - }) - .collect::)>>(); - for (fe, vec_fe) in vec { - self.files_with_identical_size_referenced.insert(fe.size, (fe, vec_fe)); - } - } - } - - fn prehash_load_cache_at_start(&mut self) -> (BTreeMap>, BTreeMap>, BTreeMap>) { - // Cache algorithm - // - Load data from cache - // - Convert from BT> to BT - // - Save to proper values - let loaded_hash_map; - let mut records_already_cached: BTreeMap> = Default::default(); - let mut non_cached_files_to_check: BTreeMap> = Default::default(); - - if self.use_prehash_cache { - debug!("prehash_load_cache_at_start - using prehash cache start"); - - let (messages, loaded_items) = load_cache_from_file_generalized_by_size::( - &get_duplicate_cache_file(&self.hash_type, true), - self.get_delete_outdated_cache(), - &self.files_with_identical_size, - ); - self.get_text_messages_mut().extend_with_another_messages(messages); - loaded_hash_map = loaded_items.unwrap_or_default(); - - debug!("prehash_load_cache_at_start - started diff between loaded and prechecked files"); - for (size, mut vec_file_entry) in mem::take(&mut self.files_with_identical_size) { - if let Some(cached_vec_file_entry) = loaded_hash_map.get(&size) { - // TODO maybe hashset is not needed when using < 4 elements - let cached_path_entries = cached_vec_file_entry.iter().map(|e| &e.path).collect::>(); - for file_entry in vec_file_entry { - if cached_path_entries.contains(&file_entry.path) { - records_already_cached.entry(size).or_default().push(file_entry); - } else { - non_cached_files_to_check.entry(size).or_default().push(file_entry); - } - } - } else { - non_cached_files_to_check.entry(size).or_default().append(&mut vec_file_entry); - } - } - - debug!( - "prehash_load_cache_at_start - completed diff between loaded and prechecked files, {}({}) - non cached, {}({}) - already cached", - non_cached_files_to_check.values().map(Vec::len).sum::(), - format_size(non_cached_files_to_check.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), - records_already_cached.values().map(Vec::len).sum::(), - format_size(records_already_cached.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), - ); - } else { - debug!("prehash_load_cache_at_start - not using prehash cache start"); - loaded_hash_map = Default::default(); - mem::swap(&mut self.files_with_identical_size, &mut non_cached_files_to_check); - } - debug!("prehash_load_cache_at_start - end"); - (loaded_hash_map, records_already_cached, non_cached_files_to_check) - } - - fn prehash_save_cache_at_exit(&mut self, loaded_hash_map: BTreeMap>, pre_hash_results: &Vec<(u64, BTreeMap>, Vec)>) { - debug!("prehash_save_cache_at_exit - start - using prehash cache {}", self.use_prehash_cache); - if self.use_prehash_cache { - // All results = records already cached + computed results - let mut save_cache_to_hashmap: BTreeMap = Default::default(); - - for (size, vec_file_entry) in loaded_hash_map { - if size >= self.minimal_prehash_cache_file_size { - for file_entry in vec_file_entry { - save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone()); - } - } - } - - for (size, hash_map, _errors) in pre_hash_results { - if *size >= self.minimal_prehash_cache_file_size { - for vec_file_entry in hash_map.values() { - for file_entry in vec_file_entry { - save_cache_to_hashmap.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone()); - } - } - } - } - - let messages = save_cache_to_file_generalized( - &get_duplicate_cache_file(&self.hash_type, true), - &save_cache_to_hashmap, - self.common_data.save_also_as_json, - self.minimal_prehash_cache_file_size, - ); - self.get_text_messages_mut().extend_with_another_messages(messages); - - debug!("prehash_save_cache_at_exit - saving prehash cache end"); - } - } - - fn prehashing( - &mut self, - stop_receiver: Option<&Receiver<()>>, - progress_sender: Option<&UnboundedSender>, - pre_checked_map: &mut BTreeMap>, - ) -> Option<()> { - debug!("prehashing - start"); - let check_type = self.hash_type; - let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = prepare_thread_handler_common( - progress_sender, - 1, - 2, - self.files_with_identical_size.values().map(Vec::len).sum(), - self.check_method, - self.common_data.tool_type, - ); - - let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.prehash_load_cache_at_start(); - - #[allow(clippy::type_complexity)] - let pre_hash_results: Vec<(u64, BTreeMap>, Vec)> = non_cached_files_to_check - .par_iter() - .map(|(size, vec_file_entry)| { - let mut hashmap_with_hash: BTreeMap> = Default::default(); - let mut errors: Vec = Vec::new(); - let mut buffer = [0u8; 1024 * 2]; - - atomic_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed); - if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { - check_was_stopped.store(true, Ordering::Relaxed); - return None; - } - for file_entry in vec_file_entry { - match hash_calculation(&mut buffer, file_entry, &check_type, 0) { - Ok(hash_string) => { - hashmap_with_hash.entry(hash_string.clone()).or_default().push(file_entry.clone()); - } - Err(s) => errors.push(s), - } - } - Some((*size, hashmap_with_hash, errors)) - }) - .while_some() - .collect(); - - send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); - - // Check if user aborted search(only from GUI) - if check_was_stopped.load(Ordering::Relaxed) { - return None; - } - - // Add data from cache - for (size, vec_file_entry) in &records_already_cached { - pre_checked_map.entry(*size).or_default().append(&mut vec_file_entry.clone()); - } - - // Check results - for (size, hash_map, errors) in &pre_hash_results { - self.common_data.text_messages.warnings.append(&mut errors.clone()); - for vec_file_entry in hash_map.values() { - if vec_file_entry.len() > 1 { - pre_checked_map.entry(*size).or_default().append(&mut vec_file_entry.clone()); - } - } - } - - self.prehash_save_cache_at_exit(loaded_hash_map, &pre_hash_results); - - debug!("prehashing - end"); - Some(()) - } - - fn full_hashing_load_cache_at_start( - &mut self, - mut pre_checked_map: BTreeMap>, - ) -> (BTreeMap>, BTreeMap>, BTreeMap>) { - debug!("full_hashing_load_cache_at_start - start"); - let loaded_hash_map; - let mut records_already_cached: BTreeMap> = Default::default(); - let mut non_cached_files_to_check: BTreeMap> = Default::default(); - - if self.common_data.use_cache { - debug!("full_hashing_load_cache_at_start - using cache"); - let (messages, loaded_items) = - load_cache_from_file_generalized_by_size::(&get_duplicate_cache_file(&self.hash_type, false), self.get_delete_outdated_cache(), &pre_checked_map); - self.get_text_messages_mut().extend_with_another_messages(messages); - loaded_hash_map = loaded_items.unwrap_or_default(); - - debug!("full_hashing_load_cache_at_start - started diff between loaded and prechecked files"); - for (size, mut vec_file_entry) in pre_checked_map { - if let Some(cached_vec_file_entry) = loaded_hash_map.get(&size) { - // TODO maybe hashset is not needed when using < 4 elements - let cached_path_entries = cached_vec_file_entry.iter().map(|e| &e.path).collect::>(); - for file_entry in vec_file_entry { - if cached_path_entries.contains(&file_entry.path) { - records_already_cached.entry(size).or_default().push(file_entry); - } else { - non_cached_files_to_check.entry(size).or_default().push(file_entry); - } - } - } else { - non_cached_files_to_check.entry(size).or_default().append(&mut vec_file_entry); - } - } - - debug!( - "full_hashing_load_cache_at_start - completed diff between loaded and prechecked files - {}({}) non cached, {}({}) already cached", - non_cached_files_to_check.len(), - format_size(non_cached_files_to_check.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), - records_already_cached.len(), - format_size(records_already_cached.values().map(|v| v.iter().map(|e| e.size).sum::()).sum::(), BINARY), - ); - } else { - debug!("full_hashing_load_cache_at_start - not using cache"); - loaded_hash_map = Default::default(); - mem::swap(&mut pre_checked_map, &mut non_cached_files_to_check); - } - debug!("full_hashing_load_cache_at_start - end"); - (loaded_hash_map, records_already_cached, non_cached_files_to_check) - } - - fn full_hashing_save_cache_at_exit( - &mut self, - records_already_cached: BTreeMap>, - full_hash_results: &mut Vec<(u64, BTreeMap>, Vec)>, - loaded_hash_map: BTreeMap>, - ) { - debug!("full_hashing_save_cache_at_exit - start"); - if !self.common_data.use_cache { - return; - } - 'main: for (size, vec_file_entry) in records_already_cached { - // Check if size already exists, if exists we must to change it outside because cannot have mut and non mut reference to full_hash_results - for (full_size, full_hashmap, _errors) in &mut (*full_hash_results) { - if size == *full_size { - for file_entry in vec_file_entry { - full_hashmap.entry(file_entry.hash.clone()).or_default().push(file_entry); - } - continue 'main; - } - } - // Size doesn't exists add results to files - let mut temp_hashmap: BTreeMap> = Default::default(); - for file_entry in vec_file_entry { - temp_hashmap.entry(file_entry.hash.clone()).or_default().push(file_entry); - } - full_hash_results.push((size, temp_hashmap, Vec::new())); - } - - // Must save all results to file, old loaded from file with all currently counted results - let mut all_results: BTreeMap = Default::default(); - for (_size, vec_file_entry) in loaded_hash_map { - for file_entry in vec_file_entry { - all_results.insert(file_entry.path.to_string_lossy().to_string(), file_entry); - } - } - for (_size, hashmap, _errors) in full_hash_results { - for vec_file_entry in hashmap.values() { - for file_entry in vec_file_entry { - all_results.insert(file_entry.path.to_string_lossy().to_string(), file_entry.clone()); - } - } - } - - let messages = save_cache_to_file_generalized( - &get_duplicate_cache_file(&self.hash_type, false), - &all_results, - self.common_data.save_also_as_json, - self.minimal_cache_file_size, - ); - self.get_text_messages_mut().extend_with_another_messages(messages); - - debug!("full_hashing_save_cache_at_exit - end"); - } - - fn full_hashing( - &mut self, - stop_receiver: Option<&Receiver<()>>, - progress_sender: Option<&UnboundedSender>, - pre_checked_map: BTreeMap>, - ) -> Option<()> { - debug!("full_hashing - start"); - let check_type = self.hash_type; - - let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = prepare_thread_handler_common( - progress_sender, - 2, - 2, - pre_checked_map.values().map(Vec::len).sum(), - self.check_method, - self.common_data.tool_type, - ); - - ///////////////////////////////////////////////////////////////////////////// HASHING START - { - let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.full_hashing_load_cache_at_start(pre_checked_map); - - let mut full_hash_results: Vec<(u64, BTreeMap>, Vec)> = non_cached_files_to_check - .into_par_iter() - .map(|(size, vec_file_entry)| { - let mut hashmap_with_hash: BTreeMap> = Default::default(); - let mut errors: Vec = Vec::new(); - let mut buffer = [0u8; 1024 * 16]; - - atomic_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed); - for mut file_entry in vec_file_entry { - if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { - check_was_stopped.store(true, Ordering::Relaxed); - return None; - } - - match hash_calculation(&mut buffer, &file_entry, &check_type, u64::MAX) { - Ok(hash_string) => { - file_entry.hash = hash_string.clone(); - hashmap_with_hash.entry(hash_string.clone()).or_default().push(file_entry); - } - Err(s) => errors.push(s), - } - } - Some((size, hashmap_with_hash, errors)) - }) - .while_some() - .collect(); - - self.full_hashing_save_cache_at_exit(records_already_cached, &mut full_hash_results, loaded_hash_map); - - send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); - - // Break if stop was clicked after saving to cache - if check_was_stopped.load(Ordering::Relaxed) { - return None; - } - - for (size, hash_map, mut errors) in full_hash_results { - self.common_data.text_messages.warnings.append(&mut errors); - for (_hash, vec_file_entry) in hash_map { - if vec_file_entry.len() > 1 { - self.files_with_identical_hashes.entry(size).or_default().push(vec_file_entry); - } - } - } - } - - debug!("full_hashing - end"); - Some(()) - } - - fn hash_reference_folders(&mut self) { - // Reference - only use in size, because later hash will be counted differently - if self.common_data.use_reference_folders { - let vec = mem::take(&mut self.files_with_identical_hashes) - .into_iter() - .filter_map(|(_size, vec_vec_file_entry)| { - let mut all_results_with_same_size = Vec::new(); - for vec_file_entry in vec_vec_file_entry { - let (mut files_from_referenced_folders, normal_files): (Vec<_>, Vec<_>) = vec_file_entry - .into_iter() - .partition(|e| self.common_data.directories.is_in_referenced_directory(e.get_path())); - - if files_from_referenced_folders.is_empty() || normal_files.is_empty() { - continue; - } - all_results_with_same_size.push((files_from_referenced_folders.pop().unwrap(), normal_files)); - } - if all_results_with_same_size.is_empty() { - None - } else { - Some(all_results_with_same_size) - } - }) - .collect::)>>>(); - for vec_of_vec in vec { - self.files_with_identical_hashes_referenced.insert(vec_of_vec[0].0.size, vec_of_vec); - } - } - - if self.common_data.use_reference_folders { - for (size, vector_vectors) in &self.files_with_identical_hashes_referenced { - for (_fe, vector) in vector_vectors { - self.information.number_of_duplicated_files_by_hash += vector.len(); - self.information.number_of_groups_by_hash += 1; - self.information.lost_space_by_hash += (vector.len() as u64) * size; - } - } - } else { - for (size, vector_vectors) in &self.files_with_identical_hashes { - for vector in vector_vectors { - self.information.number_of_duplicated_files_by_hash += vector.len() - 1; - self.information.number_of_groups_by_hash += 1; - self.information.lost_space_by_hash += (vector.len() as u64 - 1) * size; - } - } - } - } - - /// The slowest checking type, which must be applied after checking for size - fn check_files_hash(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - assert_eq!(self.check_method, CheckingMethod::Hash); - - let mut pre_checked_map: BTreeMap> = Default::default(); - let ret = self.prehashing(stop_receiver, progress_sender, &mut pre_checked_map); - if ret.is_none() { - return false; - } - - let ret = self.full_hashing(stop_receiver, progress_sender, pre_checked_map); - if ret.is_none() { - return false; - } - - self.hash_reference_folders(); - - // Clean unused data - self.files_with_identical_size = Default::default(); - - true - } - - /// Function to delete files, from filed before `BTreeMap` - /// Using another function to delete files to avoid duplicates data - fn delete_files(&mut self) { - if self.delete_method == DeleteMethod::None { - return; - } - - match self.check_method { - CheckingMethod::Name => { - for vector in self.files_with_identical_names.values() { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.common_data.text_messages, self.dryrun); - } - } - CheckingMethod::SizeName => { - for vector in self.files_with_identical_size_names.values() { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.common_data.text_messages, self.dryrun); - } - } - CheckingMethod::Hash => { - for vector_vectors in self.files_with_identical_hashes.values() { - for vector in vector_vectors { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.common_data.text_messages, self.dryrun); - } - } - } - CheckingMethod::Size => { - for vector in self.files_with_identical_size.values() { - let _tuple: (u64, usize, usize) = delete_files(vector, &self.delete_method, &mut self.common_data.text_messages, self.dryrun); - } - } - _ => panic!(), - } - } } impl Default for DuplicateFinder { @@ -978,12 +924,8 @@ impl Default for DuplicateFinder { } impl DebugPrint for DuplicateFinder { - #[allow(dead_code)] - #[allow(unreachable_code)] - /// Debugging printing - only available on debug build fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } println!("---------------DEBUG PRINT---------------"); @@ -1015,62 +957,40 @@ impl DebugPrint for DuplicateFinder { println!("Files list size - {}", self.files_with_identical_size.len()); println!("Hashed Files list size - {}", self.files_with_identical_hashes.len()); println!("Checking Method - {:?}", self.check_method); - println!("Delete Method - {:?}", self.delete_method); self.debug_print_common(); println!("-----------------------------------------"); } } -impl SaveResults for DuplicateFinder { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( +impl PrintResults for DuplicateFinder { + fn write_results(&self, writer: &mut T) -> io::Result<()> { + writeln!( writer, "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } + )?; + match self.check_method { CheckingMethod::Name => { if !self.files_with_identical_names.is_empty() { writeln!( writer, "-------------------------------------------------Files with same names-------------------------------------------------" - ) - .unwrap(); + )?; writeln!( writer, "Found {} files in {} groups with same name(may have different content)", self.information.number_of_duplicated_files_by_name, self.information.number_of_groups_by_name, - ) - .unwrap(); + )?; for (name, vector) in self.files_with_identical_names.iter().rev() { - writeln!(writer, "Name - {} - {} files ", name, vector.len()).unwrap(); + writeln!(writer, "Name - {} - {} files ", name, vector.len())?; for j in vector { - writeln!(writer, "{}", j.path.display()).unwrap(); + writeln!(writer, "{}", j.path.display())?; } - writeln!(writer).unwrap(); + writeln!(writer)?; } } else { - write!(writer, "Not found any files with same names.").unwrap(); + write!(writer, "Not found any files with same names.")?; } } CheckingMethod::SizeName => { @@ -1078,23 +998,21 @@ impl SaveResults for DuplicateFinder { writeln!( writer, "-------------------------------------------------Files with same size and names-------------------------------------------------" - ) - .unwrap(); + )?; writeln!( writer, "Found {} files in {} groups with same size and name(may have different content)", self.information.number_of_duplicated_files_by_size_name, self.information.number_of_groups_by_size_name, - ) - .unwrap(); + )?; for ((size, name), vector) in self.files_with_identical_size_names.iter().rev() { - writeln!(writer, "Name - {}, {} - {} files ", name, format_size(*size, BINARY), vector.len()).unwrap(); + writeln!(writer, "Name - {}, {} - {} files ", name, format_size(*size, BINARY), vector.len())?; for j in vector { - writeln!(writer, "{}", j.path.display()).unwrap(); + writeln!(writer, "{}", j.path.display())?; } - writeln!(writer).unwrap(); + writeln!(writer)?; } } else { - write!(writer, "Not found any files with same size and names.").unwrap(); + write!(writer, "Not found any files with same size and names.")?; } } CheckingMethod::Size => { @@ -1102,24 +1020,22 @@ impl SaveResults for DuplicateFinder { writeln!( writer, "-------------------------------------------------Files with same size-------------------------------------------------" - ) - .unwrap(); + )?; writeln!( writer, "Found {} duplicated files which in {} groups which takes {}.", self.information.number_of_duplicated_files_by_size, self.information.number_of_groups_by_size, format_size(self.information.lost_space_by_size, BINARY) - ) - .unwrap(); + )?; for (size, vector) in self.files_with_identical_size.iter().rev() { - write!(writer, "\n---- Size {} ({}) - {} files \n", format_size(*size, BINARY), size, vector.len()).unwrap(); + write!(writer, "\n---- Size {} ({}) - {} files \n", format_size(*size, BINARY), size, vector.len())?; for file_entry in vector { - writeln!(writer, "{}", file_entry.path.display()).unwrap(); + writeln!(writer, "{}", file_entry.path.display())?; } } } else { - write!(writer, "Not found any duplicates.").unwrap(); + write!(writer, "Not found any duplicates.")?; } } CheckingMethod::Hash => { @@ -1127,121 +1043,33 @@ impl SaveResults for DuplicateFinder { writeln!( writer, "-------------------------------------------------Files with same hashes-------------------------------------------------" - ) - .unwrap(); + )?; writeln!( writer, "Found {} duplicated files which in {} groups which takes {}.", self.information.number_of_duplicated_files_by_hash, self.information.number_of_groups_by_hash, format_size(self.information.lost_space_by_hash, BINARY) - ) - .unwrap(); + )?; for (size, vectors_vector) in self.files_with_identical_hashes.iter().rev() { for vector in vectors_vector { - writeln!(writer, "\n---- Size {} ({}) - {} files", format_size(*size, BINARY), size, vector.len()).unwrap(); + writeln!(writer, "\n---- Size {} ({}) - {} files", format_size(*size, BINARY), size, vector.len())?; for file_entry in vector { - writeln!(writer, "{}", file_entry.path.display()).unwrap(); + writeln!(writer, "{}", file_entry.path.display())?; } } } } else { - write!(writer, "Not found any duplicates.").unwrap(); + write!(writer, "Not found any duplicates.")?; } } _ => panic!(), } - true + Ok(()) } } -impl PrintResults for DuplicateFinder { - /// Print information's about duplicated entries - /// Only needed for CLI - fn print_results(&self) { - let mut number_of_files: u64 = 0; - let mut number_of_groups: u64 = 0; - - match self.check_method { - CheckingMethod::Name => { - for i in &self.files_with_identical_names { - number_of_files += i.1.len() as u64; - number_of_groups += 1; - } - println!("Found {number_of_files} files in {number_of_groups} groups with same name(may have different content)",); - for (name, vector) in &self.files_with_identical_names { - println!("Name - {} - {} files ", name, vector.len()); - for j in vector { - println!("{}", j.path.display()); - } - println!(); - } - } - CheckingMethod::SizeName => { - for i in &self.files_with_identical_size_names { - number_of_files += i.1.len() as u64; - number_of_groups += 1; - } - println!("Found {number_of_files} files in {number_of_groups} groups with same size and name(may have different content)",); - for ((size, name), vector) in &self.files_with_identical_size_names { - println!("Name - {}, {} - {} files ", name, format_size(*size, BINARY), vector.len()); - for j in vector { - println!("{}", j.path.display()); - } - println!(); - } - } - CheckingMethod::Hash => { - for vector in self.files_with_identical_hashes.values() { - for j in vector { - number_of_files += j.len() as u64; - number_of_groups += 1; - } - } - println!( - "Found {} duplicated files in {} groups with same content which took {}:", - number_of_files, - number_of_groups, - format_size(self.information.lost_space_by_size, BINARY) - ); - for (size, vector) in self.files_with_identical_hashes.iter().rev() { - for j in vector { - println!("Size - {} ({}) - {} files ", format_size(*size, BINARY), size, j.len()); - for k in j { - println!("{}", k.path.display()); - } - println!("----"); - } - println!(); - } - } - CheckingMethod::Size => { - for i in &self.files_with_identical_size { - number_of_files += i.1.len() as u64; - number_of_groups += 1; - } - println!( - "Found {} files in {} groups with same size(may have different content) which took {}:", - number_of_files, - number_of_groups, - format_size(self.information.lost_space_by_size, BINARY) - ); - for (size, vector) in &self.files_with_identical_size { - println!("Size - {} ({}) - {} files ", format_size(*size, BINARY), size, vector.len()); - for j in vector { - println!("{}", j.path.display()); - } - println!(); - } - } - _ => panic!(), - } - } -} - -/// Functions to remove slice(vector) of files with provided method -/// Returns size of removed elements, number of deleted and failed to delete files and modified warning list fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, text_messages: &mut Messages, dryrun: bool) -> (u64, usize, usize) { assert!(vector.len() > 1, "Vector length must be bigger than 1(This should be done in previous steps)."); let mut gained_space: u64 = 0; @@ -1252,11 +1080,13 @@ fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, text_message DeleteMethod::OneOldest | DeleteMethod::AllExceptNewest => values.max_by(|(_, l), (_, r)| l.modified_date.cmp(&r.modified_date)), DeleteMethod::OneNewest | DeleteMethod::AllExceptOldest | DeleteMethod::HardLink => values.min_by(|(_, l), (_, r)| l.modified_date.cmp(&r.modified_date)), DeleteMethod::None => values.next(), + _ => unreachable!(), }; let q_index = q_index.map_or(0, |t| t.0); let n = match delete_method { DeleteMethod::OneNewest | DeleteMethod::OneOldest => 1, DeleteMethod::AllExceptNewest | DeleteMethod::AllExceptOldest | DeleteMethod::None | DeleteMethod::HardLink => usize::MAX, + _ => unreachable!(), }; for (index, file) in vector.iter().enumerate() { if q_index == index { @@ -1283,6 +1113,7 @@ fn delete_files(vector: &[FileEntry], delete_method: &DeleteMethod, text_message } } DeleteMethod::None => Ok(None), + _ => unreachable!(), }; match r { diff --git a/czkawka_core/src/empty_files.rs b/czkawka_core/src/empty_files.rs index cc335d7..f71e053 100644 --- a/czkawka_core/src/empty_files.rs +++ b/czkawka_core/src/empty_files.rs @@ -1,34 +1,25 @@ use std::fs; -use std::fs::File; + use std::io::prelude::*; -use std::io::BufWriter; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; -use log::{debug, info}; +use log::debug; use crate::common_dir_traversal::{DirTraversalBuilder, DirTraversalResult, FileEntry, ProgressData, ToolType}; -use crate::common_tool::{CommonData, CommonToolData}; +use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; use crate::common_traits::*; -#[derive(Eq, PartialEq, Clone, Debug)] -pub enum DeleteMethod { - None, - Delete, -} - -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_empty_files: usize, } -/// Struct with required information's to work pub struct EmptyFiles { common_data: CommonToolData, information: Info, empty_files: Vec, - delete_method: DeleteMethod, } impl CommonData for EmptyFiles { @@ -46,18 +37,11 @@ impl EmptyFiles { common_data: CommonToolData::new(ToolType::EmptyFiles), information: Info::default(), empty_files: vec![], - delete_method: DeleteMethod::None, } } + #[fun_time(message = "find_empty_files")] pub fn find_empty_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding empty files"); - let start_time = std::time::Instant::now(); - self.find_empty_files_internal(stop_receiver, progress_sender); - info!("Ended finding empty files which took {:?}", start_time.elapsed()); - } - - fn find_empty_files_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); if !self.check_files(stop_receiver, progress_sender) { self.common_data.stopped_search = true; @@ -67,9 +51,8 @@ impl EmptyFiles { self.debug_print(); } - /// Check files for any with size == 0 + #[fun_time(message = "check_files")] fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_files - start"); let result = DirTraversalBuilder::new() .root_dirs(self.common_data.directories.included_directories.clone()) .group_by(|_fe| ()) @@ -83,29 +66,27 @@ impl EmptyFiles { .recursive_search(self.common_data.recursive_search) .build() .run(); - debug!("check_files - collected files to check"); - let res = match result { + + match result { DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { - if let Some(empty_files) = grouped_file_entries.get(&()) { - self.empty_files = empty_files.clone(); - } + self.empty_files = grouped_file_entries.into_values().flatten().collect(); self.information.number_of_empty_files = self.empty_files.len(); self.common_data.text_messages.warnings.extend(warnings); + debug!("Found {} empty files.", self.information.number_of_empty_files); + true } DirTraversalResult::SuccessFolders { .. } => { unreachable!() } DirTraversalResult::Stopped => false, - }; - debug!("check_files - end"); - res + } } - /// Function to delete files, from filed Vector + #[fun_time(message = "delete_files")] fn delete_files(&mut self) { - match self.delete_method { + match self.common_data.delete_method { DeleteMethod::Delete => { for file_entry in &self.empty_files { if fs::remove_file(file_entry.path.clone()).is_err() { @@ -116,6 +97,9 @@ impl EmptyFiles { DeleteMethod::None => { //Just do nothing } + _ => { + unreachable!() + } } } } @@ -127,71 +111,35 @@ impl Default for EmptyFiles { } impl DebugPrint for EmptyFiles { - #[allow(dead_code)] - #[allow(unreachable_code)] - /// Debugging printing - only available on debug build fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } println!("---------------DEBUG PRINT---------------"); println!("Empty list size - {}", self.empty_files.len()); - println!("Delete Method - {:?}", self.delete_method); self.debug_print_common(); println!("-----------------------------------------"); } } -impl SaveResults for EmptyFiles { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( +impl PrintResults for EmptyFiles { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { + writeln!( writer, "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } + )?; if !self.empty_files.is_empty() { - writeln!(writer, "Found {} empty files.", self.information.number_of_empty_files).unwrap(); + writeln!(writer, "Found {} empty files.", self.information.number_of_empty_files)?; for file_entry in &self.empty_files { - writeln!(writer, "{}", file_entry.path.display()).unwrap(); + writeln!(writer, "{}", file_entry.path.display())?; } } else { - write!(writer, "Not found any empty files.").unwrap(); + write!(writer, "Not found any empty files.")?; } - true - } -} - -impl PrintResults for EmptyFiles { - /// Print information's about duplicated entries - /// Only needed for CLI - fn print_results(&self) { - println!("Found {} empty files.\n", self.information.number_of_empty_files); - for file_entry in &self.empty_files { - println!("{}", file_entry.path.display()); - } + Ok(()) } } @@ -203,8 +151,4 @@ impl EmptyFiles { pub const fn get_information(&self) -> &Info { &self.information } - - pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { - self.delete_method = delete_method; - } } diff --git a/czkawka_core/src/empty_folder.rs b/czkawka_core/src/empty_folder.rs index 61712e3..7cc130b 100644 --- a/czkawka_core/src/empty_folder.rs +++ b/czkawka_core/src/empty_folder.rs @@ -1,18 +1,18 @@ use std::collections::BTreeMap; use std::fs; -use std::fs::File; -use std::io::{BufWriter, Write}; + +use std::io::Write; use std::path::PathBuf; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; -use log::{debug, info}; +use log::debug; use crate::common_dir_traversal::{Collect, DirTraversalBuilder, DirTraversalResult, FolderEmptiness, FolderEntry, ProgressData, ToolType}; use crate::common_tool::{CommonData, CommonToolData}; -use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; +use crate::common_traits::{DebugPrint, PrintResults}; -/// Struct to store most basics info about all folder pub struct EmptyFolder { common_data: CommonToolData, information: Info, @@ -20,16 +20,12 @@ pub struct EmptyFolder { empty_folder_list: BTreeMap, // Path, FolderEntry } -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_empty_folders: usize, } -/// Method implementation for `EmptyFolder` impl EmptyFolder { - /// New function providing basics values - pub fn new() -> Self { Self { common_data: CommonToolData::new(ToolType::EmptyFolders), @@ -47,14 +43,8 @@ impl EmptyFolder { &self.information } + #[fun_time(message = "find_empty_folders")] pub fn find_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding empty folders"); - let start_time = std::time::Instant::now(); - self.find_empty_folders_internal(stop_receiver, progress_sender); - info!("Ended finding empty folders which took {:?}", start_time.elapsed()); - } - - fn find_empty_folders_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); if !self.check_for_empty_folders(stop_receiver, progress_sender) { self.common_data.stopped_search = true; @@ -67,12 +57,6 @@ impl EmptyFolder { self.debug_print(); } - pub fn set_delete_folder(&mut self, delete_folder: bool) { - self.delete_folders = delete_folder; - } - - /// Clean directory tree - /// If directory contains only 2 empty folders, then this directory should be removed instead two empty folders inside because it will produce another empty folder. fn optimize_folders(&mut self) { let mut new_directory_folders: BTreeMap = Default::default(); @@ -92,10 +76,8 @@ impl EmptyFolder { self.information.number_of_empty_folders = self.empty_folder_list.len(); } - /// Function to check if folder are empty. - /// Parameter `initial_checking` for second check before deleting to be sure that checked folder is still empty + #[fun_time(message = "check_for_empty_folders")] fn check_for_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_for_empty_folders - start"); let result = DirTraversalBuilder::new() .root_dirs(self.common_data.directories.included_directories.clone()) .group_by(|_fe| ()) @@ -107,31 +89,27 @@ impl EmptyFolder { .max_stage(0) .build() .run(); - debug!("check_for_empty_folders - collected folders to check"); - let res = match result { + + match result { DirTraversalResult::SuccessFiles { .. } => { unreachable!() } DirTraversalResult::SuccessFolders { folder_entries, warnings } => { - // We need to set empty folder list - #[allow(unused_mut)] // Used is later by Windows build - for (mut name, folder_entry) in folder_entries { + for (name, folder_entry) in folder_entries { if folder_entry.is_empty != FolderEmptiness::No { self.empty_folder_list.insert(name, folder_entry); } } self.common_data.text_messages.warnings.extend(warnings); - + debug!("Found {} empty folders.", self.empty_folder_list.len()); true } DirTraversalResult::Stopped => false, - }; - debug!("check_for_empty_folders - end"); - res + } } - /// Deletes earlier found empty folders + #[fun_time(message = "delete_empty_folders")] fn delete_empty_folders(&mut self) { // Folders may be deleted or require too big privileges for name in self.empty_folder_list.keys() { @@ -154,11 +132,8 @@ impl Default for EmptyFolder { } impl DebugPrint for EmptyFolder { - #[allow(dead_code)] - #[allow(unreachable_code)] fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } @@ -169,60 +144,19 @@ impl DebugPrint for EmptyFolder { } } -impl SaveResults for EmptyFolder { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( - writer, - "Results of searching {:?} with excluded directories {:?}", - self.common_data.directories.included_directories, self.common_data.directories.excluded_directories - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } - +impl PrintResults for EmptyFolder { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { if !self.empty_folder_list.is_empty() { - writeln!( - writer, - "-------------------------------------------------Empty folder list-------------------------------------------------" - ) - .unwrap(); - writeln!(writer, "Found {} empty folders", self.information.number_of_empty_folders).unwrap(); + writeln!(writer, "--------------------------Empty folder list--------------------------")?; + writeln!(writer, "Found {} empty folders", self.information.number_of_empty_folders)?; for name in self.empty_folder_list.keys() { - writeln!(writer, "{}", name.display()).unwrap(); + writeln!(writer, "{}", name.display())?; } } else { - write!(writer, "Not found any empty folders.").unwrap(); + write!(writer, "Not found any empty folders.")?; } - true - } -} - -impl PrintResults for EmptyFolder { - fn print_results(&self) { - if !self.empty_folder_list.is_empty() { - println!("Found {} empty folders", self.empty_folder_list.len()); - } - for name in self.empty_folder_list.keys() { - println!("{}", name.display()); - } + Ok(()) } } @@ -234,3 +168,8 @@ impl CommonData for EmptyFolder { &mut self.common_data } } +impl EmptyFolder { + pub fn set_delete_folder(&mut self, delete_folder: bool) { + self.delete_folders = delete_folder; + } +} diff --git a/czkawka_core/src/invalid_symlinks.rs b/czkawka_core/src/invalid_symlinks.rs index 2436c0a..118222c 100644 --- a/czkawka_core/src/invalid_symlinks.rs +++ b/czkawka_core/src/invalid_symlinks.rs @@ -1,54 +1,37 @@ use std::fs; -use std::fs::File; + use std::io::prelude::*; -use std::io::BufWriter; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; -use log::{debug, info}; +use log::debug; use crate::common_dir_traversal::{Collect, DirTraversalBuilder, DirTraversalResult, ErrorType, FileEntry, ProgressData, ToolType}; -use crate::common_tool::{CommonData, CommonToolData}; +use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; use crate::common_traits::*; -#[derive(Eq, PartialEq, Clone, Debug, Copy)] -pub enum DeleteMethod { - None, - Delete, -} - -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_invalid_symlinks: usize, } -/// Struct with required information's to work pub struct InvalidSymlinks { common_data: CommonToolData, information: Info, invalid_symlinks: Vec, - delete_method: DeleteMethod, } - impl InvalidSymlinks { pub fn new() -> Self { Self { common_data: CommonToolData::new(ToolType::InvalidSymlinks), information: Info::default(), invalid_symlinks: vec![], - delete_method: DeleteMethod::None, } } + #[fun_time(message = "find_invalid_links")] pub fn find_invalid_links(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding invalid symlinks"); - let start_time = std::time::Instant::now(); - self.find_invalid_links_internal(stop_receiver, progress_sender); - info!("Ended finding invalid symlinks which took {:?}", start_time.elapsed()); - } - - fn find_invalid_links_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); if !self.check_files(stop_receiver, progress_sender) { self.common_data.stopped_search = true; @@ -58,21 +41,8 @@ impl InvalidSymlinks { self.debug_print(); } - pub const fn get_invalid_symlinks(&self) -> &Vec { - &self.invalid_symlinks - } - - pub const fn get_information(&self) -> &Info { - &self.information - } - - pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { - self.delete_method = delete_method; - } - - /// Check files for any with size == 0 + #[fun_time(message = "check_files")] fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_files - start"); let result = DirTraversalBuilder::new() .root_dirs(self.common_data.directories.included_directories.clone()) .group_by(|_fe| ()) @@ -85,26 +55,23 @@ impl InvalidSymlinks { .recursive_search(self.common_data.recursive_search) .build() .run(); - debug!("check_files - collected files"); - let res = match result { + + match result { DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { - if let Some(((), invalid_symlinks)) = grouped_file_entries.into_iter().next() { - self.invalid_symlinks = invalid_symlinks; - } + self.invalid_symlinks = grouped_file_entries.into_values().flatten().collect(); self.information.number_of_invalid_symlinks = self.invalid_symlinks.len(); self.common_data.text_messages.warnings.extend(warnings); + debug!("Found {} invalid symlinks.", self.information.number_of_invalid_symlinks); true } DirTraversalResult::SuccessFolders { .. } => unreachable!(), DirTraversalResult::Stopped => false, - }; - debug!("check_files - end"); - res + } } - /// Function to delete files, from filed Vector + #[fun_time(message = "delete_files")] fn delete_files(&mut self) { - match self.delete_method { + match self.common_data.delete_method { DeleteMethod::Delete => { for file_entry in &self.invalid_symlinks { if fs::remove_file(file_entry.path.clone()).is_err() { @@ -115,6 +82,7 @@ impl InvalidSymlinks { DeleteMethod::None => { //Just do nothing } + _ => unreachable!(), } } } @@ -126,52 +94,21 @@ impl Default for InvalidSymlinks { } impl DebugPrint for InvalidSymlinks { - #[allow(dead_code)] - #[allow(unreachable_code)] - /// Debugging printing - only available on debug build fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } println!("---------------DEBUG PRINT---------------"); println!("Invalid symlinks list size - {}", self.invalid_symlinks.len()); - println!("Delete Method - {:?}", self.delete_method); self.debug_print_common(); println!("-----------------------------------------"); } } -impl SaveResults for InvalidSymlinks { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( - writer, - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", - self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } - +impl PrintResults for InvalidSymlinks { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { if !self.invalid_symlinks.is_empty() { - writeln!(writer, "Found {} invalid symlinks.", self.information.number_of_invalid_symlinks).unwrap(); + writeln!(writer, "Found {} invalid symlinks.", self.information.number_of_invalid_symlinks)?; for file_entry in &self.invalid_symlinks { writeln!( writer, @@ -182,32 +119,13 @@ impl SaveResults for InvalidSymlinks { ErrorType::InfiniteRecursion => "Infinite Recursion", ErrorType::NonExistentFile => "Non Existent File", } - ) - .unwrap(); + )?; } } else { - write!(writer, "Not found any invalid symlinks.").unwrap(); + write!(writer, "Not found any invalid symlinks.")?; } - true - } -} -impl PrintResults for InvalidSymlinks { - /// Print information's about duplicated entries - /// Only needed for CLI - fn print_results(&self) { - println!("Found {} invalid symlinks.\n", self.information.number_of_invalid_symlinks); - for file_entry in &self.invalid_symlinks { - println!( - "{}\t\t{}\t\t{}", - file_entry.path.display(), - file_entry.symlink_info.clone().expect("invalid traversal result").destination_path.display(), - match file_entry.symlink_info.clone().expect("invalid traversal result").type_of_error { - ErrorType::InfiniteRecursion => "Infinite Recursion", - ErrorType::NonExistentFile => "Non Existent File", - } - ); - } + Ok(()) } } @@ -219,3 +137,13 @@ impl CommonData for InvalidSymlinks { &mut self.common_data } } + +impl InvalidSymlinks { + pub const fn get_invalid_symlinks(&self) -> &Vec { + &self.invalid_symlinks + } + + pub const fn get_information(&self) -> &Info { + &self.information + } +} diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs index ac2a7b8..da5955c 100644 --- a/czkawka_core/src/same_music.rs +++ b/czkawka_core/src/same_music.rs @@ -2,7 +2,7 @@ use std::cmp::max; use std::collections::{BTreeMap, HashSet}; use std::fs::File; use std::io::prelude::*; -use std::io::BufWriter; + use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -10,9 +10,11 @@ use std::{mem, panic}; use anyhow::Context; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; +use humansize::{format_size, BINARY}; use lofty::{read_from, AudioFile, ItemKey, TaggedFileExt}; -use log::{debug, info}; +use log::debug; use rayon::prelude::*; use rusty_chromaprint::{match_fingerprints, Configuration, Fingerprinter}; use serde::{Deserialize, Serialize}; @@ -29,12 +31,6 @@ use crate::common_dir_traversal::{CheckingMethod, DirTraversalBuilder, DirTraver use crate::common_tool::{CommonData, CommonToolData}; use crate::common_traits::*; -#[derive(Eq, PartialEq, Clone, Debug, Copy)] -pub enum DeleteMethod { - None, - Delete, -} - bitflags! { #[derive(PartialEq, Copy, Clone, Debug)] pub struct MusicSimilarity : u32 { @@ -95,14 +91,12 @@ impl FileEntry { } } -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_duplicates: usize, pub number_of_groups: u64, } -/// Struct with required information's to work pub struct SameMusic { common_data: CommonToolData, information: Info, @@ -110,7 +104,6 @@ pub struct SameMusic { music_entries: Vec, duplicated_music_entries: Vec>, duplicated_music_entries_referenced: Vec<(MusicEntry, Vec)>, - delete_method: DeleteMethod, music_similarity: MusicSimilarity, approximate_comparison: bool, check_type: CheckingMethod, @@ -125,7 +118,6 @@ impl SameMusic { common_data: CommonToolData::new(ToolType::SameMusic), information: Info::default(), music_entries: Vec::with_capacity(2048), - delete_method: DeleteMethod::None, music_similarity: MusicSimilarity::NONE, duplicated_music_entries: vec![], music_to_check: Default::default(), @@ -138,14 +130,8 @@ impl SameMusic { } } + #[fun_time(message = "print_results")] pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding same music files"); - let start_time = std::time::Instant::now(); - self.find_same_music_internal(stop_receiver, progress_sender); - info!("Ended finding same music which took {:?}", start_time.elapsed()); - } - - fn find_same_music_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty(); if !self.check_files(stop_receiver, progress_sender) { @@ -183,6 +169,7 @@ impl SameMusic { self.debug_print(); } + #[fun_time(message = "check_files")] fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { if !self.common_data.allowed_extensions.using_custom_extensions() { self.common_data.allowed_extensions.extend_allowed_extensions(AUDIO_FILES_EXTENSIONS); @@ -207,15 +194,16 @@ impl SameMusic { .max_stage(2) .build() .run(); + match result { DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { - if let Some(music_to_check) = grouped_file_entries.get(&()) { - for fe in music_to_check { - self.music_to_check.insert(fe.path.to_string_lossy().to_string(), fe.to_music_entry()); - } - } + self.music_to_check = grouped_file_entries + .into_values() + .flatten() + .map(|fe| (fe.path.to_string_lossy().to_string(), fe.to_music_entry())) + .collect(); self.common_data.text_messages.warnings.extend(warnings); - + debug!("check_files - Found {} music files.", self.music_to_check.len()); true } DirTraversalResult::SuccessFolders { .. } => { @@ -225,8 +213,8 @@ impl SameMusic { } } + #[fun_time(message = "load_cache")] fn load_cache(&mut self, checking_tags: bool) -> (BTreeMap, BTreeMap, BTreeMap) { - debug!("load_cache - start, using cache {}", self.common_data.use_cache); let loaded_hash_map; let mut records_already_cached: BTreeMap = Default::default(); @@ -238,6 +226,7 @@ impl SameMusic { self.get_text_messages_mut().extend_with_another_messages(messages); loaded_hash_map = loaded_items.unwrap_or_default(); + debug!("load_cache - Starting to check for differences"); for (name, file_entry) in mem::take(&mut self.music_to_check) { if let Some(cached_file_entry) = loaded_hash_map.get(&name) { records_already_cached.insert(name.clone(), cached_file_entry.clone()); @@ -245,16 +234,22 @@ impl SameMusic { non_cached_files_to_check.insert(name, file_entry); } } + debug!( + "load_cache - completed diff between loaded and prechecked files, {}({}) - non cached, {}({}) - already cached", + non_cached_files_to_check.len(), + format_size(non_cached_files_to_check.values().map(|e| e.size).sum::(), BINARY), + records_already_cached.len(), + format_size(records_already_cached.values().map(|e| e.size).sum::(), BINARY), + ); } else { loaded_hash_map = Default::default(); mem::swap(&mut self.music_to_check, &mut non_cached_files_to_check); } - debug!("load_cache - end"); (loaded_hash_map, records_already_cached, non_cached_files_to_check) } + #[fun_time(message = "save_cache")] fn save_cache(&mut self, vec_file_entry: Vec, loaded_hash_map: BTreeMap, checking_tags: bool) { - debug!("save_cache - start, using cache {}", self.common_data.use_cache); if !self.common_data.use_cache { return; } @@ -267,18 +262,17 @@ impl SameMusic { let messages = save_cache_to_file_generalized(get_similar_music_cache_file(checking_tags), &all_results, self.common_data.save_also_as_json, 0); self.get_text_messages_mut().extend_with_another_messages(messages); - debug!("save_cache - end"); } + #[fun_time(message = "calculate_fingerprint")] fn calculate_fingerprint(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("calculate_fingerprint - start"); let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.load_cache(false); let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = prepare_thread_handler_common(progress_sender, 1, 3, non_cached_files_to_check.len(), self.check_type, self.common_data.tool_type); let configuration = &self.hash_preset_config; - // Clean for duplicate files + debug!("calculate_fingerprint - starting fingerprinting"); let mut vec_file_entry = non_cached_files_to_check .into_par_iter() .map(|(path, mut music_entry)| { @@ -299,6 +293,7 @@ impl SameMusic { .filter(Option::is_some) .map(Option::unwrap) .collect::>(); + debug!("calculate_fingerprint - ended fingerprinting"); send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); @@ -313,12 +308,11 @@ impl SameMusic { if check_was_stopped.load(Ordering::Relaxed) { return false; } - debug!("calculate_fingerprint - end"); true } + #[fun_time(message = "read_tags")] fn read_tags(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("read_tags - start"); let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.load_cache(true); let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = @@ -360,13 +354,11 @@ impl SameMusic { return false; } - debug!("read_tags - end"); - true } + #[fun_time(message = "check_for_duplicate_tags")] fn check_for_duplicate_tags(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_for_duplicate_tags - start"); let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 2, 2, self.music_to_check.len(), self.check_type, self.common_data.tool_type); @@ -462,17 +454,14 @@ impl SameMusic { // Clear unused data self.music_entries.clear(); - debug!("check_for_duplicate_tags - end"); - true } + #[fun_time(message = "read_tags_to_files_similar_by_content")] fn read_tags_to_files_similar_by_content(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("read_tags_to_files_similar_by_content - start"); let groups_to_check = max(self.duplicated_music_entries.len(), self.duplicated_music_entries_referenced.len()); let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = prepare_thread_handler_common(progress_sender, 3, 3, groups_to_check, self.check_type, self.common_data.tool_type); - // TODO is ther a way to just run iterator and not collect any info? if !self.duplicated_music_entries.is_empty() { let _: Vec<_> = self .duplicated_music_entries @@ -515,7 +504,6 @@ impl SameMusic { send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); - debug!("read_tags_to_files_similar_by_content - end"); !check_was_stopped.load(Ordering::Relaxed) } @@ -535,6 +523,7 @@ impl SameMusic { (base_files, files_to_compare) } + #[fun_time(message = "compare_fingerprints")] fn compare_fingerprints( &mut self, stop_receiver: Option<&Receiver<()>>, @@ -542,7 +531,6 @@ impl SameMusic { base_files: Vec, files_to_compare: &[MusicEntry], ) -> Option>> { - debug!("compare_fingerprints - start"); let mut used_paths: HashSet = Default::default(); let configuration = &self.hash_preset_config; @@ -591,12 +579,11 @@ impl SameMusic { duplicated_music_entries.push(music_entries); } } - debug!("compare_fingerprints - end"); Some(duplicated_music_entries) } + #[fun_time(message = "check_for_duplicate_fingerprints")] fn check_for_duplicate_fingerprints(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_for_duplicate_fingerprints - start"); let (base_files, files_to_compare) = self.split_fingerprints_to_check(); let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 2, 3, base_files.len(), self.check_type, self.common_data.tool_type); @@ -629,10 +616,10 @@ impl SameMusic { // Clear unused data self.music_entries.clear(); - debug!("check_for_duplicate_fingerprints - end"); true } + #[fun_time(message = "check_music_item")] fn check_music_item( &self, old_duplicates: Vec>, @@ -640,7 +627,6 @@ impl SameMusic { get_item: fn(&MusicEntry) -> &str, approximate_comparison: bool, ) -> Vec> { - debug!("check_music_item - start"); let mut new_duplicates: Vec<_> = Default::default(); let old_duplicates_len = old_duplicates.len(); for vec_file_entry in old_duplicates { @@ -662,11 +648,10 @@ impl SameMusic { } atomic_counter.fetch_add(old_duplicates_len, Ordering::Relaxed); - debug!("check_music_item - end"); new_duplicates } - /// Function to delete files, from filed Vector + #[fun_time(message = "delete_files")] fn delete_files(&mut self) { // TODO @@ -698,10 +683,6 @@ impl SameMusic { &self.information } - pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { - self.delete_method = delete_method; - } - pub fn set_approximate_comparison(&mut self, approximate_comparison: bool) { self.approximate_comparison = approximate_comparison; } @@ -877,7 +858,6 @@ fn read_single_file_tag(path: &str, music_entry: &mut MusicEntry) -> bool { genre = tag_value.to_string(); } } - // println!("{:?}", tag.items()); } if let Ok(old_length_number) = length.parse::() { @@ -887,7 +867,7 @@ fn read_single_file_tag(path: &str, music_entry: &mut MusicEntry) -> bool { if minutes != 0 || seconds != 0 { length = format!("{minutes}:{seconds:02}"); } else if old_length_number > 0 { - // That means, that audio have length smaller that second, but length is properly read + // That means, that audio have length smaller that second, but length was properly read length = "0:01".to_string(); } else { length = String::new(); @@ -913,56 +893,26 @@ impl Default for SameMusic { } impl DebugPrint for SameMusic { - #[allow(dead_code)] - #[allow(unreachable_code)] - /// Debugging printing - only available on debug build + #[fun_time(message = "debug_print")] fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } println!("---------------DEBUG PRINT---------------"); println!("Found files music - {}", self.music_entries.len()); println!("Found duplicated files music - {}", self.duplicated_music_entries.len()); - println!("Delete Method - {:?}", self.delete_method); self.debug_print_common(); println!("-----------------------------------------"); } } -impl SaveResults for SameMusic { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( - writer, - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", - self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } - +impl PrintResults for SameMusic { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { if !self.duplicated_music_entries.is_empty() { - writeln!(writer, "{} music files which have similar friends\n\n.", self.duplicated_music_entries.len()).unwrap(); + writeln!(writer, "{} music files which have similar friends\n\n.", self.duplicated_music_entries.len())?; for vec_file_entry in &self.duplicated_music_entries { - writeln!(writer, "Found {} music files which have similar friends", vec_file_entry.len()).unwrap(); + writeln!(writer, "Found {} music files which have similar friends", vec_file_entry.len())?; for file_entry in vec_file_entry { writeln!( writer, @@ -980,33 +930,10 @@ impl SaveResults for SameMusic { writeln!(writer).unwrap(); } } else { - write!(writer, "Not found any similar music files.").unwrap(); + write!(writer, "Not found any similar music files.")?; } - true - } -} - -impl PrintResults for SameMusic { - /// Print information's about duplicated entries - /// Only needed for CLI - fn print_results(&self) { - println!("Found {} similar music files.\n", self.duplicated_music_entries.len()); - for vec_file_entry in &self.duplicated_music_entries { - for file_entry in vec_file_entry { - println!( - "TT: {} - TA: {} - Y: {} - L: {} - G: {} - B: {} - P: {}", - file_entry.track_title, - file_entry.track_artist, - file_entry.year, - file_entry.length, - file_entry.genre, - file_entry.bitrate, - file_entry.path.display() - ); - } - println!(); - } + Ok(()) } } @@ -1053,6 +980,15 @@ fn get_approximate_conversion(what: &mut String) { *what = new_what; } +impl CommonData for SameMusic { + fn get_cd(&self) -> &CommonToolData { + &self.common_data + } + fn get_cd_mut(&mut self) -> &mut CommonToolData { + &mut self.common_data + } +} + #[cfg(test)] mod tests { use crate::same_music::get_approximate_conversion; @@ -1076,12 +1012,3 @@ mod tests { assert_eq!(what, "Kekistan"); } } - -impl CommonData for SameMusic { - fn get_cd(&self) -> &CommonToolData { - &self.common_data - } - fn get_cd_mut(&mut self) -> &mut CommonToolData { - &mut self.common_data - } -} diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 1394d32..05e30c6 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::fs::{DirEntry, File, Metadata}; -use std::io::{Write, *}; +use std::fs::{DirEntry, Metadata}; +use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::atomic::Ordering; use std::time::SystemTime; @@ -8,11 +8,12 @@ use std::{mem, panic}; use bk_tree::BKTree; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; use humansize::{format_size, BINARY}; use image::GenericImageView; use image_hasher::{FilterType, HashAlg, HasherConfig}; -use log::{debug, info}; +use log::debug; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -25,7 +26,7 @@ use crate::common::{ use crate::common_cache::{get_similar_images_cache_file, load_cache_from_file_generalized_by_path, save_cache_to_file_generalized}; use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData, ToolType}; use crate::common_tool::{CommonData, CommonToolData}; -use crate::common_traits::{DebugPrint, PrintResults, ResultEntry, SaveResults}; +use crate::common_traits::{DebugPrint, PrintResults, ResultEntry}; use crate::flc; type ImHash = Vec; @@ -59,7 +60,6 @@ impl ResultEntry for FileEntry { } } -/// Used by CLI tool when we cannot use directly values #[derive(Clone, Debug, Copy)] pub enum SimilarityPreset { Original, @@ -72,7 +72,6 @@ pub enum SimilarityPreset { None, } -/// Distance metric to use with the BK-tree. struct Hamming; impl bk_tree::Metric for Hamming { @@ -85,7 +84,6 @@ impl bk_tree::Metric for Hamming { } } -/// Struct to store most basics info about all folder pub struct SimilarImages { common_data: CommonToolData, information: Info, @@ -102,17 +100,13 @@ pub struct SimilarImages { exclude_images_with_same_size: bool, } -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_duplicates: usize, pub number_of_groups: u64, } -/// Method implementation for `EmptyFolder` impl SimilarImages { - /// New function providing basics values - pub fn new() -> Self { Self { common_data: CommonToolData::new(ToolType::SimilarImages), @@ -130,14 +124,8 @@ impl SimilarImages { } } + #[fun_time(message = "find_similar_images")] pub fn find_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding similar images files"); - let start_time = std::time::Instant::now(); - self.find_similar_images_internal(stop_receiver, progress_sender); - info!("Ended finding similar images which took {:?}", start_time.elapsed()); - } - - pub fn find_similar_images_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); self.common_data.use_reference_folders = !self.common_data.directories.reference_directories.is_empty(); if !self.check_for_similar_images(stop_receiver, progress_sender) { @@ -152,20 +140,11 @@ impl SimilarImages { self.common_data.stopped_search = true; return; } - // if self.delete_folders { - // self.delete_empty_folders(); - // } self.debug_print(); } - // pub fn set_delete_folder(&mut self, delete_folder: bool) { - // self.delete_folders = delete_folder; - // } - - /// Function to check if folder are empty. - /// Parameter `initial_checking` for second check before deleting to be sure that checked folder is still empty + #[fun_time(message = "check_for_similar_images")] fn check_for_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_for_similar_images - start"); let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector if !self.common_data.allowed_extensions.using_custom_extensions() { @@ -246,7 +225,6 @@ impl SimilarImages { send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); - debug!("check_for_similar_images - end"); true } @@ -279,8 +257,8 @@ impl SimilarImages { } } + #[fun_time(message = "hash_images_load_cache")] fn hash_images_load_cache(&mut self) -> (BTreeMap, BTreeMap, BTreeMap) { - debug!("hash_images_load_cache - start, use cache: {}", self.common_data.use_cache); let loaded_hash_map; let mut records_already_cached: BTreeMap = Default::default(); @@ -295,6 +273,7 @@ impl SimilarImages { self.get_text_messages_mut().extend_with_another_messages(messages); loaded_hash_map = loaded_items.unwrap_or_default(); + debug!("hash_images-load_cache - starting calculating diff"); for (name, file_entry) in mem::take(&mut self.images_to_check) { if let Some(cached_file_entry) = loaded_hash_map.get(&name) { records_already_cached.insert(name.clone(), cached_file_entry.clone()); @@ -302,11 +281,17 @@ impl SimilarImages { non_cached_files_to_check.insert(name, file_entry); } } + debug!( + "hash_images_load_cache - completed diff between loaded and prechecked files, {}({}) - non cached, {}({}) - already cached", + non_cached_files_to_check.len(), + format_size(non_cached_files_to_check.values().map(|e| e.size).sum::(), BINARY), + records_already_cached.len(), + format_size(records_already_cached.values().map(|e| e.size).sum::(), BINARY), + ); } else { loaded_hash_map = Default::default(); mem::swap(&mut self.images_to_check, &mut non_cached_files_to_check); } - debug!("hash_images_load_cache - end"); (loaded_hash_map, records_already_cached, non_cached_files_to_check) } @@ -317,8 +302,8 @@ impl SimilarImages { // - Join already read hashes with hashes which were read from file // - Join all hashes and save it to file + #[fun_time(message = "hash_images")] fn hash_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("hash_images - start"); let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.hash_images_load_cache(); let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = @@ -363,13 +348,11 @@ impl SimilarImages { return false; } - debug!("hash_images - end"); - true } + #[fun_time(message = "save_to_cache")] fn save_to_cache(&mut self, vec_file_entry: Vec<(FileEntry, ImHash)>, loaded_hash_map: BTreeMap) { - debug!("save_to_cache - start, using cache: {}", self.common_data.use_cache); if self.common_data.use_cache { // Must save all results to file, old loaded from file with all currently counted results let mut all_results: BTreeMap = loaded_hash_map; @@ -385,7 +368,6 @@ impl SimilarImages { ); self.get_text_messages_mut().extend_with_another_messages(messages); } - debug!("save_to_cache - end"); } fn collect_image_file_entry(&self, mut file_entry: FileEntry) -> (FileEntry, ImHash) { @@ -460,8 +442,8 @@ impl SimilarImages { } // Split hashes at 2 parts, base hashes and hashes to compare, 3 argument is set of hashes with multiple images + #[fun_time(message = "split_hashes")] fn split_hashes(&mut self, all_hashed_images: &HashMap>) -> (Vec, HashSet) { - debug!("split_hashes - start"); let hashes_with_multiple_images: HashSet = all_hashed_images .iter() .filter_map(|(hash, vec_file_entry)| { @@ -499,10 +481,10 @@ impl SimilarImages { } base_hashes = all_hashed_images.keys().cloned().collect::>(); } - debug!("split_hashes - end"); (base_hashes, hashes_with_multiple_images) } + #[fun_time(message = "collect_hash_compare_result")] fn collect_hash_compare_result( &self, hashes_parents: HashMap, @@ -511,7 +493,6 @@ impl SimilarImages { collected_similar_images: &mut HashMap>, hashes_similarity: HashMap, ) { - debug!("collect_hash_compare_result - start, use reference: {}", self.common_data.use_reference_folders); if self.common_data.use_reference_folders { // This is same step as without reference folders, but also checks if children are inside/outside reference directories, because may happen, that one file is inside reference folder and other outside @@ -561,9 +542,9 @@ impl SimilarImages { collected_similar_images.get_mut(&parent_hash).unwrap().append(&mut vec_fe); } } - debug!("collect_hash_compare_result - end"); } + #[fun_time(message = "compare_hashes_with_non_zero_tolerance")] fn compare_hashes_with_non_zero_tolerance( &mut self, all_hashed_images: &HashMap>, @@ -572,7 +553,6 @@ impl SimilarImages { stop_receiver: Option<&Receiver<()>>, tolerance: u32, ) -> bool { - debug!("compare_hashes_with_non_zero_tolerance - start"); // Don't use hashes with multiple images in bktree, because they will always be master of group and cannot be find by other hashes let (base_hashes, hashes_with_multiple_images) = self.split_hashes(all_hashed_images); @@ -637,10 +617,10 @@ impl SimilarImages { debug_check_for_duplicated_things(self.common_data.use_reference_folders, &hashes_parents, &hashes_similarity, all_hashed_images, "LATTER"); self.collect_hash_compare_result(hashes_parents, &hashes_with_multiple_images, all_hashed_images, collected_similar_images, hashes_similarity); - debug!("compare_hashes_with_non_zero_tolerance - end"); true } + #[fun_time(message = "connect_results")] fn connect_results( &self, partial_results: Vec<(&ImHash, Vec<(u32, &ImHash)>)>, @@ -648,7 +628,6 @@ impl SimilarImages { hashes_similarity: &mut HashMap, hashes_with_multiple_images: &HashSet, ) { - debug!("connect_results - start"); for (original_hash, vec_compared_hashes) in partial_results { let mut number_of_added_child_items = 0; for (similarity, compared_hash) in vec_compared_hashes { @@ -701,11 +680,10 @@ impl SimilarImages { hashes_parents.insert((*original_hash).clone(), number_of_added_child_items); } } - debug!("connect_results - end"); } + #[fun_time(message = "find_similar_hashes")] fn find_similar_hashes(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("find_similar_hashes - start"); if self.image_hashes.is_empty() { return true; } @@ -724,10 +702,8 @@ impl SimilarImages { collected_similar_images.insert(hash, vec_file_entry); } } - } else { - if !self.compare_hashes_with_non_zero_tolerance(&all_hashed_images, &mut collected_similar_images, progress_sender, stop_receiver, tolerance) { - return false; - } + } else if !self.compare_hashes_with_non_zero_tolerance(&all_hashed_images, &mut collected_similar_images, progress_sender, stop_receiver, tolerance) { + return false; } self.verify_duplicated_items(&collected_similar_images); @@ -756,12 +732,11 @@ impl SimilarImages { self.images_to_check = Default::default(); self.bktree = BKTree::new(Hamming); - debug!("find_similar_hashes - end"); true } + #[fun_time(message = "exclude_items_with_same_size")] fn exclude_items_with_same_size(&mut self) { - debug!("exclude_items_with_same_size - start, exclude: {}", self.exclude_images_with_same_size); if self.exclude_images_with_same_size { for vec_file_entry in mem::take(&mut self.similar_vectors) { let mut bt_sizes: BTreeSet = Default::default(); @@ -777,14 +752,10 @@ impl SimilarImages { } } } - debug!("exclude_items_with_same_size - end"); } + #[fun_time(message = "remove_multiple_records_from_reference_folders")] fn remove_multiple_records_from_reference_folders(&mut self) { - debug!( - "remove_multiple_records_from_reference_folders - start, use reference: {}", - self.common_data.use_reference_folders - ); if self.common_data.use_reference_folders { self.similar_referenced_vectors = mem::take(&mut self.similar_vectors) .into_iter() @@ -801,16 +772,14 @@ impl SimilarImages { }) .collect::)>>(); } - debug!("remove_multiple_records_from_reference_folders - end"); } - #[allow(dead_code)] - #[allow(unreachable_code)] #[allow(unused_variables)] // TODO this probably not works good when reference folders are used pub fn verify_duplicated_items(&self, collected_similar_images: &HashMap>) { - #[cfg(not(debug_assertions))] - return; + if !cfg!(debug_assertions) { + return; + } // Validating if group contains duplicated results let mut result_hashset: HashSet = Default::default(); let mut found = false; @@ -851,11 +820,8 @@ impl Default for SimilarImages { } impl DebugPrint for SimilarImages { - #[allow(dead_code)] - #[allow(unreachable_code)] fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } @@ -865,39 +831,13 @@ impl DebugPrint for SimilarImages { } } -impl SaveResults for SimilarImages { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( - writer, - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", - self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } - +impl PrintResults for SimilarImages { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { if !self.similar_vectors.is_empty() { - write!(writer, "{} images which have similar friends\n\n", self.similar_vectors.len()).unwrap(); + write!(writer, "{} images which have similar friends\n\n", self.similar_vectors.len())?; for struct_similar in &self.similar_vectors { - writeln!(writer, "Found {} images which have similar friends", struct_similar.len()).unwrap(); + writeln!(writer, "Found {} images which have similar friends", struct_similar.len())?; for file_entry in struct_similar { writeln!( writer, @@ -906,37 +846,15 @@ impl SaveResults for SimilarImages { file_entry.dimensions, format_size(file_entry.size, BINARY), get_string_from_similarity(&file_entry.similarity, self.hash_size) - ) - .unwrap(); + )?; } - writeln!(writer).unwrap(); + writeln!(writer)?; } } else { - write!(writer, "Not found any similar images.").unwrap(); + write!(writer, "Not found any similar images.")?; } - true - } -} - -impl PrintResults for SimilarImages { - fn print_results(&self) { - if !self.similar_vectors.is_empty() { - println!("Found {} images which have similar friends", self.similar_vectors.len()); - - for vec_file_entry in &self.similar_vectors { - for file_entry in vec_file_entry { - println!( - "{} - {} - {} - {}", - file_entry.path.display(), - file_entry.dimensions, - format_size(file_entry.size, BINARY), - get_string_from_similarity(&file_entry.similarity, self.hash_size) - ); - } - println!(); - } - } + Ok(()) } } @@ -1064,8 +982,9 @@ fn debug_check_for_duplicated_things( all_hashed_images: &HashMap>, numm: &str, ) { - #[cfg(not(debug_assertions))] - return; + if !cfg!(debug_assertions) { + return; + } if use_reference_folders { return; diff --git a/czkawka_core/src/similar_videos.rs b/czkawka_core/src/similar_videos.rs index 589e035..ab6ad33 100644 --- a/czkawka_core/src/similar_videos.rs +++ b/czkawka_core/src/similar_videos.rs @@ -1,15 +1,15 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fs::{DirEntry, File, Metadata}; -use std::io::{Write, *}; +use std::fs::{DirEntry, Metadata}; +use std::io::Write; use std::mem; use std::path::{Path, PathBuf}; use std::sync::atomic::Ordering; use crossbeam_channel::Receiver; use ffmpeg_cmdline_utils::FfmpegErrorKind::FfmpegNotFound; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; use humansize::{format_size, BINARY}; -use log::{debug, info}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; use vid_dup_finder_lib::HashCreationErrorKind::DetermineVideo; @@ -19,7 +19,7 @@ use crate::common::{check_folder_children, prepare_thread_handler_common, send_i use crate::common_cache::{get_similar_videos_cache_file, load_cache_from_file_generalized_by_path, save_cache_to_file_generalized}; use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData, ToolType}; use crate::common_tool::{CommonData, CommonToolData}; -use crate::common_traits::{DebugPrint, PrintResults, ResultEntry, SaveResults}; +use crate::common_traits::{DebugPrint, PrintResults, ResultEntry}; use crate::flc; use crate::localizer_core::generate_translation_hashmap; @@ -46,7 +46,6 @@ impl ResultEntry for FileEntry { } } -/// Distance metric to use with the BK-tree. struct Hamming; impl bk_tree::Metric> for Hamming { @@ -61,7 +60,6 @@ impl bk_tree::Metric> for Hamming { } } -/// Struct to store most basics info about all folder pub struct SimilarVideos { common_data: CommonToolData, information: Info, @@ -82,17 +80,13 @@ impl CommonData for SimilarVideos { } } -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_duplicates: usize, pub number_of_groups: u64, } -/// Method implementation for `EmptyFolder` impl SimilarVideos { - /// New function providing basics values - pub fn new() -> Self { Self { common_data: CommonToolData::new(ToolType::SimilarVideos), @@ -106,14 +100,8 @@ impl SimilarVideos { } } + #[fun_time(message = "find_similar_videos")] pub fn find_similar_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding similar videos"); - let start_time = std::time::Instant::now(); - self.find_similar_videos_internal(stop_receiver, progress_sender); - info!("Ended finding similar videos which took {:?}", start_time.elapsed()); - } - - fn find_similar_videos_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { if !check_if_ffmpeg_is_installed() { self.common_data.text_messages.errors.push(flc!("core_ffmpeg_not_found")); #[cfg(target_os = "windows")] @@ -134,21 +122,12 @@ impl SimilarVideos { self.common_data.stopped_search = true; return; } - // if self.delete_folders { - // self.delete_empty_folders(); - // } } self.debug_print(); } - // pub fn set_delete_folder(&mut self, delete_folder: bool) { - // self.delete_folders = delete_folder; - // } - - /// Function to check if folder are empty. - /// Parameter `initial_checking` for second check before deleting to be sure that checked folder is still empty + #[fun_time(message = "check_for_similar_videos")] fn check_for_similar_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_for_similar_videos - start"); let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector if !self.common_data.allowed_extensions.using_custom_extensions() { @@ -225,7 +204,6 @@ impl SimilarVideos { send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); - debug!("check_for_similar_videos - end"); true } @@ -258,8 +236,8 @@ impl SimilarVideos { } } + #[fun_time(message = "load_cache_at_start")] fn load_cache_at_start(&mut self) -> (BTreeMap, BTreeMap, BTreeMap) { - debug!("load_cache_at_start - start, use cache: {}", self.common_data.use_cache); let loaded_hash_map; let mut records_already_cached: BTreeMap = Default::default(); let mut non_cached_files_to_check: BTreeMap = Default::default(); @@ -281,12 +259,11 @@ impl SimilarVideos { loaded_hash_map = Default::default(); mem::swap(&mut self.videos_to_check, &mut non_cached_files_to_check); } - debug!("load_cache_at_start - end"); (loaded_hash_map, records_already_cached, non_cached_files_to_check) } + #[fun_time(message = "sort_videos")] fn sort_videos(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("sort_videos - start"); let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.load_cache_at_start(); let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = @@ -362,11 +339,11 @@ impl SimilarVideos { self.videos_hashes = Default::default(); self.videos_to_check = Default::default(); - debug!("sort_videos - end"); true } + + #[fun_time(message = "save_cache")] fn save_cache(&mut self, vec_file_entry: Vec, loaded_hash_map: BTreeMap) { - debug!("save_cache - start, use cache: {}", self.common_data.use_cache); if self.common_data.use_cache { // Must save all results to file, old loaded from file with all currently counted results let mut all_results: BTreeMap = loaded_hash_map; @@ -377,11 +354,10 @@ impl SimilarVideos { let messages = save_cache_to_file_generalized(&get_similar_videos_cache_file(), &all_results, self.common_data.save_also_as_json, 0); self.get_text_messages_mut().extend_with_another_messages(messages); } - debug!("save_cache - end"); } + #[fun_time(message = "match_groups_of_videos")] fn match_groups_of_videos(&mut self, vector_of_hashes: Vec, hashmap_with_file_entries: &HashMap) { - debug!("match_groups_of_videos - start"); let match_group = vid_dup_finder_lib::search(vector_of_hashes, NormalizedTolerance::new(self.tolerance as f64 / 100.0f64)); let mut collected_similar_videos: Vec> = Default::default(); for i in match_group { @@ -404,11 +380,10 @@ impl SimilarVideos { } self.similar_vectors = collected_similar_videos; - debug!("match_groups_of_videos - end"); } + #[fun_time(message = "remove_from_reference_folders")] fn remove_from_reference_folders(&mut self) { - debug!("remove_from_reference_folders - start, use reference folders: {}", self.common_data.use_reference_folders); if self.common_data.use_reference_folders { self.similar_referenced_vectors = mem::take(&mut self.similar_vectors) .into_iter() @@ -425,7 +400,6 @@ impl SimilarVideos { }) .collect::)>>(); } - debug!("remove_from_reference_folders - end"); } } @@ -436,11 +410,9 @@ impl Default for SimilarVideos { } impl DebugPrint for SimilarVideos { - #[allow(dead_code)] - #[allow(unreachable_code)] + #[fun_time(message = "debug_print")] fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } @@ -451,64 +423,23 @@ impl DebugPrint for SimilarVideos { } } -impl SaveResults for SimilarVideos { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( - writer, - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", - self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } - +impl PrintResults for SimilarVideos { + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { if !self.similar_vectors.is_empty() { - write!(writer, "{} videos which have similar friends\n\n", self.similar_vectors.len()).unwrap(); + write!(writer, "{} videos which have similar friends\n\n", self.similar_vectors.len())?; for struct_similar in &self.similar_vectors { - writeln!(writer, "Found {} videos which have similar friends", struct_similar.len()).unwrap(); + writeln!(writer, "Found {} videos which have similar friends", struct_similar.len())?; for file_entry in struct_similar { - writeln!(writer, "{} - {}", file_entry.path.display(), format_size(file_entry.size, BINARY)).unwrap(); + writeln!(writer, "{} - {}", file_entry.path.display(), format_size(file_entry.size, BINARY))?; } - writeln!(writer).unwrap(); + writeln!(writer)?; } } else { - write!(writer, "Not found any similar videos.").unwrap(); + write!(writer, "Not found any similar videos.")?; } - true - } -} - -impl PrintResults for SimilarVideos { - fn print_results(&self) { - if !self.similar_vectors.is_empty() { - println!("Found {} videos which have similar friends", self.similar_vectors.len()); - - for vec_file_entry in &self.similar_vectors { - for file_entry in vec_file_entry { - println!("{} - {}", file_entry.path.display(), format_size(file_entry.size, BINARY)); - } - println!(); - } - } + Ok(()) } } diff --git a/czkawka_core/src/temporary.rs b/czkawka_core/src/temporary.rs index bfd1d51..d11901b 100644 --- a/czkawka_core/src/temporary.rs +++ b/czkawka_core/src/temporary.rs @@ -1,19 +1,19 @@ use std::fs; -use std::fs::{DirEntry, File, Metadata}; +use std::fs::{DirEntry, Metadata}; use std::io::prelude::*; -use std::io::BufWriter; + use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; -use log::{debug, info}; use rayon::prelude::*; use crate::common::{check_folder_children, prepare_thread_handler_common, send_info_and_wait_for_ending_all_threads}; use crate::common_dir_traversal::{common_get_entry_data_metadata, common_read_dir, get_lowercase_name, get_modified_time, CheckingMethod, ProgressData, ToolType}; -use crate::common_tool::{CommonData, CommonToolData}; +use crate::common_tool::{CommonData, CommonToolData, DeleteMethod}; use crate::common_traits::*; const TEMP_EXTENSIONS: &[&str] = &[ @@ -32,30 +32,21 @@ const TEMP_EXTENSIONS: &[&str] = &[ ".partial", ]; -#[derive(Eq, PartialEq, Clone, Debug, Copy)] -pub enum DeleteMethod { - None, - Delete, -} - #[derive(Clone)] pub struct FileEntry { pub path: PathBuf, pub modified_date: u64, } -/// Info struck with helpful information's about results #[derive(Default)] pub struct Info { pub number_of_temporary_files: usize, } -/// Struct with required information's to work pub struct Temporary { common_data: CommonToolData, information: Info, temporary_files: Vec, - delete_method: DeleteMethod, } impl Temporary { @@ -63,12 +54,12 @@ impl Temporary { Self { common_data: CommonToolData::new(ToolType::TemporaryFiles), information: Info::default(), - delete_method: DeleteMethod::None, temporary_files: vec![], } } - fn find_temporary_files_internal(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { + #[fun_time(message = "find_temporary_files")] + pub fn find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { self.optimize_dirs_before_start(); if !self.check_files(stop_receiver, progress_sender) { self.common_data.stopped_search = true; @@ -78,15 +69,8 @@ impl Temporary { self.debug_print(); } - pub fn find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) { - info!("Starting finding temporary files"); - let start_time = std::time::Instant::now(); - self.find_temporary_files_internal(stop_receiver, progress_sender); - info!("Ended finding temporary files which took {:?}", start_time.elapsed()); - } - + #[fun_time(message = "check_files")] fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - debug!("check_files - start"); let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector // Add root folders for finding @@ -156,7 +140,6 @@ impl Temporary { send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); self.information.number_of_temporary_files = self.temporary_files.len(); - debug!("check_files - end"); true } pub fn get_file_entry( @@ -188,9 +171,9 @@ impl Temporary { }) } - /// Function to delete files, from filed Vector + #[fun_time(message = "delete_files")] fn delete_files(&mut self) { - match self.delete_method { + match self.common_data.delete_method { DeleteMethod::Delete => { let mut warnings = Vec::new(); for file_entry in &self.temporary_files { @@ -203,57 +186,25 @@ impl Temporary { DeleteMethod::None => { //Just do nothing } + _ => unreachable!(), } } } -impl SaveResults for Temporary { - fn save_results_to_file(&mut self, file_name: &str) -> bool { - let file_name: String = match file_name { - "" => "results.txt".to_string(), - k => k.to_string(), - }; - - let file_handler = match File::create(&file_name) { - Ok(t) => t, - Err(e) => { - self.common_data.text_messages.errors.push(format!("Failed to create file {file_name}, reason {e}")); - return false; - } - }; - let mut writer = BufWriter::new(file_handler); - - if let Err(e) = writeln!( - writer, - "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", - self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items - ) { - self.common_data - .text_messages - .errors - .push(format!("Failed to save results to file {file_name}, reason {e}")); - return false; - } - - if !self.temporary_files.is_empty() { - writeln!(writer, "Found {} temporary files.", self.information.number_of_temporary_files).unwrap(); - for file_entry in &self.temporary_files { - writeln!(writer, "{}", file_entry.path.display()).unwrap(); - } - } else { - write!(writer, "Not found any temporary files.").unwrap(); - } - - true - } -} - impl PrintResults for Temporary { - fn print_results(&self) { - println!("Found {} temporary files.\n", self.information.number_of_temporary_files); + fn write_results(&self, writer: &mut T) -> std::io::Result<()> { + writeln!( + writer, + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + self.common_data.directories.included_directories, self.common_data.directories.excluded_directories, self.common_data.excluded_items.items + )?; + writeln!(writer, "Found {} temporary files.\n", self.information.number_of_temporary_files)?; + for file_entry in &self.temporary_files { - println!("{}", file_entry.path.display()); + writeln!(writer, "{}", file_entry.path.display())?; } + + Ok(()) } } @@ -264,16 +215,12 @@ impl Default for Temporary { } impl DebugPrint for Temporary { - #[allow(dead_code)] - #[allow(unreachable_code)] fn debug_print(&self) { - #[cfg(not(debug_assertions))] - { + if !cfg!(debug_assertions) { return; } println!("### Information's"); println!("Temporary list size - {}", self.temporary_files.len()); - println!("Delete Method - {:?}", self.delete_method); self.debug_print_common(); } } @@ -295,8 +242,4 @@ impl Temporary { pub const fn get_information(&self) -> &Info { &self.information } - - pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { - self.delete_method = delete_method; - } } diff --git a/czkawka_gui/Cargo.toml b/czkawka_gui/Cargo.toml index d55624b..f946392 100644 --- a/czkawka_gui/Cargo.toml +++ b/czkawka_gui/Cargo.toml @@ -51,6 +51,7 @@ once_cell = "1.18" log = "0.4.20" handsome_logger = "0.8" +fun_time = { version = "0.3.1", features = ["log"] } czkawka_core = { path = "../czkawka_core", version = "6.0.0", features = [] } gtk4 = { version = "0.7", default-features = false, features = ["v4_6"] } diff --git a/czkawka_gui/src/connect_things/connect_button_compare.rs b/czkawka_gui/src/connect_things/connect_button_compare.rs index 0d8f446..4a114d7 100644 --- a/czkawka_gui/src/connect_things/connect_button_compare.rs +++ b/czkawka_gui/src/connect_things/connect_button_compare.rs @@ -257,7 +257,6 @@ pub fn connect_button_compare(gui_data: &GuiData) { }); } -/// Populate all parameters for current group, it is used at start and when changing groups fn populate_groups_at_start( nb_object: &NotebookObject, model: &TreeModel, @@ -345,7 +344,6 @@ fn populate_groups_at_start( check_button_right_preview_text.set_active(is_active); } -/// Generate images which will be used later as preview images without needing to open them again and again fn generate_cache_for_results(vector_with_path: Vec<(String, String, TreePath)>) -> Vec<(String, String, Image, Image, TreePath)> { // TODO use here threads, // For now threads cannot be used because Image and TreeIter cannot be used in threads @@ -414,7 +412,6 @@ fn generate_cache_for_results(vector_with_path: Vec<(String, String, TreePath)>) cache_all_images } -/// Takes info about current items in groups like path fn get_all_path(model: &TreeModel, current_path: &TreePath, column_header: i32, column_path: i32, column_name: i32) -> Vec<(String, String, TreePath)> { let used_iter = model.iter(current_path).unwrap(); @@ -456,7 +453,6 @@ fn get_all_path(model: &TreeModel, current_path: &TreePath, column_header: i32, returned_vector } -/// Moves iterator to previous/next header fn move_iter(model: &TreeModel, tree_path: &TreePath, column_header: i32, go_next: bool) -> TreePath { let tree_iter = model.iter(tree_path).unwrap(); @@ -486,7 +482,6 @@ fn move_iter(model: &TreeModel, tree_path: &TreePath, column_header: i32, go_nex model.path(&tree_iter) } -/// Populate bottom Scrolled View with small thumbnails fn populate_similar_scrolled_view( scrolled_window: &ScrolledWindow, image_cache: &[(String, String, Image, Image, TreePath)], @@ -581,7 +576,6 @@ fn populate_similar_scrolled_view( scrolled_window.set_child(Some(&all_gtk_box)); } -/// Disables/Enables L/R buttons at the bottom scrolled view fn update_bottom_buttons( all_gtk_box: >k4::Box, shared_using_for_preview: &Rc, Option)>>, diff --git a/czkawka_gui/src/connect_things/connect_button_save.rs b/czkawka_gui/src/connect_things/connect_button_save.rs index fa9b13f..88c7da3 100644 --- a/czkawka_gui/src/connect_things/connect_button_save.rs +++ b/czkawka_gui/src/connect_things/connect_button_save.rs @@ -2,11 +2,10 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +use czkawka_core::common_traits::PrintResults; use gtk4::prelude::*; use gtk4::{Button, Entry}; -use czkawka_core::common_traits::SaveResults; - use crate::flg; use crate::gui_structs::gui_data::GuiData; use crate::help_functions::BottomButtonsEnum; @@ -33,63 +32,72 @@ pub fn connect_button_save(gui_data: &GuiData) { buttons_save.connect_clicked(move |_| { let file_name; - match to_notebook_main_enum(notebook_main.current_page().unwrap()) { + let result = match to_notebook_main_enum(notebook_main.current_page().unwrap()) { NotebookMainEnum::Duplicate => { file_name = "results_duplicates.txt"; - shared_duplication_state.borrow_mut().save_results_to_file(file_name); + shared_duplication_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::EmptyDirectories => { file_name = "results_empty_folder.txt"; - shared_empty_folders_state.borrow_mut().save_results_to_file(file_name); + shared_empty_folders_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::EmptyFiles => { file_name = "results_empty_files.txt"; - shared_empty_files_state.borrow_mut().save_results_to_file(file_name); + shared_empty_files_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::Temporary => { file_name = "results_temporary_files.txt"; - shared_temporary_files_state.borrow_mut().save_results_to_file(file_name); + shared_temporary_files_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::BigFiles => { file_name = "results_big_files.txt"; - shared_big_files_state.borrow_mut().save_results_to_file(file_name); + shared_big_files_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::SimilarImages => { file_name = "results_similar_images.txt"; - shared_similar_images_state.borrow_mut().save_results_to_file(file_name); + shared_similar_images_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::SimilarVideos => { file_name = "results_similar_videos.txt"; - shared_similar_videos_state.borrow_mut().save_results_to_file(file_name); + shared_similar_videos_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::SameMusic => { file_name = "results_same_music.txt"; - shared_same_music_state.borrow_mut().save_results_to_file(file_name); + shared_same_music_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::Symlinks => { file_name = "results_invalid_symlinks.txt"; - shared_same_invalid_symlinks.borrow_mut().save_results_to_file(file_name); + shared_same_invalid_symlinks.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::BrokenFiles => { file_name = "results_broken_files.txt"; - shared_broken_files_state.borrow_mut().save_results_to_file(file_name); + shared_broken_files_state.borrow_mut().print_results_to_file(file_name) } NotebookMainEnum::BadExtensions => { file_name = "results_bad_extensions.txt"; - shared_bad_extensions_state.borrow_mut().save_results_to_file(file_name); + shared_bad_extensions_state.borrow_mut().print_results_to_file(file_name) + } + }; + + match result { + Ok(()) => (), + Err(e) => { + entry_info.set_text(&format!("Failed to save results to file {e}")); + return; } } + post_save_things( file_name, &to_notebook_main_enum(notebook_main.current_page().unwrap()), diff --git a/czkawka_gui/src/connect_things/connect_button_search.rs b/czkawka_gui/src/connect_things/connect_button_search.rs index f09ad34..e6f4eb7 100644 --- a/czkawka_gui/src/connect_things/connect_button_search.rs +++ b/czkawka_gui/src/connect_things/connect_button_search.rs @@ -4,11 +4,11 @@ use std::sync::Arc; use std::thread; use crossbeam_channel::Receiver; +use fun_time::fun_time; use futures::channel::mpsc::UnboundedSender; use glib::Sender; use gtk4::prelude::*; use gtk4::Grid; -use log::debug; use czkawka_core::bad_extensions::BadExtensions; use czkawka_core::big_file::BigFile; @@ -794,9 +794,8 @@ fn bad_extensions_search( }); } +#[fun_time(message = "clean_tree_view")] fn clean_tree_view(tree_view: >k4::TreeView) { - debug!("Start clean tree view"); let list_store = get_list_store(tree_view); list_store.clear(); - debug!("Cleared tree view"); } diff --git a/czkawka_gui/src/connect_things/connect_progress_window.rs b/czkawka_gui/src/connect_things/connect_progress_window.rs index 2be29bf..584ec83 100644 --- a/czkawka_gui/src/connect_things/connect_progress_window.rs +++ b/czkawka_gui/src/connect_things/connect_progress_window.rs @@ -106,9 +106,10 @@ fn process_bar_same_music(gui_data: &GuiData, item: &ProgressData) { 3 => { common_set_data(item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - match item.checking_method { - CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_tags", progress_ratio_tm(item))), - _ => panic!(), + if item.checking_method == CheckingMethod::AudioContent { + label_stage.set_text(&flg!("progress_scanning_music_tags", progress_ratio_tm(item))); + } else { + panic!(); } } _ => panic!(), diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index 849d318..37388df 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -595,7 +595,6 @@ pub fn check_how_much_elements_is_selected(tree_view: &TreeView, column_header: (number_of_selected_items, number_of_selected_groups) } -/// Counts how much headers/groups is in treeview pub fn count_number_of_groups(tree_view: &TreeView, column_header: i32) -> u32 { let mut number_of_selected_groups = 0; diff --git a/czkawka_gui/src/language_functions.rs b/czkawka_gui/src/language_functions.rs index 50e8867..5a601a4 100644 --- a/czkawka_gui/src/language_functions.rs +++ b/czkawka_gui/src/language_functions.rs @@ -4,7 +4,6 @@ pub struct Language { pub short_text: &'static str, } -/// Languages should be alphabetically sorted pub const LANGUAGES_ALL: [Language; 15] = [ Language { combo_box_text: "English", diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index c82d85d..0707d96 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -14,6 +14,7 @@ use glib::Priority; use gtk4::gio::ApplicationFlags; use gtk4::prelude::*; use gtk4::Application; +use log::info; use connect_things::connect_about_buttons::*; use connect_things::connect_button_compare::*; @@ -33,7 +34,7 @@ use connect_things::connect_selection_of_directories::*; use connect_things::connect_settings::*; use connect_things::connect_show_hide_ui::*; use connect_things::connect_similar_image_size_change::*; -use czkawka_core::common::{get_number_of_threads, set_number_of_threads, setup_logger}; +use czkawka_core::common::{get_number_of_threads, print_version_mode, set_number_of_threads, setup_logger}; use czkawka_core::common_dir_traversal::ProgressData; use czkawka_core::*; use gui_structs::gui_data::*; @@ -72,6 +73,7 @@ fn main() { let application = Application::new(None::, ApplicationFlags::HANDLES_OPEN | ApplicationFlags::HANDLES_COMMAND_LINE); application.connect_command_line(move |app, cmdline| { setup_logger(false); + print_version_mode(); build_ui(app, &cmdline.arguments()); 0 }); @@ -101,7 +103,7 @@ fn build_ui(application: &Application, arguments: &[OsString]) { arguments, ); set_number_of_threads(gui_data.settings.scale_settings_number_of_threads.value().round() as usize); - println!("Set thread number to {}", get_number_of_threads()); + info!("Set thread number to {}", get_number_of_threads()); // Needs to run when entire GUI is initialized connect_change_language(&gui_data); diff --git a/czkawka_gui/src/opening_selecting_records.rs b/czkawka_gui/src/opening_selecting_records.rs index 7b10628..413e443 100644 --- a/czkawka_gui/src/opening_selecting_records.rs +++ b/czkawka_gui/src/opening_selecting_records.rs @@ -14,8 +14,8 @@ pub fn opening_enter_function_ported_upper_directories( _modifier_type: ModifierType, ) -> glib::Propagation { let tree_view = event_controller.widget().downcast::().unwrap(); - #[cfg(debug_assertions)] - { + + if cfg!(debug_assertions) { println!("key_code {key_code}"); } @@ -70,8 +70,7 @@ pub fn opening_double_click_function_directories(gesture_click: &GestureClick, n pub fn opening_enter_function_ported(event_controller: >k4::EventControllerKey, _key: Key, key_code: u32, _modifier_type: ModifierType) -> glib::Propagation { let tree_view = event_controller.widget().downcast::().unwrap(); - #[cfg(debug_assertions)] - { + if cfg!(debug_assertions) { println!("key_code {key_code}"); } diff --git a/czkawka_gui/src/saving_loading.rs b/czkawka_gui/src/saving_loading.rs index ccf6723..281c8c2 100644 --- a/czkawka_gui/src/saving_loading.rs +++ b/czkawka_gui/src/saving_loading.rs @@ -981,7 +981,6 @@ fn set_directories(tree_view_included_directories: &TreeView, tree_view_excluded } } -/// Function do not allow to set invalid index to combobox because this would cause to show empty value and function would crash fn save_proper_value_to_combo_box(combo_box: &ComboBoxText, what_to_save: u32) { combo_box.set_active(Some(what_to_save)); if combo_box.active().is_none() { @@ -989,7 +988,6 @@ fn save_proper_value_to_combo_box(combo_box: &ComboBoxText, what_to_save: u32) { } } -/// Reset configuration to defaults pub fn reset_configuration(manual_clearing: bool, upper_notebook: &GuiUpperNotebook, main_notebook: &GuiMainNotebook, settings: &GuiSettings, text_view_errors: &TextView) { // TODO Maybe add popup dialog to confirm resetting let text_view_errors = text_view_errors.clone(); diff --git a/czkawka_gui/ui/about_dialog.ui b/czkawka_gui/ui/about_dialog.ui index bd6bdbf..fb698cc 100644 --- a/czkawka_gui/ui/about_dialog.ui +++ b/czkawka_gui/ui/about_dialog.ui @@ -1,8 +1,8 @@ - + - + 2020 - 2023 Rafał Mikrut(qarmin) diff --git a/czkawka_gui/ui/compare_images.ui b/czkawka_gui/ui/compare_images.ui index 7c212a2..e68c4fd 100644 --- a/czkawka_gui/ui/compare_images.ui +++ b/czkawka_gui/ui/compare_images.ui @@ -1,8 +1,8 @@ - + - + diff --git a/czkawka_gui/ui/czkawka.cmb b/czkawka_gui/ui/czkawka.cmb index 6723a26..3a1e99a 100755 --- a/czkawka_gui/ui/czkawka.cmb +++ b/czkawka_gui/ui/czkawka.cmb @@ -1,1041 +1,1045 @@ - + - (3,None,"about_dialog.ui","about_dialog.ui",None,None,None,None,None,None), - (4,None,"compare_images.ui","compare_images.ui",None,None,None,None,None,None), - (5,None,"main_window.ui","main_window.ui",None,None,None,None,None,None), - (6,None,"popover_right_click.ui","popover_right_click.ui",None,None,None,None,None,None), - (7,None,"popover_select.ui","popover_select.ui",None,None,None,None,None,None), - (8,None,"progress.ui","progress.ui",None,None,None,None,None,None), - (9,None,"settings.ui","settings.ui",None,None,None,None,None,None), - (10,None,"popover_sort.ui","popover_sort.ui",None,None,None,None,None,None) + (3,None,"about_dialog.ui","about_dialog.ui",None,None,None,None,None,None,None), + (4,None,"compare_images.ui","compare_images.ui",None,None,None,None,None,None,None), + (5,None,"main_window.ui","main_window.ui",None,None,None,None,None,None,None), + (6,None,"popover_right_click.ui","popover_right_click.ui",None,None,None,None,None,None,None), + (7,None,"popover_select.ui","popover_select.ui",None,None,None,None,None,None,None), + (8,None,"progress.ui","progress.ui",None,None,None,None,None,None,None), + (9,None,"settings.ui","settings.ui",None,None,None,None,None,None,None), + (10,None,"popover_sort.ui","popover_sort.ui",None,None,None,None,None,None,None) - (3,"gtk","4.0",None), - (4,"gtk","4.0",None), - (5,"gtk","4.0",None), - (6,"gtk","4.0",None), - (7,"gtk","4.0",None), - (8,"gtk","4.0",None), - (9,"gtk","4.0",None) + (3,"gtk","4.6",None), + (4,"gtk","4.6",None), + (5,"gtk","4.6",None), + (6,"gtk","4.6",None), + (7,"gtk","4.6",None), + (8,"gtk","4.6",None), + (9,"gtk","4.6",None), + (10,"gtk","4.6",None) - (3,1,"GtkAboutDialog","about_dialog",None,None,None,None,None), - (4,1,"GtkDialog","window_compare",None,None,None,None,None), - (4,2,"GtkBox",None,1,None,None,None,None), - (4,3,"GtkBox",None,2,None,None,None,None), - (4,4,"GtkLabel","label_group_info",3,None,None,None,1), - (4,5,"GtkButton","button_go_next_compare_group",3,None,None,None,2), - (4,6,"GtkImage",None,5,None,None,None,None), - (4,7,"GtkButton","button_go_previous_compare_group",3,None,None,None,None), - (4,8,"GtkImage",None,7,None,None,None,None), - (4,9,"GtkBox",None,2,None,None,None,1), - (4,10,"GtkCheckButton","check_button_left_preview_text",9,None,None,None,None), - (4,11,"GtkCheckButton","check_button_right_preview_text",9,None,None,None,1), - (4,12,"GtkBox",None,2,None,None,None,2), - (4,13,"GtkImage","image_compare_left",12,None,None,None,None), - (4,14,"GtkImage","image_compare_right",12,None,None,None,1), - (4,15,"GtkScrolledWindow","scrolled_window_compare_choose_images",2,None,None,None,3), - (5,1,"GtkAdjustment","adjustment1",None,None,None,None,None), - (5,2,"GtkWindow","window_main",None,None,None,None,None), - (5,3,"GtkBox",None,2,None,None,None,None), - (5,4,"GtkPaned",None,3,None,None,None,None), - (5,5,"GtkNotebook","notebook_upper",4,None,None,None,None), - (5,6,"GtkNotebookPage",None,5,None,None,None,None), - (5,7,"GtkBox","notebook_upper_included_directories",6,None,None,None,None), - (5,8,"GtkBox",None,7,None,None,None,None), - (5,9,"GtkButton","buttons_add_included_directory",8,None,None,None,None), - (5,10,"GtkBox",None,9,None,None,None,None), - (5,11,"GtkImage",None,10,None,None,None,None), - (5,12,"GtkLabel",None,10,None,None,None,1), - (5,13,"GtkButton","buttons_remove_included_directory",8,None,None,None,1), - (5,14,"GtkBox",None,13,None,None,None,None), - (5,15,"GtkImage",None,14,None,None,None,None), - (5,16,"GtkLabel",None,14,None,None,None,1), - (5,17,"GtkButton","buttons_manual_add_included_directory",8,None,None,None,2), - (5,18,"GtkBox",None,17,None,None,None,None), - (5,19,"GtkImage",None,18,None,None,None,None), - (5,20,"GtkLabel",None,18,None,None,None,1), - (5,21,"GtkScrolledWindow","scrolled_window_included_directories",7,None,None,None,1), - (5,22,"GtkCheckButton","check_button_recursive",7,None,None,None,2), - (5,23,"GtkLabel",None,6,None,None,None,None), - (5,24,"GtkNotebookPage",None,5,None,None,None,1), - (5,25,"GtkBox","notebook_upper_excluded_directories",24,None,None,None,None), - (5,26,"GtkBox",None,25,None,None,None,None), - (5,27,"GtkButton","buttons_add_excluded_directory",26,None,None,None,None), - (5,28,"GtkBox",None,27,None,None,None,None), - (5,29,"GtkImage",None,28,None,None,None,None), - (5,30,"GtkLabel",None,28,None,None,None,1), - (5,31,"GtkButton","buttons_remove_excluded_directory",26,None,None,None,1), - (5,32,"GtkBox",None,31,None,None,None,None), - (5,33,"GtkImage",None,32,None,None,None,None), - (5,34,"GtkLabel",None,32,None,None,None,1), - (5,35,"GtkButton","buttons_manual_add_excluded_directory",26,None,None,None,2), - (5,36,"GtkBox",None,35,None,None,None,None), - (5,37,"GtkImage",None,36,None,None,None,None), - (5,38,"GtkLabel",None,36,None,None,None,1), - (5,39,"GtkScrolledWindow","scrolled_window_excluded_directories",25,None,None,None,1), - (5,40,"GtkLabel",None,24,None,None,None,None), - (5,41,"GtkNotebookPage",None,5,None,None,None,2), - (5,42,"GtkBox","notebook_upper_excluded_items",41,None,None,None,None), - (5,43,"GtkBox",None,42,None,None,None,None), - (5,44,"GtkLabel","label_excluded_items",43,None,None,None,None), - (5,45,"GtkEntry","entry_excluded_items",43,None,None,None,1), - (5,46,"GtkBox",None,42,None,None,None,1), - (5,47,"GtkLabel","label_allowed_extensions",46,None,None,None,None), - (5,48,"GtkEntry","entry_allowed_extensions",46,None,None,None,1), - (5,49,"GtkBox",None,42,None,None,None,2), - (5,50,"GtkLabel","label_general_size_bytes",49,None,None,None,None), - (5,51,"GtkLabel","label_general_min_size",49,None,None,None,1), - (5,52,"GtkEntry","entry_general_minimal_size",49,None,None,None,2), - (5,53,"GtkLabel","label_general_max_size",49,None,None,None,3), - (5,54,"GtkEntry","entry_general_maximal_size",49,None,None,None,4), - (5,55,"GtkLabel",None,41,None,None,None,None), - (5,56,"GtkNotebook","notebook_main",4,None,None,None,1), - (5,57,"GtkNotebookPage",None,56,None,None,None,None), - (5,58,"GtkPaned",None,57,None,None,None,None), - (5,59,"GtkBox",None,58,None,None,None,None), - (5,60,"GtkBox",None,59,None,None,None,None), - (5,61,"GtkLabel","label_duplicate_check_method",60,None,None,None,None), - (5,62,"GtkComboBoxText","combo_box_duplicate_check_method",60,None,None,None,1), - (5,63,"GtkLabel","label_duplicate_hash_type",60,None,None,None,2), - (5,64,"GtkComboBoxText","combo_box_duplicate_hash_type",60,None,None,None,3), - (5,65,"GtkCheckButton","check_button_duplicate_case_sensitive_name",60,None,None,None,4), - (5,66,"GtkScrolledWindow","scrolled_window_duplicate_finder",59,None,None,None,1), - (5,67,"GtkImage","image_preview_duplicates",58,None,None,None,1), - (5,68,"GtkLabel",None,57,None,None,None,None), - (5,69,"GtkNotebookPage",None,56,None,None,None,1), - (5,70,"GtkScrolledWindow","scrolled_window_empty_folder_finder",69,None,None,None,None), - (5,71,"GtkLabel",None,69,None,None,None,None), - (5,72,"GtkNotebookPage",None,56,None,None,None,2), - (5,73,"GtkBox",None,72,None,None,None,None), - (5,74,"GtkBox",None,73,None,None,None,None), - (5,75,"GtkLabel","label_big_shown_files",74,None,None,None,2), - (5,76,"GtkEntry","entry_big_files_number",74,None,None,None,3), - (5,77,"GtkScrolledWindow","scrolled_window_big_files_finder",73,None,None,None,1), - (5,78,"GtkLabel",None,72,None,None,None,None), - (5,79,"GtkNotebookPage",None,56,None,None,None,3), - (5,80,"GtkScrolledWindow","scrolled_window_empty_files_finder",79,None,None,None,None), - (5,81,"GtkLabel",None,79,None,None,None,None), - (5,82,"GtkNotebookPage",None,56,None,None,None,4), - (5,83,"GtkScrolledWindow","scrolled_window_temporary_files_finder",82,None,None,None,None), - (5,84,"GtkLabel",None,82,None,None,None,None), - (5,85,"GtkNotebookPage",None,56,None,None,None,5), - (5,86,"GtkPaned",None,85,None,None,None,None), - (5,87,"GtkBox",None,86,None,None,None,None), - (5,88,"GtkBox",None,87,None,None,None,None), - (5,89,"GtkLabel","label_image_resize_algorithm",88,None,None,None,None), - (5,90,"GtkComboBoxText","combo_box_image_resize_algorithm",88,None,None,None,1), - (5,91,"GtkLabel","label_image_hash_size",88,None,None,None,2), - (5,92,"GtkComboBoxText","combo_box_image_hash_size",88,None,None,None,3), - (5,93,"GtkLabel","label_image_hash_type",88,None,None,None,4), - (5,94,"GtkComboBoxText","combo_box_image_hash_algorithm",88,None,None,None,5), - (5,95,"GtkBox",None,87,None,None,None,1), - (5,96,"GtkLabel","label_image_similarity",95,None,None,None,None), - (5,97,"GtkLabel","label_image_similarity_max",95,None,None,None,1), - (5,98,"GtkScale","scale_similarity_similar_images",95,None,None,None,2), - (5,99,"GtkLabel","label_similar_images_minimal_similarity",95,None,None,None,3), - (5,100,"GtkCheckButton","check_button_image_ignore_same_size",95,None,None,None,4), - (5,102,"GtkScrolledWindow","scrolled_window_similar_images_finder",87,None,None,None,2), - (5,103,"GtkImage","image_preview_similar_images",86,None,None,None,1), - (5,104,"GtkLabel",None,85,None,None,None,None), - (5,105,"GtkNotebookPage",None,56,None,None,None,6), - (5,106,"GtkBox",None,105,None,None,None,None), - (5,107,"GtkBox",None,106,None,None,None,None), - (5,108,"GtkLabel","label_video_similarity",107,None,None,None,None), - (5,109,"GtkLabel","label_video_similarity_max",107,None,None,None,1), - (5,110,"GtkScale","scale_similarity_similar_videos",107,None,None,None,2), - (5,111,"GtkLabel","label_video_similarity_min",107,None,None,None,3), - (5,112,"GtkCheckButton","check_button_video_ignore_same_size",107,None,None,None,4), - (5,113,"GtkScrolledWindow","scrolled_window_similar_videos_finder",106,None,None,None,1), - (5,114,"GtkLabel",None,105,None,None,None,None), - (5,115,"GtkNotebookPage",None,56,None,None,None,7), - (5,116,"GtkBox",None,115,None,None,None,None), - (5,117,"GtkBox",None,116,None,None,None,None), - (5,118,"GtkCheckButton","check_button_music_title",117,None,None,None,None), - (5,119,"GtkCheckButton","check_button_music_artist",117,None,None,None,1), - (5,120,"GtkCheckButton","check_button_music_year",117,None,None,None,2), - (5,121,"GtkCheckButton","check_button_music_bitrate",117,None,None,None,3), - (5,122,"GtkCheckButton","check_button_music_genre",117,None,None,None,4), - (5,123,"GtkCheckButton","check_button_music_length",117,None,None,None,5), - (5,124,"GtkBox",None,116,None,None,None,1), - (5,125,"GtkCheckButton","check_button_music_approximate_comparison",124,None,None,None,2), - (5,126,"GtkScrolledWindow","scrolled_window_same_music_finder",116,None,None,None,2), - (5,127,"GtkLabel",None,115,None,None,None,None), - (5,128,"GtkNotebookPage",None,56,None,None,None,8), - (5,129,"GtkScrolledWindow","scrolled_window_invalid_symlinks",128,None,None,None,None), - (5,130,"GtkLabel",None,128,None,None,None,None), - (5,131,"GtkNotebookPage",None,56,None,None,None,9), - (5,132,"GtkBox",None,131,None,None,None,None), - (5,133,"GtkLabel",None,131,None,None,None,None), - (5,134,"GtkNotebookPage",None,56,None,None,None,10), - (5,135,"GtkScrolledWindow","scrolled_window_bad_extensions",134,None,None,None,None), - (5,136,"GtkLabel",None,134,None,None,None,None), - (5,137,"GtkBox","buttons",3,None,None,None,1), - (5,138,"GtkBox",None,137,None,None,None,None), - (5,139,"GtkButton","buttons_search",138,None,None,None,None), - (5,140,"GtkBox",None,139,None,None,None,None), - (5,141,"GtkImage",None,140,None,None,None,None), - (5,142,"GtkLabel",None,140,None,None,None,1), - (5,176,"GtkBox",None,3,None,None,None,2), - (5,177,"GtkEntry","entry_info",176,None,None,None,None), - (5,178,"GtkEntry",None,176,None,None,None,1), - (5,179,"GtkScrolledWindow","scrolled_window_errors",3,None,None,None,3), - (5,180,"GtkTextView","text_view_errors",179,None,None,None,None), - (5,181,"GtkHeaderBar",None,2,None,"titlebar",None,None), - (5,182,"GtkBox",None,181,None,"end",None,None), - (5,183,"GtkButton","button_settings",182,None,None,None,None), - (5,184,"GtkImage",None,183,None,None,None,None), - (5,185,"GtkButton","button_app_info",182,None,None,None,1), - (5,186,"GtkImage",None,185,None,None,None,None), - (5,187,"GtkBox",None,137,None,None,None,4), - (5,188,"GtkBox",None,187,None,None,None,1), - (5,189,"GtkMenuButton","buttons_select",188,None,None,None,None), - (5,190,"GtkBox",None,189,None,None,None,None), - (5,191,"GtkImage",None,190,None,None,None,None), - (5,192,"GtkLabel","label_buttons_select",190,None,None,None,1), - (5,193,"GtkButton","buttons_compare",188,None,None,None,2), - (5,194,"GtkBox",None,193,None,None,None,None), - (5,195,"GtkImage",None,194,None,None,None,None), - (5,196,"GtkLabel",None,194,None,None,None,1), - (5,197,"GtkButton","buttons_delete",188,None,None,None,3), - (5,198,"GtkBox",None,197,None,None,None,None), - (5,199,"GtkImage",None,198,None,None,None,None), - (5,200,"GtkLabel",None,198,None,None,None,1), - (5,201,"GtkButton","buttons_move",188,None,None,None,4), - (5,202,"GtkBox",None,201,None,None,None,None), - (5,203,"GtkImage",None,202,None,None,None,None), - (5,204,"GtkLabel",None,202,None,None,None,1), - (5,205,"GtkButton","buttons_save",188,None,None,None,5), - (5,206,"GtkBox",None,205,None,None,None,None), - (5,207,"GtkImage",None,206,None,None,None,None), - (5,208,"GtkLabel",None,206,None,None,None,1), - (5,209,"GtkButton","buttons_symlink",188,None,None,None,6), - (5,210,"GtkBox",None,209,None,None,None,None), - (5,211,"GtkImage",None,210,None,None,None,None), - (5,212,"GtkLabel",None,210,None,None,None,1), - (5,213,"GtkButton","buttons_hardlink",188,None,None,None,7), - (5,214,"GtkBox",None,213,None,None,None,None), - (5,215,"GtkImage",None,214,None,None,None,None), - (5,216,"GtkLabel",None,214,None,None,None,1), - (5,217,"GtkButton","buttons_show_errors",187,None,None,None,2), - (5,218,"GtkImage",None,217,None,None,None,None), - (5,219,"GtkButton","buttons_show_upper_notebook",187,None,None,None,3), - (5,220,"GtkImage",None,219,None,None,None,None), - (5,221,"GtkComboBoxText","combo_box_big_files_mode",74,None,None,None,1), - (5,222,"GtkLabel","label_big_files_mode",74,None,None,None,None), - (5,223,"GtkScrolledWindow","scrolled_window_broken_files",132,None,None,None,1), - (5,224,"GtkBox",None,132,None,None,None,None), - (5,225,"GtkCheckButton","check_button_broken_files_audio",224,None,None,None,None), - (5,226,"GtkCheckButton","check_button_broken_files_pdf",224,None,None,None,1), - (5,227,"GtkCheckButton","check_button_broken_files_archive",224,None,None,None,2), - (5,228,"GtkCheckButton","check_button_broken_files_image",224,None,None,None,3), - (5,229,"GtkMenuButton","buttons_sort",188,None,None,None,1), - (5,230,"GtkBox",None,229,None,None,None,None), - (5,231,"GtkImage",None,230,None,None,None,None), - (5,232,"GtkLabel","label_buttons_sort",230,None,None,None,1), - (5,234,"GtkLabel","label_audio_check_type",124,None,None,None,None), - (5,235,"GtkComboBoxText","combo_box_audio_check_type",124,None,None,None,1), - (5,236,"GtkScale","scale_seconds_same_music",117,None,None,None,7), - (5,237,"GtkScale","scale_similarity_same_music",117,None,None,None,9), - (5,238,"GtkLabel","label_same_music_seconds",117,None,None,None,6), - (5,239,"GtkLabel","label_same_music_similarity",117,None,None,None,8), - (6,1,"GtkPopover","popover_right_click",None,None,None,None,None), - (6,2,"GtkBox",None,1,None,None,None,None), - (6,3,"GtkButton","buttons_popover_right_click_open_file",2,None,None,None,None), - (6,4,"GtkButton","buttons_popover_right_click_open_folder",2,None,None,None,1), - (7,1,"GtkPopover","popover_select",None,None,None,None,None), - (7,2,"GtkBox",None,1,None,None,None,None), - (7,3,"GtkButton","buttons_popover_select_custom",2,None,None,None,None), - (7,4,"GtkButton","buttons_popover_unselect_custom",2,None,None,None,1), - (7,5,"GtkSeparator","separator_select_custom",2,None,None,None,2), - (7,6,"GtkButton","buttons_popover_select_all_images_except_biggest",2,None,None,None,3), - (7,7,"GtkButton","buttons_popover_select_all_images_except_smallest",2,None,None,None,4), - (7,8,"GtkSeparator","separator_select_image_size",2,None,None,None,5), - (7,9,"GtkButton","buttons_popover_select_all_except_oldest",2,None,None,None,6), - (7,10,"GtkButton","buttons_popover_select_all_except_newest",2,None,None,None,7), - (7,11,"GtkButton","buttons_popover_select_one_oldest",2,None,None,None,8), - (7,12,"GtkButton","buttons_popover_select_one_newest",2,None,None,None,9), - (7,13,"GtkSeparator","separator_select_date",2,None,None,None,10), - (7,14,"GtkButton","buttons_popover_reverse",2,None,None,None,11), - (7,15,"GtkSeparator","separator_select_reverse",2,None,None,None,12), - (7,16,"GtkButton","buttons_popover_select_all",2,None,None,None,13), - (7,17,"GtkButton","buttons_popover_unselect_all",2,None,None,None,14), - (8,15,"GtkDialog","window_progress",None,None,None,None,None), - (8,16,"GtkBox",None,15,None,None,None,None), - (8,17,"GtkGrid","grid_progress_stages",16,None,None,None,None), - (8,18,"GtkLabel","label_progress_all_stages",17,None,None,None,None), - (8,19,"GtkProgressBar","progress_bar_all_stages",17,None,None,None,1), - (8,20,"GtkLabel","label_progress_current_stage",17,None,None,None,2), - (8,21,"GtkProgressBar","progress_bar_current_stage",17,None,None,None,3), - (8,22,"GtkLabel","label_stage",16,None,None,None,1), - (8,23,"GtkButton","button_stop_in_dialog",16,None,None,None,2), - (8,24,"GtkBox",None,23,None,None,None,None), - (8,25,"GtkImage",None,24,None,None,None,None), - (8,26,"GtkLabel",None,24,None,None,None,1), - (9,1,"GtkDialog","window_settings",None,None,None,None,None), - (9,3,"GtkBox","potatoo",1,None,None,None,None), - (9,6,"GtkNotebook","notebook_settings",3,None,None,None,1), - (9,7,"GtkNotebookPage",None,6,None,None,None,None), - (9,8,"GtkBox",None,7,None,None,None,None), - (9,9,"GtkBox",None,8,None,None,None,None), - (9,10,"GtkBox",None,9,None,None,None,None), - (9,11,"GtkLabel","label_settings_general_language",10,None,None,None,None), - (9,12,"GtkComboBoxText","combo_box_settings_language",10,None,None,None,1), - (9,13,"GtkCheckButton","check_button_settings_load_at_start",9,None,None,None,1), - (9,14,"GtkCheckButton","check_button_settings_save_at_exit",9,None,None,None,2), - (9,15,"GtkCheckButton","check_button_settings_confirm_deletion",9,None,None,None,3), - (9,16,"GtkCheckButton","check_button_settings_confirm_link",9,None,None,None,4), - (9,17,"GtkCheckButton","check_button_settings_confirm_group_deletion",9,None,None,None,5), - (9,18,"GtkCheckButton","check_button_settings_show_text_view",9,None,None,None,6), - (9,19,"GtkCheckButton","check_button_settings_use_cache",9,None,None,None,7), - (9,20,"GtkCheckButton","check_button_settings_save_also_json",9,None,None,None,8), - (9,21,"GtkCheckButton","check_button_settings_use_trash",9,None,None,None,9), - (9,22,"GtkBox",None,8,None,None,None,2), - (9,23,"GtkButton","button_settings_open_cache_folder",22,None,None,None,None), - (9,24,"GtkButton","button_settings_open_settings_folder",22,None,None,None,1), - (9,25,"GtkLabel",None,7,None,None,None,None), - (9,26,"GtkNotebookPage",None,6,None,None,None,1), - (9,27,"GtkBox",None,26,None,None,None,None), - (9,28,"GtkCheckButton","check_button_settings_hide_hard_links",27,None,None,None,None), - (9,29,"GtkCheckButton","check_button_settings_show_preview_duplicates",27,None,None,None,1), - (9,30,"GtkCheckButton","check_button_settings_duplicates_delete_outdated_cache",27,None,None,None,8), - (9,31,"GtkBox",None,27,None,None,None,3), - (9,32,"GtkLabel","label_settings_duplicate_minimal_size_cache",31,None,None,None,None), - (9,33,"GtkEntry","entry_settings_cache_file_minimal_size",31,None,None,None,1), - (9,34,"GtkCheckButton","check_button_duplicates_use_prehash_cache",27,None,None,None,4), - (9,35,"GtkButton","button_settings_duplicates_clear_cache",27,None,None,None,9), - (9,36,"GtkBox",None,27,None,None,None,6), - (9,37,"GtkLabel","label_settings_duplicate_minimal_size_cache_prehash",36,None,None,None,None), - (9,38,"GtkEntry","entry_settings_prehash_cache_file_minimal_size",36,None,None,None,1), - (9,39,"GtkLabel",None,26,None,None,None,None), - (9,40,"GtkNotebookPage",None,6,None,None,None,2), - (9,41,"GtkBox",None,40,None,None,None,None), - (9,42,"GtkCheckButton","check_button_settings_show_preview_similar_images",41,None,None,None,None), - (9,43,"GtkCheckButton","check_button_settings_similar_images_delete_outdated_cache",41,None,None,None,1), - (9,44,"GtkButton","button_settings_similar_images_clear_cache",41,None,None,None,2), - (9,45,"GtkLabel",None,40,None,None,None,None), - (9,46,"GtkNotebookPage",None,6,None,None,None,3), - (9,47,"GtkBox",None,46,None,None,None,None), - (9,48,"GtkButton","button_settings_similar_videos_clear_cache",47,None,None,None,3), - (9,49,"GtkCheckButton","check_button_settings_similar_videos_delete_outdated_cache",47,None,None,None,1), - (9,50,"GtkLabel",None,46,None,None,None,None), - (9,51,"GtkBox",None,3,None,None,None,2), - (9,52,"GtkButton","button_settings_load_configuration",51,None,None,None,None), - (9,53,"GtkButton","button_settings_reset_configuration",51,None,None,None,1), - (9,54,"GtkButton","button_settings_save_configuration",51,None,None,None,2), - (9,55,"GtkCheckButton","check_button_settings_one_filesystem",9,None,None,None,10), - (9,56,"GtkBox",None,9,None,None,None,11), - (9,57,"GtkLabel","label_settings_number_of_threads",56,None,None,None,None), - (9,58,"GtkScale","scale_settings_number_of_threads",56,None,None,None,1), - (9,59,"GtkLabel","label_restart_needed",8,None,None,None,1), - (10,1,"GtkPopover","popover_sort",None,None,None,None,None), - (10,2,"GtkBox",None,1,None,None,None,None), - (10,3,"GtkButton","buttons_popover_sort_file_name",2,None,None,None,None), - (10,4,"GtkButton","buttons_popover_sort_folder_name",2,None,None,None,1), - (10,5,"GtkButton","buttons_popover_sort_full_name",2,None,None,None,2), - (10,6,"GtkButton","buttons_popover_sort_size",2,None,None,None,3), - (10,7,"GtkButton","buttons_popover_sort_selection",2,None,None,None,4) + (3,1,"GtkAboutDialog","about_dialog",None,None,None,None,None,None), + (4,1,"GtkDialog","window_compare",None,None,None,None,None,None), + (4,2,"GtkBox",None,1,None,None,None,None,None), + (4,3,"GtkBox",None,2,None,None,None,None,None), + (4,4,"GtkLabel","label_group_info",3,None,None,None,1,None), + (4,5,"GtkButton","button_go_next_compare_group",3,None,None,None,2,None), + (4,6,"GtkImage",None,5,None,None,None,None,None), + (4,7,"GtkButton","button_go_previous_compare_group",3,None,None,None,None,None), + (4,8,"GtkImage",None,7,None,None,None,None,None), + (4,9,"GtkBox",None,2,None,None,None,1,None), + (4,10,"GtkCheckButton","check_button_left_preview_text",9,None,None,None,None,None), + (4,11,"GtkCheckButton","check_button_right_preview_text",9,None,None,None,1,None), + (4,12,"GtkBox",None,2,None,None,None,2,None), + (4,13,"GtkImage","image_compare_left",12,None,None,None,None,None), + (4,14,"GtkImage","image_compare_right",12,None,None,None,1,None), + (4,15,"GtkScrolledWindow","scrolled_window_compare_choose_images",2,None,None,None,3,None), + (5,1,"GtkAdjustment","adjustment1",None,None,None,None,None,None), + (5,2,"GtkWindow","window_main",None,None,None,None,None,None), + (5,3,"GtkBox",None,2,None,None,None,None,None), + (5,4,"GtkPaned",None,3,None,None,None,None,None), + (5,5,"GtkNotebook","notebook_upper",4,None,None,None,None,None), + (5,6,"GtkNotebookPage",None,5,None,None,None,None,None), + (5,7,"GtkBox","notebook_upper_included_directories",6,None,None,None,None,None), + (5,8,"GtkBox",None,7,None,None,None,None,None), + (5,9,"GtkButton","buttons_add_included_directory",8,None,None,None,None,None), + (5,10,"GtkBox",None,9,None,None,None,None,None), + (5,11,"GtkImage",None,10,None,None,None,None,None), + (5,12,"GtkLabel",None,10,None,None,None,1,None), + (5,13,"GtkButton","buttons_remove_included_directory",8,None,None,None,1,None), + (5,14,"GtkBox",None,13,None,None,None,None,None), + (5,15,"GtkImage",None,14,None,None,None,None,None), + (5,16,"GtkLabel",None,14,None,None,None,1,None), + (5,17,"GtkButton","buttons_manual_add_included_directory",8,None,None,None,2,None), + (5,18,"GtkBox",None,17,None,None,None,None,None), + (5,19,"GtkImage",None,18,None,None,None,None,None), + (5,20,"GtkLabel",None,18,None,None,None,1,None), + (5,21,"GtkScrolledWindow","scrolled_window_included_directories",7,None,None,None,1,None), + (5,22,"GtkCheckButton","check_button_recursive",7,None,None,None,2,None), + (5,23,"GtkLabel",None,6,None,None,None,None,None), + (5,24,"GtkNotebookPage",None,5,None,None,None,1,None), + (5,25,"GtkBox","notebook_upper_excluded_directories",24,None,None,None,None,None), + (5,26,"GtkBox",None,25,None,None,None,None,None), + (5,27,"GtkButton","buttons_add_excluded_directory",26,None,None,None,None,None), + (5,28,"GtkBox",None,27,None,None,None,None,None), + (5,29,"GtkImage",None,28,None,None,None,None,None), + (5,30,"GtkLabel",None,28,None,None,None,1,None), + (5,31,"GtkButton","buttons_remove_excluded_directory",26,None,None,None,1,None), + (5,32,"GtkBox",None,31,None,None,None,None,None), + (5,33,"GtkImage",None,32,None,None,None,None,None), + (5,34,"GtkLabel",None,32,None,None,None,1,None), + (5,35,"GtkButton","buttons_manual_add_excluded_directory",26,None,None,None,2,None), + (5,36,"GtkBox",None,35,None,None,None,None,None), + (5,37,"GtkImage",None,36,None,None,None,None,None), + (5,38,"GtkLabel",None,36,None,None,None,1,None), + (5,39,"GtkScrolledWindow","scrolled_window_excluded_directories",25,None,None,None,1,None), + (5,40,"GtkLabel",None,24,None,None,None,None,None), + (5,41,"GtkNotebookPage",None,5,None,None,None,2,None), + (5,42,"GtkBox","notebook_upper_excluded_items",41,None,None,None,None,None), + (5,43,"GtkBox",None,42,None,None,None,None,None), + (5,44,"GtkLabel","label_excluded_items",43,None,None,None,None,None), + (5,45,"GtkEntry","entry_excluded_items",43,None,None,None,1,None), + (5,46,"GtkBox",None,42,None,None,None,1,None), + (5,47,"GtkLabel","label_allowed_extensions",46,None,None,None,None,None), + (5,48,"GtkEntry","entry_allowed_extensions",46,None,None,None,1,None), + (5,49,"GtkBox",None,42,None,None,None,2,None), + (5,50,"GtkLabel","label_general_size_bytes",49,None,None,None,None,None), + (5,51,"GtkLabel","label_general_min_size",49,None,None,None,1,None), + (5,52,"GtkEntry","entry_general_minimal_size",49,None,None,None,2,None), + (5,53,"GtkLabel","label_general_max_size",49,None,None,None,3,None), + (5,54,"GtkEntry","entry_general_maximal_size",49,None,None,None,4,None), + (5,55,"GtkLabel",None,41,None,None,None,None,None), + (5,56,"GtkNotebook","notebook_main",4,None,None,None,1,None), + (5,57,"GtkNotebookPage",None,56,None,None,None,None,None), + (5,58,"GtkPaned",None,57,None,None,None,None,None), + (5,59,"GtkBox",None,58,None,None,None,None,None), + (5,60,"GtkBox",None,59,None,None,None,None,None), + (5,61,"GtkLabel","label_duplicate_check_method",60,None,None,None,None,None), + (5,62,"GtkComboBoxText","combo_box_duplicate_check_method",60,None,None,None,1,None), + (5,63,"GtkLabel","label_duplicate_hash_type",60,None,None,None,2,None), + (5,64,"GtkComboBoxText","combo_box_duplicate_hash_type",60,None,None,None,3,None), + (5,65,"GtkCheckButton","check_button_duplicate_case_sensitive_name",60,None,None,None,4,None), + (5,66,"GtkScrolledWindow","scrolled_window_duplicate_finder",59,None,None,None,1,None), + (5,67,"GtkImage","image_preview_duplicates",58,None,None,None,1,None), + (5,68,"GtkLabel",None,57,None,None,None,None,None), + (5,69,"GtkNotebookPage",None,56,None,None,None,1,None), + (5,70,"GtkScrolledWindow","scrolled_window_empty_folder_finder",69,None,None,None,None,None), + (5,71,"GtkLabel",None,69,None,None,None,None,None), + (5,72,"GtkNotebookPage",None,56,None,None,None,2,None), + (5,73,"GtkBox",None,72,None,None,None,None,None), + (5,74,"GtkBox",None,73,None,None,None,None,None), + (5,75,"GtkLabel","label_big_shown_files",74,None,None,None,2,None), + (5,76,"GtkEntry","entry_big_files_number",74,None,None,None,3,None), + (5,77,"GtkScrolledWindow","scrolled_window_big_files_finder",73,None,None,None,1,None), + (5,78,"GtkLabel",None,72,None,None,None,None,None), + (5,79,"GtkNotebookPage",None,56,None,None,None,3,None), + (5,80,"GtkScrolledWindow","scrolled_window_empty_files_finder",79,None,None,None,None,None), + (5,81,"GtkLabel",None,79,None,None,None,None,None), + (5,82,"GtkNotebookPage",None,56,None,None,None,4,None), + (5,83,"GtkScrolledWindow","scrolled_window_temporary_files_finder",82,None,None,None,None,None), + (5,84,"GtkLabel",None,82,None,None,None,None,None), + (5,85,"GtkNotebookPage",None,56,None,None,None,5,None), + (5,86,"GtkPaned",None,85,None,None,None,None,None), + (5,87,"GtkBox",None,86,None,None,None,None,None), + (5,88,"GtkBox",None,87,None,None,None,None,None), + (5,89,"GtkLabel","label_image_resize_algorithm",88,None,None,None,None,None), + (5,90,"GtkComboBoxText","combo_box_image_resize_algorithm",88,None,None,None,1,None), + (5,91,"GtkLabel","label_image_hash_size",88,None,None,None,2,None), + (5,92,"GtkComboBoxText","combo_box_image_hash_size",88,None,None,None,3,None), + (5,93,"GtkLabel","label_image_hash_type",88,None,None,None,4,None), + (5,94,"GtkComboBoxText","combo_box_image_hash_algorithm",88,None,None,None,5,None), + (5,95,"GtkBox",None,87,None,None,None,1,None), + (5,96,"GtkLabel","label_image_similarity",95,None,None,None,None,None), + (5,97,"GtkLabel","label_image_similarity_max",95,None,None,None,1,None), + (5,98,"GtkScale","scale_similarity_similar_images",95,None,None,None,2,None), + (5,99,"GtkLabel","label_similar_images_minimal_similarity",95,None,None,None,3,None), + (5,100,"GtkCheckButton","check_button_image_ignore_same_size",95,None,None,None,4,None), + (5,102,"GtkScrolledWindow","scrolled_window_similar_images_finder",87,None,None,None,2,None), + (5,103,"GtkImage","image_preview_similar_images",86,None,None,None,1,None), + (5,104,"GtkLabel",None,85,None,None,None,None,None), + (5,105,"GtkNotebookPage",None,56,None,None,None,6,None), + (5,106,"GtkBox",None,105,None,None,None,None,None), + (5,107,"GtkBox",None,106,None,None,None,None,None), + (5,108,"GtkLabel","label_video_similarity",107,None,None,None,None,None), + (5,109,"GtkLabel","label_video_similarity_max",107,None,None,None,1,None), + (5,110,"GtkScale","scale_similarity_similar_videos",107,None,None,None,2,None), + (5,111,"GtkLabel","label_video_similarity_min",107,None,None,None,3,None), + (5,112,"GtkCheckButton","check_button_video_ignore_same_size",107,None,None,None,4,None), + (5,113,"GtkScrolledWindow","scrolled_window_similar_videos_finder",106,None,None,None,1,None), + (5,114,"GtkLabel",None,105,None,None,None,None,None), + (5,115,"GtkNotebookPage",None,56,None,None,None,7,None), + (5,116,"GtkBox",None,115,None,None,None,None,None), + (5,117,"GtkBox",None,116,None,None,None,None,None), + (5,118,"GtkCheckButton","check_button_music_title",117,None,None,None,None,None), + (5,119,"GtkCheckButton","check_button_music_artist",117,None,None,None,1,None), + (5,120,"GtkCheckButton","check_button_music_year",117,None,None,None,2,None), + (5,121,"GtkCheckButton","check_button_music_bitrate",117,None,None,None,3,None), + (5,122,"GtkCheckButton","check_button_music_genre",117,None,None,None,4,None), + (5,123,"GtkCheckButton","check_button_music_length",117,None,None,None,5,None), + (5,124,"GtkBox",None,116,None,None,None,1,None), + (5,125,"GtkCheckButton","check_button_music_approximate_comparison",124,None,None,None,2,None), + (5,126,"GtkScrolledWindow","scrolled_window_same_music_finder",116,None,None,None,2,None), + (5,127,"GtkLabel",None,115,None,None,None,None,None), + (5,128,"GtkNotebookPage",None,56,None,None,None,8,None), + (5,129,"GtkScrolledWindow","scrolled_window_invalid_symlinks",128,None,None,None,None,None), + (5,130,"GtkLabel",None,128,None,None,None,None,None), + (5,131,"GtkNotebookPage",None,56,None,None,None,9,None), + (5,132,"GtkBox",None,131,None,None,None,None,None), + (5,133,"GtkLabel",None,131,None,None,None,None,None), + (5,134,"GtkNotebookPage",None,56,None,None,None,10,None), + (5,135,"GtkScrolledWindow","scrolled_window_bad_extensions",134,None,None,None,None,None), + (5,136,"GtkLabel",None,134,None,None,None,None,None), + (5,137,"GtkBox","buttons",3,None,None,None,1,None), + (5,138,"GtkBox",None,137,None,None,None,None,None), + (5,139,"GtkButton","buttons_search",138,None,None,None,None,None), + (5,140,"GtkBox",None,139,None,None,None,None,None), + (5,141,"GtkImage",None,140,None,None,None,None,None), + (5,142,"GtkLabel",None,140,None,None,None,1,None), + (5,176,"GtkBox",None,3,None,None,None,2,None), + (5,177,"GtkEntry","entry_info",176,None,None,None,None,None), + (5,178,"GtkEntry",None,176,None,None,None,1,None), + (5,179,"GtkScrolledWindow","scrolled_window_errors",3,None,None,None,3,None), + (5,180,"GtkTextView","text_view_errors",179,None,None,None,None,None), + (5,181,"GtkHeaderBar",None,2,None,"titlebar",None,None,None), + (5,182,"GtkBox",None,181,None,"end",None,None,None), + (5,183,"GtkButton","button_settings",182,None,None,None,None,None), + (5,184,"GtkImage",None,183,None,None,None,None,None), + (5,185,"GtkButton","button_app_info",182,None,None,None,1,None), + (5,186,"GtkImage",None,185,None,None,None,None,None), + (5,187,"GtkBox",None,137,None,None,None,4,None), + (5,188,"GtkBox",None,187,None,None,None,1,None), + (5,189,"GtkMenuButton","buttons_select",188,None,None,None,None,None), + (5,190,"GtkBox",None,189,None,None,None,None,None), + (5,191,"GtkImage",None,190,None,None,None,None,None), + (5,192,"GtkLabel","label_buttons_select",190,None,None,None,1,None), + (5,193,"GtkButton","buttons_compare",188,None,None,None,2,None), + (5,194,"GtkBox",None,193,None,None,None,None,None), + (5,195,"GtkImage",None,194,None,None,None,None,None), + (5,196,"GtkLabel",None,194,None,None,None,1,None), + (5,197,"GtkButton","buttons_delete",188,None,None,None,3,None), + (5,198,"GtkBox",None,197,None,None,None,None,None), + (5,199,"GtkImage",None,198,None,None,None,None,None), + (5,200,"GtkLabel",None,198,None,None,None,1,None), + (5,201,"GtkButton","buttons_move",188,None,None,None,4,None), + (5,202,"GtkBox",None,201,None,None,None,None,None), + (5,203,"GtkImage",None,202,None,None,None,None,None), + (5,204,"GtkLabel",None,202,None,None,None,1,None), + (5,205,"GtkButton","buttons_save",188,None,None,None,5,None), + (5,206,"GtkBox",None,205,None,None,None,None,None), + (5,207,"GtkImage",None,206,None,None,None,None,None), + (5,208,"GtkLabel",None,206,None,None,None,1,None), + (5,209,"GtkButton","buttons_symlink",188,None,None,None,6,None), + (5,210,"GtkBox",None,209,None,None,None,None,None), + (5,211,"GtkImage",None,210,None,None,None,None,None), + (5,212,"GtkLabel",None,210,None,None,None,1,None), + (5,213,"GtkButton","buttons_hardlink",188,None,None,None,7,None), + (5,214,"GtkBox",None,213,None,None,None,None,None), + (5,215,"GtkImage",None,214,None,None,None,None,None), + (5,216,"GtkLabel",None,214,None,None,None,1,None), + (5,217,"GtkButton","buttons_show_errors",187,None,None,None,2,None), + (5,218,"GtkImage",None,217,None,None,None,None,None), + (5,219,"GtkButton","buttons_show_upper_notebook",187,None,None,None,3,None), + (5,220,"GtkImage",None,219,None,None,None,None,None), + (5,221,"GtkComboBoxText","combo_box_big_files_mode",74,None,None,None,1,None), + (5,222,"GtkLabel","label_big_files_mode",74,None,None,None,None,None), + (5,223,"GtkScrolledWindow","scrolled_window_broken_files",132,None,None,None,1,None), + (5,224,"GtkBox",None,132,None,None,None,None,None), + (5,225,"GtkCheckButton","check_button_broken_files_audio",224,None,None,None,None,None), + (5,226,"GtkCheckButton","check_button_broken_files_pdf",224,None,None,None,1,None), + (5,227,"GtkCheckButton","check_button_broken_files_archive",224,None,None,None,2,None), + (5,228,"GtkCheckButton","check_button_broken_files_image",224,None,None,None,3,None), + (5,229,"GtkMenuButton","buttons_sort",188,None,None,None,1,None), + (5,230,"GtkBox",None,229,None,None,None,None,None), + (5,231,"GtkImage",None,230,None,None,None,None,None), + (5,232,"GtkLabel","label_buttons_sort",230,None,None,None,1,None), + (5,234,"GtkLabel","label_audio_check_type",124,None,None,None,None,None), + (5,235,"GtkComboBoxText","combo_box_audio_check_type",124,None,None,None,1,None), + (5,236,"GtkScale","scale_seconds_same_music",117,None,None,None,7,None), + (5,237,"GtkScale","scale_similarity_same_music",117,None,None,None,9,None), + (5,238,"GtkLabel","label_same_music_seconds",117,None,None,None,6,None), + (5,239,"GtkLabel","label_same_music_similarity",117,None,None,None,8,None), + (6,1,"GtkPopover","popover_right_click",None,None,None,None,None,None), + (6,2,"GtkBox",None,1,None,None,None,None,None), + (6,3,"GtkButton","buttons_popover_right_click_open_file",2,None,None,None,None,None), + (6,4,"GtkButton","buttons_popover_right_click_open_folder",2,None,None,None,1,None), + (7,1,"GtkPopover","popover_select",None,None,None,None,None,None), + (7,2,"GtkBox",None,1,None,None,None,None,None), + (7,3,"GtkButton","buttons_popover_select_custom",2,None,None,None,None,None), + (7,4,"GtkButton","buttons_popover_unselect_custom",2,None,None,None,1,None), + (7,5,"GtkSeparator","separator_select_custom",2,None,None,None,2,None), + (7,6,"GtkButton","buttons_popover_select_all_images_except_biggest",2,None,None,None,3,None), + (7,7,"GtkButton","buttons_popover_select_all_images_except_smallest",2,None,None,None,4,None), + (7,8,"GtkSeparator","separator_select_image_size",2,None,None,None,5,None), + (7,9,"GtkButton","buttons_popover_select_all_except_oldest",2,None,None,None,6,None), + (7,10,"GtkButton","buttons_popover_select_all_except_newest",2,None,None,None,7,None), + (7,11,"GtkButton","buttons_popover_select_one_oldest",2,None,None,None,8,None), + (7,12,"GtkButton","buttons_popover_select_one_newest",2,None,None,None,9,None), + (7,13,"GtkSeparator","separator_select_date",2,None,None,None,10,None), + (7,14,"GtkButton","buttons_popover_reverse",2,None,None,None,11,None), + (7,15,"GtkSeparator","separator_select_reverse",2,None,None,None,12,None), + (7,16,"GtkButton","buttons_popover_select_all",2,None,None,None,13,None), + (7,17,"GtkButton","buttons_popover_unselect_all",2,None,None,None,14,None), + (8,15,"GtkDialog","window_progress",None,None,None,None,None,None), + (8,16,"GtkBox",None,15,None,None,None,None,None), + (8,17,"GtkGrid","grid_progress_stages",16,None,None,None,None,None), + (8,18,"GtkLabel","label_progress_all_stages",17,None,None,None,None,None), + (8,19,"GtkProgressBar","progress_bar_all_stages",17,None,None,None,1,None), + (8,20,"GtkLabel","label_progress_current_stage",17,None,None,None,2,None), + (8,21,"GtkProgressBar","progress_bar_current_stage",17,None,None,None,3,None), + (8,22,"GtkLabel","label_stage",16,None,None,None,1,None), + (8,23,"GtkButton","button_stop_in_dialog",16,None,None,None,2,None), + (8,24,"GtkBox",None,23,None,None,None,None,None), + (8,25,"GtkImage",None,24,None,None,None,None,None), + (8,26,"GtkLabel",None,24,None,None,None,1,None), + (9,1,"GtkDialog","window_settings",None,None,None,None,None,None), + (9,3,"GtkBox","potatoo",1,None,None,None,None,None), + (9,6,"GtkNotebook","notebook_settings",3,None,None,None,1,None), + (9,7,"GtkNotebookPage",None,6,None,None,None,None,None), + (9,8,"GtkBox",None,7,None,None,None,None,None), + (9,9,"GtkBox",None,8,None,None,None,None,None), + (9,10,"GtkBox",None,9,None,None,None,None,None), + (9,11,"GtkLabel","label_settings_general_language",10,None,None,None,None,None), + (9,12,"GtkComboBoxText","combo_box_settings_language",10,None,None,None,1,None), + (9,13,"GtkCheckButton","check_button_settings_load_at_start",9,None,None,None,1,None), + (9,14,"GtkCheckButton","check_button_settings_save_at_exit",9,None,None,None,2,None), + (9,15,"GtkCheckButton","check_button_settings_confirm_deletion",9,None,None,None,3,None), + (9,16,"GtkCheckButton","check_button_settings_confirm_link",9,None,None,None,4,None), + (9,17,"GtkCheckButton","check_button_settings_confirm_group_deletion",9,None,None,None,5,None), + (9,18,"GtkCheckButton","check_button_settings_show_text_view",9,None,None,None,6,None), + (9,19,"GtkCheckButton","check_button_settings_use_cache",9,None,None,None,7,None), + (9,20,"GtkCheckButton","check_button_settings_save_also_json",9,None,None,None,8,None), + (9,21,"GtkCheckButton","check_button_settings_use_trash",9,None,None,None,9,None), + (9,22,"GtkBox",None,8,None,None,None,2,None), + (9,23,"GtkButton","button_settings_open_cache_folder",22,None,None,None,None,None), + (9,24,"GtkButton","button_settings_open_settings_folder",22,None,None,None,1,None), + (9,25,"GtkLabel",None,7,None,None,None,None,None), + (9,26,"GtkNotebookPage",None,6,None,None,None,1,None), + (9,27,"GtkBox",None,26,None,None,None,None,None), + (9,28,"GtkCheckButton","check_button_settings_hide_hard_links",27,None,None,None,None,None), + (9,29,"GtkCheckButton","check_button_settings_show_preview_duplicates",27,None,None,None,1,None), + (9,30,"GtkCheckButton","check_button_settings_duplicates_delete_outdated_cache",27,None,None,None,8,None), + (9,31,"GtkBox",None,27,None,None,None,3,None), + (9,32,"GtkLabel","label_settings_duplicate_minimal_size_cache",31,None,None,None,None,None), + (9,33,"GtkEntry","entry_settings_cache_file_minimal_size",31,None,None,None,1,None), + (9,34,"GtkCheckButton","check_button_duplicates_use_prehash_cache",27,None,None,None,4,None), + (9,35,"GtkButton","button_settings_duplicates_clear_cache",27,None,None,None,9,None), + (9,36,"GtkBox",None,27,None,None,None,6,None), + (9,37,"GtkLabel","label_settings_duplicate_minimal_size_cache_prehash",36,None,None,None,None,None), + (9,38,"GtkEntry","entry_settings_prehash_cache_file_minimal_size",36,None,None,None,1,None), + (9,39,"GtkLabel",None,26,None,None,None,None,None), + (9,40,"GtkNotebookPage",None,6,None,None,None,2,None), + (9,41,"GtkBox",None,40,None,None,None,None,None), + (9,42,"GtkCheckButton","check_button_settings_show_preview_similar_images",41,None,None,None,None,None), + (9,43,"GtkCheckButton","check_button_settings_similar_images_delete_outdated_cache",41,None,None,None,1,None), + (9,44,"GtkButton","button_settings_similar_images_clear_cache",41,None,None,None,2,None), + (9,45,"GtkLabel",None,40,None,None,None,None,None), + (9,46,"GtkNotebookPage",None,6,None,None,None,3,None), + (9,47,"GtkBox",None,46,None,None,None,None,None), + (9,48,"GtkButton","button_settings_similar_videos_clear_cache",47,None,None,None,3,None), + (9,49,"GtkCheckButton","check_button_settings_similar_videos_delete_outdated_cache",47,None,None,None,1,None), + (9,50,"GtkLabel",None,46,None,None,None,None,None), + (9,51,"GtkBox",None,3,None,None,None,2,None), + (9,52,"GtkButton","button_settings_load_configuration",51,None,None,None,None,None), + (9,53,"GtkButton","button_settings_reset_configuration",51,None,None,None,1,None), + (9,54,"GtkButton","button_settings_save_configuration",51,None,None,None,2,None), + (9,55,"GtkCheckButton","check_button_settings_one_filesystem",9,None,None,None,10,None), + (9,56,"GtkBox",None,9,None,None,None,11,None), + (9,57,"GtkLabel","label_settings_number_of_threads",56,None,None,None,None,None), + (9,58,"GtkScale","scale_settings_number_of_threads",56,None,None,None,1,None), + (9,59,"GtkLabel","label_restart_needed",8,None,None,None,1,None), + (10,1,"GtkPopover","popover_sort",None,None,None,None,None,None), + (10,2,"GtkBox",None,1,None,None,None,None,None), + (10,3,"GtkButton","buttons_popover_sort_file_name",2,None,None,None,None,None), + (10,4,"GtkButton","buttons_popover_sort_folder_name",2,None,None,None,1,None), + (10,5,"GtkButton","buttons_popover_sort_full_name",2,None,None,None,2,None), + (10,6,"GtkButton","buttons_popover_sort_size",2,None,None,None,3,None), + (10,7,"GtkButton","buttons_popover_sort_selection",2,None,None,None,4,None) - (3,1,"GtkAboutDialog","comments","2020 - 2023 Rafał Mikrut(qarmin)\n\nThis program is free to use and will always be.\n",1,None,None,None,None), - (3,1,"GtkAboutDialog","license-type","mit-x11",None,None,None,None,None), - (3,1,"GtkAboutDialog","logo-icon-name","help-about-symbolic",None,None,None,None,None), - (3,1,"GtkAboutDialog","program-name","Czkawka",None,None,None,None,None), - (3,1,"GtkAboutDialog","version","6.0.0",None,None,None,None,None), - (4,2,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (4,2,"GtkWidget","vexpand","1",None,None,None,None,None), - (4,4,"GtkLabel","label","Group XD/PER XD (99 images in current group)",1,None,None,None,None), - (4,4,"GtkWidget","halign","center",None,None,None,None,None), - (4,4,"GtkWidget","hexpand","1",None,None,None,None,None), - (4,5,"GtkWidget","focusable","1",None,None,None,None,None), - (4,5,"GtkWidget","receives-default","1",None,None,None,None,None), - (4,6,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (4,7,"GtkWidget","focusable","1",None,None,None,None,None), - (4,7,"GtkWidget","receives-default","1",None,None,None,None,None), - (4,8,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (4,9,"GtkBox","homogeneous","1",None,None,None,None,None), - (4,10,"GtkCheckButton","label","First Game",1,None,None,None,None), - (4,10,"GtkWidget","focusable","1",None,None,None,None,None), - (4,11,"GtkCheckButton","label","Second Game",1,None,None,None,None), - (4,11,"GtkWidget","focusable","1",None,None,None,None,None), - (4,12,"GtkBox","homogeneous","1",None,None,None,None,None), - (4,12,"GtkWidget","vexpand","1",None,None,None,None,None), - (4,13,"GtkWidget","height-request","100",None,None,None,None,None), - (4,14,"GtkWidget","height-request","100",None,None,None,None,None), - (4,15,"GtkScrolledWindow","max-content-height","150",None,None,None,None,None), - (4,15,"GtkScrolledWindow","min-content-height","150",None,None,None,None,None), - (4,15,"GtkWidget","focusable","1",None,None,None,None,None), - (5,1,"GtkAdjustment","page-increment","10",None,None,None,None,None), - (5,1,"GtkAdjustment","step-increment","1",None,None,None,None,None), - (5,1,"GtkAdjustment","upper","100",None,None,None,None,None), - (5,2,"GtkWindow","child",None,None,None,None,None,3), - (5,2,"GtkWindow","default-height","800",None,None,None,None,None), - (5,2,"GtkWindow","default-width","1100",None,None,None,None,None), - (5,3,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,4,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,4,"GtkPaned","resize-start-child","0",None,None,None,None,None), - (5,4,"GtkPaned","shrink-end-child","0",None,None,None,None,None), - (5,4,"GtkPaned","shrink-start-child","0",None,None,None,None,None), - (5,4,"GtkPaned","wide-handle","True",None,None,None,None,None), - (5,4,"GtkWidget","focusable","1",None,None,None,None,None), - (5,4,"GtkWidget","vexpand","1",None,None,None,None,None), - (5,5,"GtkWidget","focusable","1",None,None,None,None,None), - (5,6,"GtkNotebookPage","child",None,None,None,None,None,7), - (5,6,"GtkNotebookPage","tab",None,None,None,None,None,23), - (5,6,"GtkNotebookPage","tab-fill","False",None,None,None,None,None), - (5,7,"GtkBox","spacing","5",None,None,None,None,None), - (5,7,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,7,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,8,"GtkBox","spacing","1",None,None,None,None,None), - (5,8,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,8,"GtkWidget","halign","center",None,None,None,None,None), - (5,8,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,9,"GtkWidget","focusable","1",None,None,None,None,None), - (5,9,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,10,"GtkBox","spacing","4",None,None,None,None,None), - (5,10,"GtkWidget","halign","center",None,None,None,None,None), - (5,11,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,12,"GtkLabel","label","Add ",1,None,None,None,None), - (5,13,"GtkWidget","focusable","1",None,None,None,None,None), - (5,13,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,14,"GtkBox","spacing","4",None,None,None,None,None), - (5,14,"GtkWidget","halign","center",None,None,None,None,None), - (5,15,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,16,"GtkLabel","label","Remove ",1,None,None,None,None), - (5,17,"GtkWidget","focusable","1",None,None,None,None,None), - (5,17,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,17,"GtkWidget","valign","center",None,None,None,None,None), - (5,18,"GtkBox","spacing","4",None,None,None,None,None), - (5,18,"GtkWidget","halign","center",None,None,None,None,None), - (5,19,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,20,"GtkLabel","label","Manual Add",1,None,None,None,None), - (5,21,"GtkWidget","focusable","1",None,None,None,None,None), - (5,21,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,22,"GtkCheckButton","active","1",None,None,None,None,None), - (5,22,"GtkCheckButton","label","Recursive",1,None,None,None,None), - (5,22,"GtkWidget","focusable","1",None,None,None,None,None), - (5,22,"GtkWidget","halign","center",None,None,None,None,None), - (5,22,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,23,"GtkLabel","label","Included Directories",1,None,None,None,None), - (5,24,"GtkNotebookPage","child",None,None,None,None,None,25), - (5,24,"GtkNotebookPage","position","1",None,None,None,None,None), - (5,24,"GtkNotebookPage","tab",None,None,None,None,None,40), - (5,25,"GtkBox","spacing","6",None,None,None,None,None), - (5,25,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,25,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,26,"GtkBox","spacing","1",None,None,None,None,None), - (5,26,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,26,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,27,"GtkWidget","focusable","1",None,None,None,None,None), - (5,27,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,28,"GtkBox","spacing","4",None,None,None,None,None), - (5,28,"GtkWidget","halign","center",None,None,None,None,None), - (5,29,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,30,"GtkLabel","label","Add ",1,None,None,None,None), - (5,31,"GtkWidget","focusable","1",None,None,None,None,None), - (5,31,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,32,"GtkBox","spacing","4",None,None,None,None,None), - (5,32,"GtkWidget","halign","center",None,None,None,None,None), - (5,33,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,34,"GtkLabel","label","Remove ",1,None,None,None,None), - (5,35,"GtkWidget","focusable","1",None,None,None,None,None), - (5,35,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,35,"GtkWidget","valign","center",None,None,None,None,None), - (5,36,"GtkBox","spacing","4",None,None,None,None,None), - (5,36,"GtkWidget","halign","center",None,None,None,None,None), - (5,37,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,38,"GtkLabel","label","Manual Add",1,None,None,None,None), - (5,39,"GtkWidget","focusable","1",None,None,None,None,None), - (5,39,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,40,"GtkLabel","label","Excluded Directories",1,None,None,None,None), - (5,41,"GtkNotebookPage","child",None,None,None,None,None,42), - (5,41,"GtkNotebookPage","position","2",None,None,None,None,None), - (5,41,"GtkNotebookPage","tab",None,None,None,None,None,55), - (5,42,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,42,"GtkWidget","valign","center",None,None,None,None,None), - (5,43,"GtkBox","spacing","5",None,None,None,None,None), - (5,43,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,43,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,44,"GtkLabel","label","Excluded items",1,None,None,None,None), - (5,45,"GtkEditable","text","*/.git,*/node_modules,*/lost+found",1,None,None,None,None), - (5,45,"GtkWidget","focusable","1",None,None,None,None,None), - (5,45,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,46,"GtkBox","spacing","5",None,None,None,None,None), - (5,46,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,46,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,47,"GtkLabel","label","Allowed Extensions",1,None,None,None,None), - (5,48,"GtkWidget","focusable","1",None,None,None,None,None), - (5,48,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,49,"GtkBox","spacing","8",None,None,None,None,None), - (5,49,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,49,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,50,"GtkLabel","label","File Size(bytes)",1,None,None,None,None), - (5,51,"GtkLabel","label","Min:",1,None,None,None,None), - (5,52,"GtkEditable","text","8192",1,None,None,None,None), - (5,52,"GtkEntry","input-purpose","number",None,None,None,None,None), - (5,52,"GtkEntry","max-length","15",None,None,None,None,None), - (5,52,"GtkWidget","focusable","1",None,None,None,None,None), - (5,52,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,53,"GtkLabel","label","Max:",1,None,None,None,None), - (5,54,"GtkEditable","text","1099512000000",1,None,None,None,None), - (5,54,"GtkEntry","input-purpose","number",None,None,None,None,None), - (5,54,"GtkEntry","max-length","15",None,None,None,None,None), - (5,54,"GtkWidget","focusable","1",None,None,None,None,None), - (5,54,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,55,"GtkLabel","label","Items Configuration",1,None,None,None,None), - (5,56,"GtkNotebook","scrollable","1",None,None,None,None,None), - (5,56,"GtkNotebook","tab-pos","left",None,None,None,None,None), - (5,56,"GtkWidget","focusable","1",None,None,None,None,None), - (5,57,"GtkNotebookPage","child",None,None,None,None,None,58), - (5,57,"GtkNotebookPage","tab",None,None,None,None,None,68), - (5,58,"GtkPaned","wide-handle","True",None,None,None,None,None), - (5,59,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,59,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,60,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,60,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,60,"GtkWidget","margin-top","2",None,None,None,None,None), - (5,61,"GtkLabel","label","Check method",1,None,None,None,None), - (5,61,"GtkWidget","margin-end","3",None,None,None,None,None), - (5,63,"GtkLabel","label","Hash type",1,None,None,None,None), - (5,63,"GtkWidget","margin-end","2",None,None,None,None,None), - (5,63,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,65,"GtkCheckButton","label","Case sensitive",None,None,None,None,None), - (5,65,"GtkWidget","visible","0",None,None,None,None,None), - (5,66,"GtkWidget","focusable","1",None,None,None,None,None), - (5,66,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,66,"GtkWidget","vexpand","1",None,None,None,None,None), - (5,67,"GtkWidget","height-request","100",None,None,None,None,None), - (5,67,"GtkWidget","hexpand","True",None,None,None,None,None), - (5,67,"GtkWidget","hexpand-set","True",None,None,None,None,None), - (5,67,"GtkWidget","vexpand","True",None,None,None,None,None), - (5,67,"GtkWidget","vexpand-set","True",None,None,None,None,None), - (5,67,"GtkWidget","width-request","100",None,None,None,None,None), - (5,68,"GtkLabel","label","Duplicates files",1,None,None,None,None), - (5,69,"GtkNotebookPage","child",None,None,None,None,None,70), - (5,69,"GtkNotebookPage","position","1",None,None,None,None,None), - (5,69,"GtkNotebookPage","tab",None,None,None,None,None,71), - (5,70,"GtkWidget","focusable","1",None,None,None,None,None), - (5,71,"GtkLabel","label","Empty Directories",1,None,None,None,None), - (5,72,"GtkNotebookPage","child",None,None,None,None,None,73), - (5,72,"GtkNotebookPage","position","2",None,None,None,None,None), - (5,72,"GtkNotebookPage","tab",None,None,None,None,None,78), - (5,73,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,74,"GtkBox","spacing","8",None,None,None,None,None), - (5,74,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,74,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,74,"GtkWidget","margin-top","2",None,None,None,None,None), - (5,75,"GtkLabel","label","Number of shown files",1,None,None,None,None), - (5,76,"GtkEditable","text","50",1,None,None,None,None), - (5,76,"GtkEntry","input-purpose","number",None,None,None,None,None), - (5,76,"GtkEntry","max-length","15",None,None,None,None,None), - (5,76,"GtkWidget","focusable","1",None,None,None,None,None), - (5,76,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,77,"GtkWidget","focusable","1",None,None,None,None,None), - (5,77,"GtkWidget","vexpand","1",None,None,None,None,None), - (5,78,"GtkLabel","label","Big Files",1,None,None,None,None), - (5,79,"GtkNotebookPage","child",None,None,None,None,None,80), - (5,79,"GtkNotebookPage","position","3",None,None,None,None,None), - (5,79,"GtkNotebookPage","tab",None,None,None,None,None,81), - (5,80,"GtkWidget","focusable","1",None,None,None,None,None), - (5,81,"GtkLabel","label","Empty Files",1,None,None,None,None), - (5,82,"GtkNotebookPage","child",None,None,None,None,None,83), - (5,82,"GtkNotebookPage","position","4",None,None,None,None,None), - (5,82,"GtkNotebookPage","tab",None,None,None,None,None,84), - (5,83,"GtkWidget","focusable","1",None,None,None,None,None), - (5,84,"GtkLabel","label","Temporary Files",1,None,None,None,None), - (5,85,"GtkNotebookPage","child",None,None,None,None,None,86), - (5,85,"GtkNotebookPage","position","5",None,None,None,None,None), - (5,85,"GtkNotebookPage","tab",None,None,None,None,None,104), - (5,86,"GtkPaned","wide-handle","True",None,None,None,None,None), - (5,87,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,87,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,88,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,88,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,88,"GtkWidget","margin-top","2",None,None,None,None,None), - (5,89,"GtkLabel","label","Resize algorithm",1,None,None,None,None), - (5,89,"GtkWidget","margin-end","2",None,None,None,None,None), - (5,91,"GtkLabel","label","Hash size:",1,None,None,None,None), - (5,91,"GtkWidget","margin-end","2",None,None,None,None,None), - (5,91,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,93,"GtkLabel","label","Hash type:",1,None,None,None,None), - (5,93,"GtkWidget","margin-end","2",None,None,None,None,None), - (5,93,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,95,"GtkWidget","margin-bottom","2",None,None,None,None,None), - (5,95,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,95,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,96,"GtkLabel","label","Similarity ",1,None,None,None,None), - (5,97,"GtkLabel","label"," Very High ",1,None,None,None,None), - (5,98,"GtkRange","fill-level","100",None,None,None,None,None), - (5,98,"GtkRange","round-digits","1",None,None,None,None,None), - (5,98,"GtkScale","digits","0",None,None,None,None,None), - (5,98,"GtkScale","draw-value","1",None,None,None,None,None), - (5,98,"GtkScale","value-pos","right",None,None,None,None,None), - (5,98,"GtkWidget","focusable","1",None,None,None,None,None), - (5,98,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,99,"GtkLabel","label","Minimal",1,None,None,None,None), - (5,99,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,99,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,100,"GtkCheckButton","label","Ignore same size",1,None,None,None,None), - (5,100,"GtkWidget","focusable","1",None,None,None,None,None), - (5,100,"GtkWidget","margin-start","7",None,None,None,None,None), - (5,102,"GtkWidget","focusable","1",None,None,None,None,None), - (5,102,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,102,"GtkWidget","vexpand","1",None,None,None,None,None), - (5,103,"GtkWidget","height-request","100",None,None,None,None,None), - (5,103,"GtkWidget","hexpand","True",None,None,None,None,None), - (5,103,"GtkWidget","hexpand-set","True",None,None,None,None,None), - (5,103,"GtkWidget","vexpand","True",None,None,None,None,None), - (5,103,"GtkWidget","vexpand-set","True",None,None,None,None,None), - (5,103,"GtkWidget","width-request","100",None,None,None,None,None), - (5,104,"GtkLabel","label","Similar Images",1,None,None,None,None), - (5,105,"GtkNotebookPage","child",None,None,None,None,None,106), - (5,105,"GtkNotebookPage","position","6",None,None,None,None,None), - (5,105,"GtkNotebookPage","tab",None,None,None,None,None,114), - (5,106,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,107,"GtkWidget","margin-bottom","2",None,None,None,None,None), - (5,107,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,107,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,108,"GtkLabel","label","Similarity ",1,None,None,None,None), - (5,109,"GtkLabel","label"," Very High ",1,None,None,None,None), - (5,110,"GtkRange","fill-level","100",None,None,None,None,None), - (5,110,"GtkRange","round-digits","0",None,None,None,None,None), - (5,110,"GtkScale","digits","0",None,None,None,None,None), - (5,110,"GtkScale","draw-value","1",None,None,None,None,None), - (5,110,"GtkScale","value-pos","right",None,None,None,None,None), - (5,110,"GtkWidget","focusable","1",None,None,None,None,None), - (5,110,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,111,"GtkLabel","label","Minimal",1,None,None,None,None), - (5,111,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,111,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,112,"GtkCheckButton","label","Ignore same size",1,None,None,None,None), - (5,112,"GtkWidget","focusable","1",None,None,None,None,None), - (5,112,"GtkWidget","margin-start","7",None,None,None,None,None), - (5,113,"GtkWidget","focusable","1",None,None,None,None,None), - (5,113,"GtkWidget","vexpand","1",None,None,None,None,None), - (5,114,"GtkLabel","label","Similar Videos",1,None,None,None,None), - (5,115,"GtkNotebookPage","child",None,None,None,None,None,116), - (5,115,"GtkNotebookPage","position","7",None,None,None,None,None), - (5,115,"GtkNotebookPage","tab",None,None,None,None,None,127), - (5,116,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,117,"GtkBox","spacing","8",None,None,None,None,None), - (5,117,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,117,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,118,"GtkCheckButton","active","1",None,None,None,None,None), - (5,118,"GtkCheckButton","label","Title",1,None,None,None,None), - (5,118,"GtkWidget","focusable","1",None,None,None,None,None), - (5,119,"GtkCheckButton","active","1",None,None,None,None,None), - (5,119,"GtkCheckButton","label","Artist",1,None,None,None,None), - (5,119,"GtkWidget","focusable","1",None,None,None,None,None), - (5,120,"GtkCheckButton","label","Year",None,None,None,None,None), - (5,120,"GtkWidget","focusable","1",None,None,None,None,None), - (5,120,"GtkWidget","halign","start",None,None,None,None,None), - (5,121,"GtkCheckButton","label","Bitrate",None,None,None,None,None), - (5,121,"GtkWidget","focusable","1",None,None,None,None,None), - (5,122,"GtkCheckButton","label","Genre",None,None,None,None,None), - (5,122,"GtkWidget","focusable","1",None,None,None,None,None), - (5,123,"GtkCheckButton","label","Length",None,None,None,None,None), - (5,123,"GtkWidget","focusable","1",None,None,None,None,None), - (5,124,"GtkWidget","margin-bottom","2",None,None,None,None,None), - (5,124,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,124,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,125,"GtkCheckButton","label","Approximate Comparison",1,None,None,None,None), - (5,125,"GtkWidget","focusable","1",None,None,None,None,None), - (5,126,"GtkWidget","focusable","1",None,None,None,None,None), - (5,126,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,126,"GtkWidget","vexpand","1",None,None,None,None,None), - (5,127,"GtkLabel","label","Music Duplicates",1,None,None,None,None), - (5,128,"GtkNotebookPage","child",None,None,None,None,None,129), - (5,128,"GtkNotebookPage","position","8",None,None,None,None,None), - (5,128,"GtkNotebookPage","tab",None,None,None,None,None,130), - (5,129,"GtkWidget","focusable","1",None,None,None,None,None), - (5,130,"GtkLabel","label","Invalid Symlinks",1,None,None,None,None), - (5,131,"GtkNotebookPage","child",None,None,None,None,None,132), - (5,131,"GtkNotebookPage","position","9",None,None,None,None,None), - (5,131,"GtkNotebookPage","tab",None,None,None,None,None,133), - (5,132,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (5,132,"GtkWidget","focusable","1",None,None,None,None,None), - (5,133,"GtkLabel","label","Broken Files",1,None,None,None,None), - (5,134,"GtkNotebookPage","child",None,None,None,None,None,135), - (5,134,"GtkNotebookPage","position","10",None,None,None,None,None), - (5,134,"GtkNotebookPage","tab",None,None,None,None,None,136), - (5,135,"GtkWidget","focusable","1",None,None,None,None,None), - (5,136,"GtkLabel","label","Bad Extensions",1,None,None,None,None), - (5,137,"GtkWidget","margin-bottom","5",None,None,None,None,None), - (5,137,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,137,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,137,"GtkWidget","margin-top","2",None,None,None,None,None), - (5,138,"GtkBox","spacing","2",None,None,None,None,None), - (5,139,"GtkWidget","focusable","1",None,None,None,None,None), - (5,139,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,140,"GtkBox","spacing","2",None,None,None,None,None), - (5,140,"GtkWidget","halign","center",None,None,None,None,None), - (5,141,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,142,"GtkLabel","label","Search",1,None,None,None,None), - (5,176,"GtkWidget","margin-bottom","5",None,None,None,None,None), - (5,176,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,176,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,176,"GtkWidget","margin-top","5",None,None,None,None,None), - (5,177,"GtkEditable","editable","0",None,None,None,None,None), - (5,177,"GtkEntry","has-frame","0",None,None,None,None,None), - (5,177,"GtkWidget","focusable","1",None,None,None,None,None), - (5,177,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,178,"GtkEditable","editable","0",None,None,None,None,None), - (5,178,"GtkEditable","text","Czkawka 6.0.0",1,None,None,None,None), - (5,178,"GtkEditable","xalign","1",None,None,None,None,None), - (5,178,"GtkEntry","has-frame","0",None,None,None,None,None), - (5,178,"GtkWidget","focusable","1",None,None,None,None,None), - (5,179,"GtkScrolledWindow","child",None,None,None,None,None,180), - (5,179,"GtkScrolledWindow","min-content-height","100",None,None,None,None,None), - (5,179,"GtkWidget","focusable","1",None,None,None,None,None), - (5,180,"GtkTextView","editable","0",None,None,None,None,None), - (5,180,"GtkTextView","monospace","1",None,None,None,None,None), - (5,180,"GtkWidget","focusable","1",None,None,None,None,None), - (5,182,"GtkBox","spacing","5",None,None,None,None,None), - (5,183,"GtkWidget","focusable","1",None,None,None,None,None), - (5,183,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,184,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,185,"GtkWidget","focusable","1",None,None,None,None,None), - (5,185,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,186,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,187,"GtkWidget","halign","end",None,None,None,None,None), - (5,187,"GtkWidget","hexpand","True",None,None,None,None,None), - (5,188,"GtkBox","spacing","2",None,None,None,None,None), - (5,188,"GtkWidget","halign","end",None,None,None,None,None), - (5,189,"GtkWidget","focus-on-click","0",None,None,None,None,None), - (5,189,"GtkWidget","focusable","1",None,None,None,None,None), - (5,189,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,190,"GtkBox","spacing","2",None,None,None,None,None), - (5,190,"GtkWidget","halign","center",None,None,None,None,None), - (5,191,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,192,"GtkLabel","label","SelectMenu",None,None,None,None,None), - (5,193,"GtkWidget","focusable","1",None,None,None,None,None), - (5,193,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,194,"GtkBox","spacing","2",None,None,None,None,None), - (5,194,"GtkWidget","halign","center",None,None,None,None,None), - (5,195,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,196,"GtkLabel","label","Compare",1,None,None,None,None), - (5,197,"GtkWidget","focusable","1",None,None,None,None,None), - (5,197,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,198,"GtkBox","spacing","2",None,None,None,None,None), - (5,198,"GtkWidget","halign","center",None,None,None,None,None), - (5,199,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,200,"GtkLabel","label","Delete",1,None,None,None,None), - (5,201,"GtkWidget","focusable","1",None,None,None,None,None), - (5,201,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,202,"GtkBox","spacing","2",None,None,None,None,None), - (5,202,"GtkWidget","halign","center",None,None,None,None,None), - (5,203,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,204,"GtkLabel","label","Move",1,None,None,None,None), - (5,205,"GtkWidget","focusable","1",None,None,None,None,None), - (5,205,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,206,"GtkBox","spacing","2",None,None,None,None,None), - (5,206,"GtkWidget","halign","center",None,None,None,None,None), - (5,207,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,208,"GtkLabel","label","Save",1,None,None,None,None), - (5,209,"GtkWidget","focusable","1",None,None,None,None,None), - (5,209,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,210,"GtkBox","spacing","2",None,None,None,None,None), - (5,210,"GtkWidget","halign","center",None,None,None,None,None), - (5,211,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,212,"GtkLabel","label","Symlink",1,None,None,None,None), - (5,213,"GtkWidget","focusable","1",None,None,None,None,None), - (5,213,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,214,"GtkBox","spacing","2",None,None,None,None,None), - (5,214,"GtkWidget","halign","center",None,None,None,None,None), - (5,215,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,216,"GtkLabel","label","Hardlink",1,None,None,None,None), - (5,217,"GtkWidget","focusable","1",None,None,None,None,None), - (5,217,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,218,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,218,"GtkWidget","halign","center",None,None,None,None,None), - (5,219,"GtkWidget","focusable","1",None,None,None,None,None), - (5,219,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,220,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,220,"GtkWidget","halign","center",None,None,None,None,None), - (5,222,"GtkLabel","label","VVV",None,None,None,None,None), - (5,223,"GtkWidget","vexpand","True",None,None,None,None,None), - (5,225,"GtkCheckButton","active","True",None,None,None,None,None), - (5,225,"GtkCheckButton","label","Audio",None,None,None,None,None), - (5,226,"GtkCheckButton","active","True",None,None,None,None,None), - (5,226,"GtkCheckButton","label","PDF",None,None,None,None,None), - (5,227,"GtkCheckButton","active","True",None,None,None,None,None), - (5,227,"GtkCheckButton","label","Archive",None,None,None,None,None), - (5,228,"GtkCheckButton","active","True",None,None,None,None,None), - (5,228,"GtkCheckButton","label","Image",None,None,None,None,None), - (5,229,"GtkWidget","focus-on-click","0",None,None,None,None,None), - (5,229,"GtkWidget","focusable","1",None,None,None,None,None), - (5,229,"GtkWidget","receives-default","1",None,None,None,None,None), - (5,230,"GtkBox","spacing","2",None,None,None,None,None), - (5,230,"GtkWidget","halign","center",None,None,None,None,None), - (5,231,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (5,232,"GtkLabel","label","SortMenu",None,None,None,None,None), - (5,234,"GtkLabel","label","Audio check type",None,None,None,None,None), - (5,234,"GtkWidget","margin-end","2",None,None,None,None,None), - (5,236,"GtkRange","fill-level","100",None,None,None,None,None), - (5,236,"GtkRange","round-digits","1",None,None,None,None,None), - (5,236,"GtkScale","digits","0",None,None,None,None,None), - (5,236,"GtkScale","draw-value","1",None,None,None,None,None), - (5,236,"GtkScale","value-pos","right",None,None,None,None,None), - (5,236,"GtkWidget","focusable","1",None,None,None,None,None), - (5,236,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,237,"GtkRange","fill-level","100",None,None,None,None,None), - (5,237,"GtkRange","round-digits","1",None,None,None,None,None), - (5,237,"GtkScale","digits","0",None,None,None,None,None), - (5,237,"GtkScale","draw-value","1",None,None,None,None,None), - (5,237,"GtkScale","value-pos","right",None,None,None,None,None), - (5,237,"GtkWidget","focusable","1",None,None,None,None,None), - (5,237,"GtkWidget","hexpand","1",None,None,None,None,None), - (5,238,"GtkLabel","label","Minimal fragment second duration",None,None,None,None,None), - (5,238,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,238,"GtkWidget","margin-start","5",None,None,None,None,None), - (5,239,"GtkLabel","label","Max difference",None,None,None,None,None), - (5,239,"GtkWidget","margin-end","5",None,None,None,None,None), - (5,239,"GtkWidget","margin-start","5",None,None,None,None,None), - (6,1,"GtkPopover","child",None,None,None,None,None,2), - (6,1,"GtkPopover","position","left",None,None,None,None,None), - (6,2,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (6,3,"GtkButton","label","Open File",1,None,None,None,None), - (6,3,"GtkWidget","focusable","1",None,None,None,None,None), - (6,3,"GtkWidget","receives-default","1",None,None,None,None,None), - (6,4,"GtkButton","label","Open Folder",1,None,None,None,None), - (6,4,"GtkWidget","focusable","1",None,None,None,None,None), - (6,4,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,1,"GtkPopover","child",None,None,None,None,None,2), - (7,2,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (7,3,"GtkButton","label","Select custom",1,None,None,None,None), - (7,3,"GtkWidget","focusable","1",None,None,None,None,None), - (7,3,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,4,"GtkButton","label","Unselect custom",1,None,None,None,None), - (7,4,"GtkWidget","focusable","1",None,None,None,None,None), - (7,4,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,6,"GtkButton","label","Select all except biggest",1,None,None,None,None), - (7,6,"GtkWidget","focusable","1",None,None,None,None,None), - (7,6,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,7,"GtkButton","label","Select all except smallest",1,None,None,None,None), - (7,7,"GtkWidget","focusable","1",None,None,None,None,None), - (7,7,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,9,"GtkButton","label","Select all except oldest",1,None,None,None,None), - (7,9,"GtkWidget","focusable","1",None,None,None,None,None), - (7,9,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,10,"GtkButton","label","Select all except newest",1,None,None,None,None), - (7,10,"GtkWidget","focusable","1",None,None,None,None,None), - (7,10,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,11,"GtkButton","label","Select one oldest",1,None,None,None,None), - (7,11,"GtkWidget","focusable","1",None,None,None,None,None), - (7,11,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,12,"GtkButton","label","Select one newest",1,None,None,None,None), - (7,12,"GtkWidget","focusable","1",None,None,None,None,None), - (7,12,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,14,"GtkButton","label","Reverse Selection",1,None,None,None,None), - (7,14,"GtkWidget","focusable","1",None,None,None,None,None), - (7,14,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,16,"GtkButton","label","Select All",1,None,None,None,None), - (7,16,"GtkWidget","focusable","1",None,None,None,None,None), - (7,16,"GtkWidget","receives-default","1",None,None,None,None,None), - (7,17,"GtkButton","label","Unselect All",1,None,None,None,None), - (7,17,"GtkWidget","focusable","1",None,None,None,None,None), - (7,17,"GtkWidget","receives-default","1",None,None,None,None,None), - (8,16,"GtkBox","spacing","10",None,None,None,None,None), - (8,16,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (8,16,"GtkWidget","margin-bottom","10",None,None,None,None,None), - (8,16,"GtkWidget","margin-end","10",None,None,None,None,None), - (8,16,"GtkWidget","margin-start","10",None,None,None,None,None), - (8,16,"GtkWidget","margin-top","10",None,None,None,None,None), - (8,17,"GtkWidget","margin-end","2",None,None,None,None,None), - (8,17,"GtkWidget","margin-start","2",None,None,None,None,None), - (8,17,"GtkWidget","margin-top","2",None,None,None,None,None), - (8,17,"GtkWidget","valign","center",None,None,None,None,None), - (8,17,"GtkWidget","vexpand","1",None,None,None,None,None), - (8,18,"GtkLabel","label","All stages: ",1,None,None,None,None), - (8,18,"GtkWidget","name","label_progress_all_stages",None,None,None,None,None), - (8,19,"GtkProgressBar","pulse-step","0.099999999776482579",None,None,None,None,None), - (8,19,"GtkProgressBar","show-text","1",None,None,None,None,None), - (8,19,"GtkWidget","hexpand","1",None,None,None,None,None), - (8,20,"GtkLabel","label","Current stage: ",1,None,None,None,None), - (8,20,"GtkWidget","name","label_progress_current_stage",None,None,None,None,None), - (8,21,"GtkProgressBar","show-text","1",None,None,None,None,None), - (8,22,"GtkLabel","label","Stage 1/2",1,None,None,None,None), - (8,23,"GtkWidget","focusable","1",None,None,None,None,None), - (8,23,"GtkWidget","halign","end",None,None,None,None,None), - (8,23,"GtkWidget","margin-end","2",None,None,None,None,None), - (8,23,"GtkWidget","receives-default","1",None,None,None,None,None), - (8,23,"GtkWidget","valign","center",None,None,None,None,None), - (8,25,"GtkImage","icon-name","image-missing",None,None,None,None,None), - (8,26,"GtkLabel","label","Stop",1,None,None,None,None), - (8,26,"GtkWidget","hexpand","1",None,None,None,None,None), - (9,1,"GtkWindow","modal","1",None,None,None,None,None), - (9,1,"GtkWindow","title","Czkawka Options",1,None,None,None,None), - (9,3,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (9,3,"GtkWidget","vexpand","1",None,None,None,None,None), - (9,6,"GtkNotebook","tab-pos","left",None,None,None,None,None), - (9,6,"GtkWidget","focusable","1",None,None,None,None,None), - (9,6,"GtkWidget","vexpand","1",None,None,None,None,None), - (9,7,"GtkNotebookPage","child",None,None,None,None,None,8), - (9,7,"GtkNotebookPage","tab",None,None,None,None,None,25), - (9,8,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (9,8,"GtkWidget","margin-bottom","5",None,None,None,None,None), - (9,9,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (9,9,"GtkWidget","valign","center",None,None,None,None,None), - (9,11,"GtkLabel","label","Language",1,None,None,None,None), - (9,11,"GtkWidget","margin-end","10",None,None,None,None,None), - (9,11,"GtkWidget","margin-start","5",None,None,None,None,None), - (9,12,"GtkWidget","hexpand","1",None,None,None,None,None), - (9,13,"GtkCheckButton","active","1",None,None,None,None,None), - (9,13,"GtkCheckButton","label","Load configuration at start",1,None,None,None,None), - (9,13,"GtkWidget","focusable","1",None,None,None,None,None), - (9,14,"GtkCheckButton","active","1",None,None,None,None,None), - (9,14,"GtkCheckButton","label","Save configuration at exit",1,None,None,None,None), - (9,14,"GtkWidget","focusable","1",None,None,None,None,None), - (9,15,"GtkCheckButton","active","1",None,None,None,None,None), - (9,15,"GtkCheckButton","label","Show confirm dialog when deleting any files",1,None,None,None,None), - (9,15,"GtkWidget","focusable","1",None,None,None,None,None), - (9,16,"GtkCheckButton","active","1",None,None,None,None,None), - (9,16,"GtkCheckButton","label","Show confirm dialog when hard/symlinks any files",1,None,None,None,None), - (9,16,"GtkWidget","focusable","1",None,None,None,None,None), - (9,17,"GtkCheckButton","active","1",None,None,None,None,None), - (9,17,"GtkCheckButton","label","Show confirm dialog when deleting all files in group",1,None,None,None,None), - (9,17,"GtkWidget","focusable","1",None,None,None,None,None), - (9,18,"GtkCheckButton","active","1",None,None,None,None,None), - (9,18,"GtkCheckButton","label","Show bottom text panel",1,None,None,None,None), - (9,18,"GtkWidget","focusable","1",None,None,None,None,None), - (9,18,"GtkWidget","valign","center",None,None,None,None,None), - (9,19,"GtkCheckButton","active","1",None,None,None,None,None), - (9,19,"GtkCheckButton","label","Use cache",1,None,None,None,None), - (9,19,"GtkWidget","focusable","1",None,None,None,None,None), - (9,19,"GtkWidget","vexpand","1",None,None,None,None,None), - (9,20,"GtkCheckButton","active","1",None,None,None,None,None), - (9,20,"GtkCheckButton","label","Save cache also to JSON file",1,None,None,None,None), - (9,20,"GtkWidget","focusable","1",None,None,None,None,None), - (9,20,"GtkWidget","vexpand","1",None,None,None,None,None), - (9,21,"GtkCheckButton","active","1",None,None,None,None,None), - (9,21,"GtkCheckButton","label","Move deleted files to trash",1,None,None,None,None), - (9,21,"GtkWidget","focusable","1",None,None,None,None,None), - (9,22,"GtkWidget","valign","center",None,None,None,None,None), - (9,23,"GtkButton","label","Open cache folder",1,None,None,None,None), - (9,23,"GtkWidget","focusable","1",None,None,None,None,None), - (9,23,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,24,"GtkButton","label","Open settings folder",1,None,None,None,None), - (9,24,"GtkWidget","focusable","1",None,None,None,None,None), - (9,24,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,25,"GtkLabel","label","General",1,None,None,None,None), - (9,26,"GtkNotebookPage","child",None,None,None,None,None,27), - (9,26,"GtkNotebookPage","position","1",None,None,None,None,None), - (9,26,"GtkNotebookPage","tab",None,None,None,None,None,39), - (9,27,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (9,28,"GtkCheckButton","active","1",None,None,None,None,None), - (9,28,"GtkCheckButton","label","Hide hard links(only Linux and MacOS)",1,None,None,None,None), - (9,28,"GtkWidget","focusable","1",None,None,None,None,None), - (9,29,"GtkCheckButton","active","1",None,None,None,None,None), - (9,29,"GtkCheckButton","label","Show image preview",1,None,None,None,None), - (9,29,"GtkWidget","focusable","1",None,None,None,None,None), - (9,30,"GtkCheckButton","active","1",None,None,None,None,None), - (9,30,"GtkCheckButton","label","Delete outdated cache entries automatically",1,None,None,None,None), - (9,30,"GtkWidget","focusable","1",None,None,None,None,None), - (9,31,"GtkWidget","margin-end","4",None,None,None,None,None), - (9,31,"GtkWidget","margin-start","4",None,None,None,None,None), - (9,32,"GtkLabel","label","Minimal size of files in bytes saved to cache",1,None,None,None,None), - (9,32,"GtkWidget","hexpand","1",None,None,None,None,None), - (9,33,"GtkEditable","text","257144",1,None,None,None,None), - (9,33,"GtkEntry","input-purpose","number",None,None,None,None,None), - (9,33,"GtkEntry","max-length","15",None,None,None,None,None), - (9,33,"GtkWidget","focusable","1",None,None,None,None,None), - (9,33,"GtkWidget","halign","center",None,None,None,None,None), - (9,34,"GtkCheckButton","label","Use prehash cache",1,None,None,None,None), - (9,34,"GtkWidget","focusable","1",None,None,None,None,None), - (9,35,"GtkButton","label","Remove outdated results from duplicates cache",1,None,None,None,None), - (9,35,"GtkWidget","focusable","1",None,None,None,None,None), - (9,35,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,35,"GtkWidget","valign","center",None,None,None,None,None), - (9,36,"GtkWidget","margin-end","4",None,None,None,None,None), - (9,36,"GtkWidget","margin-start","4",None,None,None,None,None), - (9,37,"GtkLabel","label","Minimal size of files in bytes saved to prehash cache",1,None,None,None,None), - (9,37,"GtkWidget","hexpand","1",None,None,None,None,None), - (9,38,"GtkEditable","text","1",1,None,None,None,None), - (9,38,"GtkEntry","input-purpose","number",None,None,None,None,None), - (9,38,"GtkEntry","max-length","15",None,None,None,None,None), - (9,38,"GtkWidget","focusable","1",None,None,None,None,None), - (9,38,"GtkWidget","halign","center",None,None,None,None,None), - (9,39,"GtkLabel","label","Duplicate Finder",1,None,None,None,None), - (9,40,"GtkNotebookPage","child",None,None,None,None,None,41), - (9,40,"GtkNotebookPage","position","2",None,None,None,None,None), - (9,40,"GtkNotebookPage","tab",None,None,None,None,None,45), - (9,41,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (9,42,"GtkCheckButton","active","1",None,None,None,None,None), - (9,42,"GtkCheckButton","label","Show image preview",1,None,None,None,None), - (9,42,"GtkWidget","focusable","1",None,None,None,None,None), - (9,43,"GtkCheckButton","active","1",None,None,None,None,None), - (9,43,"GtkCheckButton","label","Delete outdated cache entries automatically",1,None,None,None,None), - (9,43,"GtkWidget","focusable","1",None,None,None,None,None), - (9,44,"GtkButton","label","Remove outdated results from images cache",1,None,None,None,None), - (9,44,"GtkWidget","focusable","1",None,None,None,None,None), - (9,44,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,44,"GtkWidget","valign","center",None,None,None,None,None), - (9,45,"GtkLabel","label","Similar Images",1,None,None,None,None), - (9,46,"GtkNotebookPage","child",None,None,None,None,None,47), - (9,46,"GtkNotebookPage","position","3",None,None,None,None,None), - (9,46,"GtkNotebookPage","tab",None,None,None,None,None,50), - (9,47,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (9,48,"GtkButton","label","Remove outdated results from videos cache",1,None,None,None,None), - (9,48,"GtkWidget","focusable","1",None,None,None,None,None), - (9,48,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,48,"GtkWidget","valign","center",None,None,None,None,None), - (9,49,"GtkCheckButton","label","Delete outdated cache entries automatically",1,None,None,None,None), - (9,49,"GtkWidget","focusable","1",None,None,None,None,None), - (9,50,"GtkLabel","label","Similar Videos",1,None,None,None,None), - (9,51,"GtkBox","spacing","3",None,None,None,None,None), - (9,51,"GtkWidget","margin-end","3",None,None,None,None,None), - (9,51,"GtkWidget","margin-start","3",None,None,None,None,None), - (9,52,"GtkButton","label","Load configuration",1,None,None,None,None), - (9,52,"GtkWidget","focusable","1",None,None,None,None,None), - (9,52,"GtkWidget","halign","center",None,None,None,None,None), - (9,52,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,53,"GtkButton","label","Reset configuration",1,None,None,None,None), - (9,53,"GtkWidget","focusable","1",None,None,None,None,None), - (9,53,"GtkWidget","halign","center",None,None,None,None,None), - (9,53,"GtkWidget","hexpand","1",None,None,None,None,None), - (9,53,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,54,"GtkButton","label","Save configuration",1,None,None,None,None), - (9,54,"GtkWidget","focusable","1",None,None,None,None,None), - (9,54,"GtkWidget","halign","center",None,None,None,None,None), - (9,54,"GtkWidget","receives-default","1",None,None,None,None,None), - (9,55,"GtkCheckButton","active","1",None,None,None,None,None), - (9,55,"GtkCheckButton","label","Exclude other filesystems(Linux)",None,None,None,None,None), - (9,55,"GtkWidget","focusable","1",None,None,None,None,None), - (9,57,"GtkAccessible","accessible-role","presentation",None,None,None,None,None), - (9,57,"GtkLabel","label","Number of used threads",None,None,None,None,None), - (9,57,"GtkLabel","wrap-mode","word-char",None,None,None,None,None), - (9,57,"GtkWidget","margin-start","5",None,None,None,None,None), - (9,58,"GtkRange","fill-level","100",None,None,None,None,None), - (9,58,"GtkRange","round-digits","1",None,None,None,None,None), - (9,58,"GtkScale","digits","0",None,None,None,None,None), - (9,58,"GtkScale","draw-value","True",None,None,None,None,None), - (9,58,"GtkScale","value-pos","right",None,None,None,None,None), - (9,58,"GtkWidget","focusable","1",None,None,None,None,None), - (9,58,"GtkWidget","hexpand","1",None,None,None,None,None), - (9,59,"GtkAccessible","accessible-role","menu-item-checkbox",None,None,None,None,None), - (9,59,"GtkLabel","label","Restart Required",None,None,None,None,None), - (9,59,"GtkWidget","margin-bottom","4",None,None,None,None,None), - (9,59,"GtkWidget","margin-top","5",None,None,None,None,None), - (10,1,"GtkPopover","child",None,None,None,None,None,2), - (10,1,"GtkPopover","position","top",None,None,None,None,None), - (10,2,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (10,3,"GtkButton","label","File name",None,None,None,None,None), - (10,3,"GtkWidget","focusable","1",None,None,None,None,None), - (10,3,"GtkWidget","receives-default","1",None,None,None,None,None), - (10,4,"GtkButton","label","Folder name",None,None,None,None,None), - (10,4,"GtkWidget","focusable","1",None,None,None,None,None), - (10,4,"GtkWidget","receives-default","1",None,None,None,None,None), - (10,5,"GtkButton","label","Full name",None,None,None,None,None), - (10,5,"GtkWidget","focusable","1",None,None,None,None,None), - (10,5,"GtkWidget","receives-default","1",None,None,None,None,None), - (10,6,"GtkButton","label","Size",None,None,None,None,None), - (10,6,"GtkWidget","focusable","1",None,None,None,None,None), - (10,6,"GtkWidget","receives-default","1",None,None,None,None,None), - (10,7,"GtkButton","label","Selection",None,None,None,None,None), - (10,7,"GtkWidget","focusable","1",None,None,None,None,None), - (10,7,"GtkWidget","receives-default","1",None,None,None,None,None) + (3,1,"GtkAboutDialog","comments","2020 - 2023 Rafał Mikrut(qarmin) + + This program is free to use and will always be. + ",1,None,None,None,None,None,None,None,None), + (3,1,"GtkAboutDialog","license-type","mit-x11",None,None,None,None,None,None,None,None,None), + (3,1,"GtkAboutDialog","logo-icon-name","help-about-symbolic",None,None,None,None,None,None,None,None,None), + (3,1,"GtkAboutDialog","program-name","Czkawka",None,None,None,None,None,None,None,None,None), + (3,1,"GtkAboutDialog","version","6.0.0",None,None,None,None,None,None,None,None,None), + (4,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (4,2,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (4,4,"GtkLabel","label","Group XD/PER XD (99 images in current group)",1,None,None,None,None,None,None,None,None), + (4,4,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (4,4,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (4,5,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (4,5,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (4,6,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (4,7,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (4,7,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (4,8,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (4,9,"GtkBox","homogeneous","1",None,None,None,None,None,None,None,None,None), + (4,10,"GtkCheckButton","label","First Game",1,None,None,None,None,None,None,None,None), + (4,10,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (4,11,"GtkCheckButton","label","Second Game",1,None,None,None,None,None,None,None,None), + (4,11,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (4,12,"GtkBox","homogeneous","1",None,None,None,None,None,None,None,None,None), + (4,12,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (4,13,"GtkWidget","height-request","100",None,None,None,None,None,None,None,None,None), + (4,14,"GtkWidget","height-request","100",None,None,None,None,None,None,None,None,None), + (4,15,"GtkScrolledWindow","max-content-height","150",None,None,None,None,None,None,None,None,None), + (4,15,"GtkScrolledWindow","min-content-height","150",None,None,None,None,None,None,None,None,None), + (4,15,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,1,"GtkAdjustment","page-increment","10",None,None,None,None,None,None,None,None,None), + (5,1,"GtkAdjustment","step-increment","1",None,None,None,None,None,None,None,None,None), + (5,1,"GtkAdjustment","upper","100",None,None,None,None,None,None,None,None,None), + (5,2,"GtkWindow","child",None,None,None,None,None,3,None,None,None,None), + (5,2,"GtkWindow","default-height","800",None,None,None,None,None,None,None,None,None), + (5,2,"GtkWindow","default-width","1100",None,None,None,None,None,None,None,None,None), + (5,3,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,4,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,4,"GtkPaned","resize-start-child","0",None,None,None,None,None,None,None,None,None), + (5,4,"GtkPaned","shrink-end-child","0",None,None,None,None,None,None,None,None,None), + (5,4,"GtkPaned","shrink-start-child","0",None,None,None,None,None,None,None,None,None), + (5,4,"GtkPaned","wide-handle","True",None,None,None,None,None,None,None,None,None), + (5,4,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,4,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (5,5,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,6,"GtkNotebookPage","child",None,None,None,None,None,7,None,None,None,None), + (5,6,"GtkNotebookPage","tab",None,None,None,None,None,23,None,None,None,None), + (5,6,"GtkNotebookPage","tab-fill","False",None,None,None,None,None,None,None,None,None), + (5,7,"GtkBox","spacing","5",None,None,None,None,None,None,None,None,None), + (5,7,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,7,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,8,"GtkBox","spacing","1",None,None,None,None,None,None,None,None,None), + (5,8,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,8,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,8,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,9,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,9,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,10,"GtkBox","spacing","4",None,None,None,None,None,None,None,None,None), + (5,10,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,11,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,12,"GtkLabel","label","Add ",1,None,None,None,None,None,None,None,None), + (5,13,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,13,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,14,"GtkBox","spacing","4",None,None,None,None,None,None,None,None,None), + (5,14,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,15,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,16,"GtkLabel","label","Remove ",1,None,None,None,None,None,None,None,None), + (5,17,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,17,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,17,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (5,18,"GtkBox","spacing","4",None,None,None,None,None,None,None,None,None), + (5,18,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,19,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,20,"GtkLabel","label","Manual Add",1,None,None,None,None,None,None,None,None), + (5,21,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,21,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,22,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (5,22,"GtkCheckButton","label","Recursive",1,None,None,None,None,None,None,None,None), + (5,22,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,22,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,22,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,23,"GtkLabel","label","Included Directories",1,None,None,None,None,None,None,None,None), + (5,24,"GtkNotebookPage","child",None,None,None,None,None,25,None,None,None,None), + (5,24,"GtkNotebookPage","position","1",None,None,None,None,None,None,None,None,None), + (5,24,"GtkNotebookPage","tab",None,None,None,None,None,40,None,None,None,None), + (5,25,"GtkBox","spacing","6",None,None,None,None,None,None,None,None,None), + (5,25,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,25,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,26,"GtkBox","spacing","1",None,None,None,None,None,None,None,None,None), + (5,26,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,26,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,27,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,27,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,28,"GtkBox","spacing","4",None,None,None,None,None,None,None,None,None), + (5,28,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,29,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,30,"GtkLabel","label","Add ",1,None,None,None,None,None,None,None,None), + (5,31,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,31,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,32,"GtkBox","spacing","4",None,None,None,None,None,None,None,None,None), + (5,32,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,33,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,34,"GtkLabel","label","Remove ",1,None,None,None,None,None,None,None,None), + (5,35,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,35,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,35,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (5,36,"GtkBox","spacing","4",None,None,None,None,None,None,None,None,None), + (5,36,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,37,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,38,"GtkLabel","label","Manual Add",1,None,None,None,None,None,None,None,None), + (5,39,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,39,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,40,"GtkLabel","label","Excluded Directories",1,None,None,None,None,None,None,None,None), + (5,41,"GtkNotebookPage","child",None,None,None,None,None,42,None,None,None,None), + (5,41,"GtkNotebookPage","position","2",None,None,None,None,None,None,None,None,None), + (5,41,"GtkNotebookPage","tab",None,None,None,None,None,55,None,None,None,None), + (5,42,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,42,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (5,43,"GtkBox","spacing","5",None,None,None,None,None,None,None,None,None), + (5,43,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,43,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,44,"GtkLabel","label","Excluded items",1,None,None,None,None,None,None,None,None), + (5,45,"GtkEditable","text","*/.git,*/node_modules,*/lost+found",1,None,None,None,None,None,None,None,None), + (5,45,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,45,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,46,"GtkBox","spacing","5",None,None,None,None,None,None,None,None,None), + (5,46,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,46,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,47,"GtkLabel","label","Allowed Extensions",1,None,None,None,None,None,None,None,None), + (5,48,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,48,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,49,"GtkBox","spacing","8",None,None,None,None,None,None,None,None,None), + (5,49,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,49,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,50,"GtkLabel","label","File Size(bytes)",1,None,None,None,None,None,None,None,None), + (5,51,"GtkLabel","label","Min:",1,None,None,None,None,None,None,None,None), + (5,52,"GtkEditable","text","8192",1,None,None,None,None,None,None,None,None), + (5,52,"GtkEntry","input-purpose","number",None,None,None,None,None,None,None,None,None), + (5,52,"GtkEntry","max-length","15",None,None,None,None,None,None,None,None,None), + (5,52,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,52,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,53,"GtkLabel","label","Max:",1,None,None,None,None,None,None,None,None), + (5,54,"GtkEditable","text","1099512000000",1,None,None,None,None,None,None,None,None), + (5,54,"GtkEntry","input-purpose","number",None,None,None,None,None,None,None,None,None), + (5,54,"GtkEntry","max-length","15",None,None,None,None,None,None,None,None,None), + (5,54,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,54,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,55,"GtkLabel","label","Items Configuration",1,None,None,None,None,None,None,None,None), + (5,56,"GtkNotebook","scrollable","1",None,None,None,None,None,None,None,None,None), + (5,56,"GtkNotebook","tab-pos","left",None,None,None,None,None,None,None,None,None), + (5,56,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,57,"GtkNotebookPage","child",None,None,None,None,None,58,None,None,None,None), + (5,57,"GtkNotebookPage","tab",None,None,None,None,None,68,None,None,None,None), + (5,58,"GtkPaned","wide-handle","True",None,None,None,None,None,None,None,None,None), + (5,59,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,59,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,60,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,60,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,60,"GtkWidget","margin-top","2",None,None,None,None,None,None,None,None,None), + (5,61,"GtkLabel","label","Check method",1,None,None,None,None,None,None,None,None), + (5,61,"GtkWidget","margin-end","3",None,None,None,None,None,None,None,None,None), + (5,63,"GtkLabel","label","Hash type",1,None,None,None,None,None,None,None,None), + (5,63,"GtkWidget","margin-end","2",None,None,None,None,None,None,None,None,None), + (5,63,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,65,"GtkCheckButton","label","Case sensitive",None,None,None,None,None,None,None,None,None), + (5,65,"GtkWidget","visible","0",None,None,None,None,None,None,None,None,None), + (5,66,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,66,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,66,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (5,67,"GtkWidget","height-request","100",None,None,None,None,None,None,None,None,None), + (5,67,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None), + (5,67,"GtkWidget","hexpand-set","True",None,None,None,None,None,None,None,None,None), + (5,67,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None), + (5,67,"GtkWidget","vexpand-set","True",None,None,None,None,None,None,None,None,None), + (5,67,"GtkWidget","width-request","100",None,None,None,None,None,None,None,None,None), + (5,68,"GtkLabel","label","Duplicates files",1,None,None,None,None,None,None,None,None), + (5,69,"GtkNotebookPage","child",None,None,None,None,None,70,None,None,None,None), + (5,69,"GtkNotebookPage","position","1",None,None,None,None,None,None,None,None,None), + (5,69,"GtkNotebookPage","tab",None,None,None,None,None,71,None,None,None,None), + (5,70,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,71,"GtkLabel","label","Empty Directories",1,None,None,None,None,None,None,None,None), + (5,72,"GtkNotebookPage","child",None,None,None,None,None,73,None,None,None,None), + (5,72,"GtkNotebookPage","position","2",None,None,None,None,None,None,None,None,None), + (5,72,"GtkNotebookPage","tab",None,None,None,None,None,78,None,None,None,None), + (5,73,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,74,"GtkBox","spacing","8",None,None,None,None,None,None,None,None,None), + (5,74,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,74,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,74,"GtkWidget","margin-top","2",None,None,None,None,None,None,None,None,None), + (5,75,"GtkLabel","label","Number of shown files",1,None,None,None,None,None,None,None,None), + (5,76,"GtkEditable","text","50",1,None,None,None,None,None,None,None,None), + (5,76,"GtkEntry","input-purpose","number",None,None,None,None,None,None,None,None,None), + (5,76,"GtkEntry","max-length","15",None,None,None,None,None,None,None,None,None), + (5,76,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,76,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,77,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,77,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (5,78,"GtkLabel","label","Big Files",1,None,None,None,None,None,None,None,None), + (5,79,"GtkNotebookPage","child",None,None,None,None,None,80,None,None,None,None), + (5,79,"GtkNotebookPage","position","3",None,None,None,None,None,None,None,None,None), + (5,79,"GtkNotebookPage","tab",None,None,None,None,None,81,None,None,None,None), + (5,80,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,81,"GtkLabel","label","Empty Files",1,None,None,None,None,None,None,None,None), + (5,82,"GtkNotebookPage","child",None,None,None,None,None,83,None,None,None,None), + (5,82,"GtkNotebookPage","position","4",None,None,None,None,None,None,None,None,None), + (5,82,"GtkNotebookPage","tab",None,None,None,None,None,84,None,None,None,None), + (5,83,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,84,"GtkLabel","label","Temporary Files",1,None,None,None,None,None,None,None,None), + (5,85,"GtkNotebookPage","child",None,None,None,None,None,86,None,None,None,None), + (5,85,"GtkNotebookPage","position","5",None,None,None,None,None,None,None,None,None), + (5,85,"GtkNotebookPage","tab",None,None,None,None,None,104,None,None,None,None), + (5,86,"GtkPaned","wide-handle","True",None,None,None,None,None,None,None,None,None), + (5,87,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,87,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,88,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,88,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,88,"GtkWidget","margin-top","2",None,None,None,None,None,None,None,None,None), + (5,89,"GtkLabel","label","Resize algorithm",1,None,None,None,None,None,None,None,None), + (5,89,"GtkWidget","margin-end","2",None,None,None,None,None,None,None,None,None), + (5,91,"GtkLabel","label","Hash size:",1,None,None,None,None,None,None,None,None), + (5,91,"GtkWidget","margin-end","2",None,None,None,None,None,None,None,None,None), + (5,91,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,93,"GtkLabel","label","Hash type:",1,None,None,None,None,None,None,None,None), + (5,93,"GtkWidget","margin-end","2",None,None,None,None,None,None,None,None,None), + (5,93,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,95,"GtkWidget","margin-bottom","2",None,None,None,None,None,None,None,None,None), + (5,95,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,95,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,96,"GtkLabel","label","Similarity ",1,None,None,None,None,None,None,None,None), + (5,97,"GtkLabel","label"," Very High ",1,None,None,None,None,None,None,None,None), + (5,98,"GtkRange","fill-level","100",None,None,None,None,None,None,None,None,None), + (5,98,"GtkRange","round-digits","1",None,None,None,None,None,None,None,None,None), + (5,98,"GtkScale","digits","0",None,None,None,None,None,None,None,None,None), + (5,98,"GtkScale","draw-value","1",None,None,None,None,None,None,None,None,None), + (5,98,"GtkScale","value-pos","right",None,None,None,None,None,None,None,None,None), + (5,98,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,98,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,99,"GtkLabel","label","Minimal",1,None,None,None,None,None,None,None,None), + (5,99,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,99,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,100,"GtkCheckButton","label","Ignore same size",1,None,None,None,None,None,None,None,None), + (5,100,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,100,"GtkWidget","margin-start","7",None,None,None,None,None,None,None,None,None), + (5,102,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,102,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,102,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (5,103,"GtkWidget","height-request","100",None,None,None,None,None,None,None,None,None), + (5,103,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None), + (5,103,"GtkWidget","hexpand-set","True",None,None,None,None,None,None,None,None,None), + (5,103,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None), + (5,103,"GtkWidget","vexpand-set","True",None,None,None,None,None,None,None,None,None), + (5,103,"GtkWidget","width-request","100",None,None,None,None,None,None,None,None,None), + (5,104,"GtkLabel","label","Similar Images",1,None,None,None,None,None,None,None,None), + (5,105,"GtkNotebookPage","child",None,None,None,None,None,106,None,None,None,None), + (5,105,"GtkNotebookPage","position","6",None,None,None,None,None,None,None,None,None), + (5,105,"GtkNotebookPage","tab",None,None,None,None,None,114,None,None,None,None), + (5,106,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,107,"GtkWidget","margin-bottom","2",None,None,None,None,None,None,None,None,None), + (5,107,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,107,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,108,"GtkLabel","label","Similarity ",1,None,None,None,None,None,None,None,None), + (5,109,"GtkLabel","label"," Very High ",1,None,None,None,None,None,None,None,None), + (5,110,"GtkRange","fill-level","100",None,None,None,None,None,None,None,None,None), + (5,110,"GtkRange","round-digits","0",None,None,None,None,None,None,None,None,None), + (5,110,"GtkScale","digits","0",None,None,None,None,None,None,None,None,None), + (5,110,"GtkScale","draw-value","1",None,None,None,None,None,None,None,None,None), + (5,110,"GtkScale","value-pos","right",None,None,None,None,None,None,None,None,None), + (5,110,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,110,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,111,"GtkLabel","label","Minimal",1,None,None,None,None,None,None,None,None), + (5,111,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,111,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,112,"GtkCheckButton","label","Ignore same size",1,None,None,None,None,None,None,None,None), + (5,112,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,112,"GtkWidget","margin-start","7",None,None,None,None,None,None,None,None,None), + (5,113,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,113,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (5,114,"GtkLabel","label","Similar Videos",1,None,None,None,None,None,None,None,None), + (5,115,"GtkNotebookPage","child",None,None,None,None,None,116,None,None,None,None), + (5,115,"GtkNotebookPage","position","7",None,None,None,None,None,None,None,None,None), + (5,115,"GtkNotebookPage","tab",None,None,None,None,None,127,None,None,None,None), + (5,116,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,117,"GtkBox","spacing","8",None,None,None,None,None,None,None,None,None), + (5,117,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,117,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,118,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (5,118,"GtkCheckButton","label","Title",1,None,None,None,None,None,None,None,None), + (5,118,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,119,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (5,119,"GtkCheckButton","label","Artist",1,None,None,None,None,None,None,None,None), + (5,119,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,120,"GtkCheckButton","label","Year",None,None,None,None,None,None,None,None,None), + (5,120,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,120,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None), + (5,121,"GtkCheckButton","label","Bitrate",None,None,None,None,None,None,None,None,None), + (5,121,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,122,"GtkCheckButton","label","Genre",None,None,None,None,None,None,None,None,None), + (5,122,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,123,"GtkCheckButton","label","Length",None,None,None,None,None,None,None,None,None), + (5,123,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,124,"GtkWidget","margin-bottom","2",None,None,None,None,None,None,None,None,None), + (5,124,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,124,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,125,"GtkCheckButton","label","Approximate Comparison",1,None,None,None,None,None,None,None,None), + (5,125,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,126,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,126,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,126,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (5,127,"GtkLabel","label","Music Duplicates",1,None,None,None,None,None,None,None,None), + (5,128,"GtkNotebookPage","child",None,None,None,None,None,129,None,None,None,None), + (5,128,"GtkNotebookPage","position","8",None,None,None,None,None,None,None,None,None), + (5,128,"GtkNotebookPage","tab",None,None,None,None,None,130,None,None,None,None), + (5,129,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,130,"GtkLabel","label","Invalid Symlinks",1,None,None,None,None,None,None,None,None), + (5,131,"GtkNotebookPage","child",None,None,None,None,None,132,None,None,None,None), + (5,131,"GtkNotebookPage","position","9",None,None,None,None,None,None,None,None,None), + (5,131,"GtkNotebookPage","tab",None,None,None,None,None,133,None,None,None,None), + (5,132,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (5,132,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,133,"GtkLabel","label","Broken Files",1,None,None,None,None,None,None,None,None), + (5,134,"GtkNotebookPage","child",None,None,None,None,None,135,None,None,None,None), + (5,134,"GtkNotebookPage","position","10",None,None,None,None,None,None,None,None,None), + (5,134,"GtkNotebookPage","tab",None,None,None,None,None,136,None,None,None,None), + (5,135,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,136,"GtkLabel","label","Bad Extensions",1,None,None,None,None,None,None,None,None), + (5,137,"GtkWidget","margin-bottom","5",None,None,None,None,None,None,None,None,None), + (5,137,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,137,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,137,"GtkWidget","margin-top","2",None,None,None,None,None,None,None,None,None), + (5,138,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,139,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,139,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,140,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,140,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,141,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,142,"GtkLabel","label","Search",1,None,None,None,None,None,None,None,None), + (5,176,"GtkWidget","margin-bottom","5",None,None,None,None,None,None,None,None,None), + (5,176,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,176,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,176,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None), + (5,177,"GtkEditable","editable","0",None,None,None,None,None,None,None,None,None), + (5,177,"GtkEntry","has-frame","0",None,None,None,None,None,None,None,None,None), + (5,177,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,177,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,178,"GtkEditable","editable","0",None,None,None,None,None,None,None,None,None), + (5,178,"GtkEditable","text","Czkawka 6.0.0",1,None,None,None,None,None,None,None,None), + (5,178,"GtkEditable","xalign","1",None,None,None,None,None,None,None,None,None), + (5,178,"GtkEntry","has-frame","0",None,None,None,None,None,None,None,None,None), + (5,178,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,179,"GtkScrolledWindow","child",None,None,None,None,None,180,None,None,None,None), + (5,179,"GtkScrolledWindow","min-content-height","100",None,None,None,None,None,None,None,None,None), + (5,179,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,180,"GtkTextView","editable","0",None,None,None,None,None,None,None,None,None), + (5,180,"GtkTextView","monospace","1",None,None,None,None,None,None,None,None,None), + (5,180,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,182,"GtkBox","spacing","5",None,None,None,None,None,None,None,None,None), + (5,183,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,183,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,184,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,185,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,185,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,186,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,187,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None), + (5,187,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None), + (5,188,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,188,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None), + (5,189,"GtkWidget","focus-on-click","0",None,None,None,None,None,None,None,None,None), + (5,189,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,189,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,190,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,190,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,191,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,192,"GtkLabel","label","SelectMenu",None,None,None,None,None,None,None,None,None), + (5,193,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,193,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,194,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,194,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,195,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,196,"GtkLabel","label","Compare",1,None,None,None,None,None,None,None,None), + (5,197,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,197,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,198,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,198,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,199,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,200,"GtkLabel","label","Delete",1,None,None,None,None,None,None,None,None), + (5,201,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,201,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,202,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,202,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,203,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,204,"GtkLabel","label","Move",1,None,None,None,None,None,None,None,None), + (5,205,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,205,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,206,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,206,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,207,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,208,"GtkLabel","label","Save",1,None,None,None,None,None,None,None,None), + (5,209,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,209,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,210,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,210,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,211,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,212,"GtkLabel","label","Symlink",1,None,None,None,None,None,None,None,None), + (5,213,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,213,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,214,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,214,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,215,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,216,"GtkLabel","label","Hardlink",1,None,None,None,None,None,None,None,None), + (5,217,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,217,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,218,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,218,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,219,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,219,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,220,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,220,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,222,"GtkLabel","label","VVV",None,None,None,None,None,None,None,None,None), + (5,223,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None), + (5,225,"GtkCheckButton","active","True",None,None,None,None,None,None,None,None,None), + (5,225,"GtkCheckButton","label","Audio",None,None,None,None,None,None,None,None,None), + (5,226,"GtkCheckButton","active","True",None,None,None,None,None,None,None,None,None), + (5,226,"GtkCheckButton","label","PDF",None,None,None,None,None,None,None,None,None), + (5,227,"GtkCheckButton","active","True",None,None,None,None,None,None,None,None,None), + (5,227,"GtkCheckButton","label","Archive",None,None,None,None,None,None,None,None,None), + (5,228,"GtkCheckButton","active","True",None,None,None,None,None,None,None,None,None), + (5,228,"GtkCheckButton","label","Image",None,None,None,None,None,None,None,None,None), + (5,229,"GtkWidget","focus-on-click","0",None,None,None,None,None,None,None,None,None), + (5,229,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,229,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (5,230,"GtkBox","spacing","2",None,None,None,None,None,None,None,None,None), + (5,230,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (5,231,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (5,232,"GtkLabel","label","SortMenu",None,None,None,None,None,None,None,None,None), + (5,234,"GtkLabel","label","Audio check type",None,None,None,None,None,None,None,None,None), + (5,234,"GtkWidget","margin-end","2",None,None,None,None,None,None,None,None,None), + (5,236,"GtkRange","fill-level","100",None,None,None,None,None,None,None,None,None), + (5,236,"GtkRange","round-digits","1",None,None,None,None,None,None,None,None,None), + (5,236,"GtkScale","digits","0",None,None,None,None,None,None,None,None,None), + (5,236,"GtkScale","draw-value","1",None,None,None,None,None,None,None,None,None), + (5,236,"GtkScale","value-pos","right",None,None,None,None,None,None,None,None,None), + (5,236,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,236,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,237,"GtkRange","fill-level","100",None,None,None,None,None,None,None,None,None), + (5,237,"GtkRange","round-digits","1",None,None,None,None,None,None,None,None,None), + (5,237,"GtkScale","digits","0",None,None,None,None,None,None,None,None,None), + (5,237,"GtkScale","draw-value","1",None,None,None,None,None,None,None,None,None), + (5,237,"GtkScale","value-pos","right",None,None,None,None,None,None,None,None,None), + (5,237,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (5,237,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (5,238,"GtkLabel","label","Minimal fragment second duration",None,None,None,None,None,None,None,None,None), + (5,238,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,238,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (5,239,"GtkLabel","label","Max difference",None,None,None,None,None,None,None,None,None), + (5,239,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None), + (5,239,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (6,1,"GtkPopover","child",None,None,None,None,None,2,None,None,None,None), + (6,1,"GtkPopover","position","left",None,None,None,None,None,None,None,None,None), + (6,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (6,3,"GtkButton","label","Open File",1,None,None,None,None,None,None,None,None), + (6,3,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (6,3,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (6,4,"GtkButton","label","Open Folder",1,None,None,None,None,None,None,None,None), + (6,4,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (6,4,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,1,"GtkPopover","child",None,None,None,None,None,2,None,None,None,None), + (7,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (7,3,"GtkButton","label","Select custom",1,None,None,None,None,None,None,None,None), + (7,3,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,3,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,4,"GtkButton","label","Unselect custom",1,None,None,None,None,None,None,None,None), + (7,4,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,4,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,6,"GtkButton","label","Select all except biggest",1,None,None,None,None,None,None,None,None), + (7,6,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,6,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,7,"GtkButton","label","Select all except smallest",1,None,None,None,None,None,None,None,None), + (7,7,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,7,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,9,"GtkButton","label","Select all except oldest",1,None,None,None,None,None,None,None,None), + (7,9,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,9,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,10,"GtkButton","label","Select all except newest",1,None,None,None,None,None,None,None,None), + (7,10,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,10,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,11,"GtkButton","label","Select one oldest",1,None,None,None,None,None,None,None,None), + (7,11,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,11,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,12,"GtkButton","label","Select one newest",1,None,None,None,None,None,None,None,None), + (7,12,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,12,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,14,"GtkButton","label","Reverse Selection",1,None,None,None,None,None,None,None,None), + (7,14,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,14,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,16,"GtkButton","label","Select All",1,None,None,None,None,None,None,None,None), + (7,16,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,16,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (7,17,"GtkButton","label","Unselect All",1,None,None,None,None,None,None,None,None), + (7,17,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (7,17,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (8,16,"GtkBox","spacing","10",None,None,None,None,None,None,None,None,None), + (8,16,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (8,16,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None), + (8,16,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None), + (8,16,"GtkWidget","margin-start","10",None,None,None,None,None,None,None,None,None), + (8,16,"GtkWidget","margin-top","10",None,None,None,None,None,None,None,None,None), + (8,17,"GtkWidget","margin-end","2",None,None,None,None,None,None,None,None,None), + (8,17,"GtkWidget","margin-start","2",None,None,None,None,None,None,None,None,None), + (8,17,"GtkWidget","margin-top","2",None,None,None,None,None,None,None,None,None), + (8,17,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (8,17,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (8,18,"GtkLabel","label","All stages: ",1,None,None,None,None,None,None,None,None), + (8,18,"GtkWidget","name","label_progress_all_stages",None,None,None,None,None,None,None,None,None), + (8,19,"GtkProgressBar","pulse-step","0.099999999776482579",None,None,None,None,None,None,None,None,None), + (8,19,"GtkProgressBar","show-text","1",None,None,None,None,None,None,None,None,None), + (8,19,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (8,20,"GtkLabel","label","Current stage: ",1,None,None,None,None,None,None,None,None), + (8,20,"GtkWidget","name","label_progress_current_stage",None,None,None,None,None,None,None,None,None), + (8,21,"GtkProgressBar","show-text","1",None,None,None,None,None,None,None,None,None), + (8,22,"GtkLabel","label","Stage 1/2",1,None,None,None,None,None,None,None,None), + (8,23,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (8,23,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None), + (8,23,"GtkWidget","margin-end","2",None,None,None,None,None,None,None,None,None), + (8,23,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (8,23,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (8,25,"GtkImage","icon-name","image-missing",None,None,None,None,None,None,None,None,None), + (8,26,"GtkLabel","label","Stop",1,None,None,None,None,None,None,None,None), + (8,26,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (9,1,"GtkWindow","modal","1",None,None,None,None,None,None,None,None,None), + (9,1,"GtkWindow","title","Czkawka Options",1,None,None,None,None,None,None,None,None), + (9,3,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (9,3,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (9,6,"GtkNotebook","tab-pos","left",None,None,None,None,None,None,None,None,None), + (9,6,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,6,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (9,7,"GtkNotebookPage","child",None,None,None,None,None,8,None,None,None,None), + (9,7,"GtkNotebookPage","tab",None,None,None,None,None,25,None,None,None,None), + (9,8,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (9,8,"GtkWidget","margin-bottom","5",None,None,None,None,None,None,None,None,None), + (9,9,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (9,9,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (9,11,"GtkLabel","label","Language",1,None,None,None,None,None,None,None,None), + (9,11,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None), + (9,11,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (9,12,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (9,13,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,13,"GtkCheckButton","label","Load configuration at start",1,None,None,None,None,None,None,None,None), + (9,13,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,14,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,14,"GtkCheckButton","label","Save configuration at exit",1,None,None,None,None,None,None,None,None), + (9,14,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,15,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,15,"GtkCheckButton","label","Show confirm dialog when deleting any files",1,None,None,None,None,None,None,None,None), + (9,15,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,16,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,16,"GtkCheckButton","label","Show confirm dialog when hard/symlinks any files",1,None,None,None,None,None,None,None,None), + (9,16,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,17,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,17,"GtkCheckButton","label","Show confirm dialog when deleting all files in group",1,None,None,None,None,None,None,None,None), + (9,17,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,18,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,18,"GtkCheckButton","label","Show bottom text panel",1,None,None,None,None,None,None,None,None), + (9,18,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,18,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (9,19,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,19,"GtkCheckButton","label","Use cache",1,None,None,None,None,None,None,None,None), + (9,19,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,19,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (9,20,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,20,"GtkCheckButton","label","Save cache also to JSON file",1,None,None,None,None,None,None,None,None), + (9,20,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,20,"GtkWidget","vexpand","1",None,None,None,None,None,None,None,None,None), + (9,21,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,21,"GtkCheckButton","label","Move deleted files to trash",1,None,None,None,None,None,None,None,None), + (9,21,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,22,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (9,23,"GtkButton","label","Open cache folder",1,None,None,None,None,None,None,None,None), + (9,23,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,23,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,24,"GtkButton","label","Open settings folder",1,None,None,None,None,None,None,None,None), + (9,24,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,24,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,25,"GtkLabel","label","General",1,None,None,None,None,None,None,None,None), + (9,26,"GtkNotebookPage","child",None,None,None,None,None,27,None,None,None,None), + (9,26,"GtkNotebookPage","position","1",None,None,None,None,None,None,None,None,None), + (9,26,"GtkNotebookPage","tab",None,None,None,None,None,39,None,None,None,None), + (9,27,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (9,28,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,28,"GtkCheckButton","label","Hide hard links(only Linux and MacOS)",1,None,None,None,None,None,None,None,None), + (9,28,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,29,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,29,"GtkCheckButton","label","Show image preview",1,None,None,None,None,None,None,None,None), + (9,29,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,30,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,30,"GtkCheckButton","label","Delete outdated cache entries automatically",1,None,None,None,None,None,None,None,None), + (9,30,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,31,"GtkWidget","margin-end","4",None,None,None,None,None,None,None,None,None), + (9,31,"GtkWidget","margin-start","4",None,None,None,None,None,None,None,None,None), + (9,32,"GtkLabel","label","Minimal size of files in bytes saved to cache",1,None,None,None,None,None,None,None,None), + (9,32,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (9,33,"GtkEditable","text","257144",1,None,None,None,None,None,None,None,None), + (9,33,"GtkEntry","input-purpose","number",None,None,None,None,None,None,None,None,None), + (9,33,"GtkEntry","max-length","15",None,None,None,None,None,None,None,None,None), + (9,33,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,33,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (9,34,"GtkCheckButton","label","Use prehash cache",1,None,None,None,None,None,None,None,None), + (9,34,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,35,"GtkButton","label","Remove outdated results from duplicates cache",1,None,None,None,None,None,None,None,None), + (9,35,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,35,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,35,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (9,36,"GtkWidget","margin-end","4",None,None,None,None,None,None,None,None,None), + (9,36,"GtkWidget","margin-start","4",None,None,None,None,None,None,None,None,None), + (9,37,"GtkLabel","label","Minimal size of files in bytes saved to prehash cache",1,None,None,None,None,None,None,None,None), + (9,37,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (9,38,"GtkEditable","text","1",1,None,None,None,None,None,None,None,None), + (9,38,"GtkEntry","input-purpose","number",None,None,None,None,None,None,None,None,None), + (9,38,"GtkEntry","max-length","15",None,None,None,None,None,None,None,None,None), + (9,38,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,38,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (9,39,"GtkLabel","label","Duplicate Finder",1,None,None,None,None,None,None,None,None), + (9,40,"GtkNotebookPage","child",None,None,None,None,None,41,None,None,None,None), + (9,40,"GtkNotebookPage","position","2",None,None,None,None,None,None,None,None,None), + (9,40,"GtkNotebookPage","tab",None,None,None,None,None,45,None,None,None,None), + (9,41,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (9,42,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,42,"GtkCheckButton","label","Show image preview",1,None,None,None,None,None,None,None,None), + (9,42,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,43,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,43,"GtkCheckButton","label","Delete outdated cache entries automatically",1,None,None,None,None,None,None,None,None), + (9,43,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,44,"GtkButton","label","Remove outdated results from images cache",1,None,None,None,None,None,None,None,None), + (9,44,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,44,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,44,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (9,45,"GtkLabel","label","Similar Images",1,None,None,None,None,None,None,None,None), + (9,46,"GtkNotebookPage","child",None,None,None,None,None,47,None,None,None,None), + (9,46,"GtkNotebookPage","position","3",None,None,None,None,None,None,None,None,None), + (9,46,"GtkNotebookPage","tab",None,None,None,None,None,50,None,None,None,None), + (9,47,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (9,48,"GtkButton","label","Remove outdated results from videos cache",1,None,None,None,None,None,None,None,None), + (9,48,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,48,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,48,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None), + (9,49,"GtkCheckButton","label","Delete outdated cache entries automatically",1,None,None,None,None,None,None,None,None), + (9,49,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,50,"GtkLabel","label","Similar Videos",1,None,None,None,None,None,None,None,None), + (9,51,"GtkBox","spacing","3",None,None,None,None,None,None,None,None,None), + (9,51,"GtkWidget","margin-end","3",None,None,None,None,None,None,None,None,None), + (9,51,"GtkWidget","margin-start","3",None,None,None,None,None,None,None,None,None), + (9,52,"GtkButton","label","Load configuration",1,None,None,None,None,None,None,None,None), + (9,52,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,52,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (9,52,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,53,"GtkButton","label","Reset configuration",1,None,None,None,None,None,None,None,None), + (9,53,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,53,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (9,53,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (9,53,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,54,"GtkButton","label","Save configuration",1,None,None,None,None,None,None,None,None), + (9,54,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,54,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None), + (9,54,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (9,55,"GtkCheckButton","active","1",None,None,None,None,None,None,None,None,None), + (9,55,"GtkCheckButton","label","Exclude other filesystems(Linux)",None,None,None,None,None,None,None,None,None), + (9,55,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,57,"GtkAccessible","accessible-role","presentation",None,None,None,None,None,None,None,None,None), + (9,57,"GtkLabel","label","Number of used threads",None,None,None,None,None,None,None,None,None), + (9,57,"GtkLabel","wrap-mode","word-char",None,None,None,None,None,None,None,None,None), + (9,57,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None), + (9,58,"GtkRange","fill-level","100",None,None,None,None,None,None,None,None,None), + (9,58,"GtkRange","round-digits","1",None,None,None,None,None,None,None,None,None), + (9,58,"GtkScale","digits","0",None,None,None,None,None,None,None,None,None), + (9,58,"GtkScale","draw-value","True",None,None,None,None,None,None,None,None,None), + (9,58,"GtkScale","value-pos","right",None,None,None,None,None,None,None,None,None), + (9,58,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (9,58,"GtkWidget","hexpand","1",None,None,None,None,None,None,None,None,None), + (9,59,"GtkAccessible","accessible-role","menu-item-checkbox",None,None,None,None,None,None,None,None,None), + (9,59,"GtkLabel","label","Restart Required",None,None,None,None,None,None,None,None,None), + (9,59,"GtkWidget","margin-bottom","4",None,None,None,None,None,None,None,None,None), + (9,59,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None), + (10,1,"GtkPopover","child",None,None,None,None,None,2,None,None,None,None), + (10,1,"GtkPopover","position","top",None,None,None,None,None,None,None,None,None), + (10,2,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None), + (10,3,"GtkButton","label","File name",None,None,None,None,None,None,None,None,None), + (10,3,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (10,3,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (10,4,"GtkButton","label","Folder name",None,None,None,None,None,None,None,None,None), + (10,4,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (10,4,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (10,5,"GtkButton","label","Full name",None,None,None,None,None,None,None,None,None), + (10,5,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (10,5,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (10,6,"GtkButton","label","Size",None,None,None,None,None,None,None,None,None), + (10,6,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (10,6,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None), + (10,7,"GtkButton","label","Selection",None,None,None,None,None,None,None,None,None), + (10,7,"GtkWidget","focusable","1",None,None,None,None,None,None,None,None,None), + (10,7,"GtkWidget","receives-default","1",None,None,None,None,None,None,None,None,None) (8,17,18,"GtkGridLayoutChild","column","0",None,None,None,None), diff --git a/czkawka_gui/ui/main_window.ui b/czkawka_gui/ui/main_window.ui index e4aaf3f..10a5b59 100644 --- a/czkawka_gui/ui/main_window.ui +++ b/czkawka_gui/ui/main_window.ui @@ -1,8 +1,8 @@ - + - + 10 1 diff --git a/czkawka_gui/ui/popover_right_click.ui b/czkawka_gui/ui/popover_right_click.ui index 47aae4a..840b1db 100644 --- a/czkawka_gui/ui/popover_right_click.ui +++ b/czkawka_gui/ui/popover_right_click.ui @@ -1,8 +1,8 @@ - + - + diff --git a/czkawka_gui/ui/popover_select.ui b/czkawka_gui/ui/popover_select.ui index 171bd89..30d06eb 100644 --- a/czkawka_gui/ui/popover_select.ui +++ b/czkawka_gui/ui/popover_select.ui @@ -1,8 +1,8 @@ - + - + diff --git a/czkawka_gui/ui/popover_sort.ui b/czkawka_gui/ui/popover_sort.ui index 9ac7f59..fe09f63 100644 --- a/czkawka_gui/ui/popover_sort.ui +++ b/czkawka_gui/ui/popover_sort.ui @@ -1,5 +1,5 @@ - + diff --git a/czkawka_gui/ui/progress.ui b/czkawka_gui/ui/progress.ui index 913844a..8f20aae 100644 --- a/czkawka_gui/ui/progress.ui +++ b/czkawka_gui/ui/progress.ui @@ -1,8 +1,8 @@ - + - + diff --git a/czkawka_gui/ui/settings.ui b/czkawka_gui/ui/settings.ui index 1b73cd9..ee51686 100644 --- a/czkawka_gui/ui/settings.ui +++ b/czkawka_gui/ui/settings.ui @@ -1,8 +1,8 @@ - + - + 1 Czkawka Options diff --git a/instructions/Compilation.md b/instructions/Compilation.md index e8b9518..2fe33b4 100644 --- a/instructions/Compilation.md +++ b/instructions/Compilation.md @@ -16,6 +16,7 @@ New versions of GTK fixes some bugs, so e.g. middle button selection will work o | GTK | 4.6 | Only for the `GTK` backend | #### Debian / Ubuntu + ```shell sudo apt install -y curl git build-essential # Needed by Rust update tool curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Download the latest stable Rust @@ -23,13 +24,17 @@ sudo apt install -y libgtk-4-dev ``` #### Fedora / CentOS / Rocky Linux + ```shell curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Download the latest stable Rust sudo yum install gtk4-devel glib2-devel ``` #### macOS -You need to install Rust via Homebrew, GTK Libraries and optionally heif library(to have support for heic files, which are quite popular on Mac) + +You need to install Rust via Homebrew, GTK Libraries and optionally heif library(to have support for heic files, which +are quite popular on Mac) + ```shell /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew install rustup @@ -41,7 +46,8 @@ brew install gtk4 adwaita-icon-theme librsvg libheif webp-pixbuf-loader Compiling Czkawka on Windows is possible, but due using GTK is very hard. -In CI we use cross compilation which simplify a lot of things, so for now there is no instruction how to compile native binaries on Windows. +In CI we use cross compilation which simplify a lot of things, so for now there is no instruction how to compile native +binaries on Windows. ### Docker @@ -52,21 +58,26 @@ docker build ./misc/docker/ --tag cargo-gtk ## Compilation Czkawka can be installed with Debug or Release build. -With Debug build additional checks, e.g., variables overflow, are available, but depending on the usage it works very slow, so it should be used only for development purposes. -Compilation with `--release` flag will optimize binaries, so they can be used with good performance (official binaries are built with this flag) - +With Debug build additional checks, e.g., variables overflow, are available, but depending on the usage it works very +slow, so it should be used only for development purposes. +Compilation with `--release` flag will optimize binaries, so they can be used with good performance (official binaries +are built with this flag) - Download the source + ``` git clone https://github.com/qarmin/czkawka.git cd czkawka ``` + - Compile and run GTK GUI + ``` cargo run --release --bin czkawka_gui ``` - Compile and run CLI (by default this will print help with examples) + ``` cargo run --release --bin czkawka_cli ``` @@ -84,9 +95,14 @@ target/release/czkawka_gui ``` ## Additional features + Currently, the only additional dependence is heif image support. To enable checking for heif images, just add ` --all-features` or `--features heif` + ``` cargo run --features heif --bin czkawka_cli -- image -d /home/rafal/ -f "results.txt" ``` + +**Be aware, that heif support is not available on Windows, so you can't compile it with this feature, because +mingw-w64-x86_64-libheif is not available in fedora repos, which are used for cross compilation.** \ No newline at end of file diff --git a/instructions/Installation.md b/instructions/Installation.md index 8f6760a..26b8ec1 100644 --- a/instructions/Installation.md +++ b/instructions/Installation.md @@ -70,7 +70,7 @@ Sadly this doesn't work for all users, so feel free to update this part of docum ### Windows -By default, all needed libraries are bundled with the app, inside `windows_czkawka_gui.zip`, but if you compile the app or just move `czkawka_gui.exe`, then you will need to install the `GTK 4` +By default, all needed libraries are bundled with the app except libheif library which allows to scan/use heif files, inside `windows_czkawka_gui.zip`, but if you compile the app or just move `czkawka_gui.exe`, then you will need to install the `GTK 4` runtime from [**here**](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases). FFmpeg to be able to use Similar Videos, you can download and install from this [**link**](https://ffmpeg.org/).