From 7ac4a262291e07b23b806cfbd7de9ab3effe830b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Mikrut?= <41945903+qarmin@users.noreply.github.com> Date: Wed, 2 Dec 2020 10:25:27 +0100 Subject: [PATCH] Add progress bar (#106) --- Cargo.lock | 167 +++++++++-------- Cargo.toml | 4 +- czkawka_cli/src/main.rs | 16 +- czkawka_core/Cargo.toml | 3 + czkawka_core/src/big_file.rs | 55 +++++- czkawka_core/src/duplicate.rs | 200 ++++++++++++++++++-- czkawka_core/src/empty_files.rs | 56 +++++- czkawka_core/src/empty_folder.rs | 58 +++++- czkawka_core/src/same_music.rs | 164 ++++++++++++++++- czkawka_core/src/similar_images.rs | 105 ++++++++++- czkawka_core/src/temporary.rs | 56 +++++- czkawka_core/src/zeroed.rs | 97 +++++++++- czkawka_gui/Cargo.toml | 2 + czkawka_gui/czkawka.glade | 171 +++++++++++++++-- czkawka_gui/src/connect_button_delete.rs | 7 +- czkawka_gui/src/connect_button_search.rs | 179 ++++++++++++------ czkawka_gui/src/connect_button_stop.rs | 13 +- czkawka_gui/src/connect_compute_results.rs | 7 +- czkawka_gui/src/connect_progress_window.rs | 203 +++++++++++++++++++++ czkawka_gui/src/connect_upper_notebook.rs | 8 +- czkawka_gui/src/gui_data.rs | 29 +++ czkawka_gui/src/main.rs | 40 +++- 22 files changed, 1407 insertions(+), 233 deletions(-) create mode 100644 czkawka_gui/src/connect_progress_window.rs diff --git a/Cargo.lock b/Cargo.lock index e2e9e54..cc76bc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,6 +134,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.2.1" @@ -233,9 +239,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.62" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" +checksum = "95752358c8f7552394baf48cd82695b345628ad3f170d607de3ca03b8dacca15" [[package]] name = "cfg-if" @@ -340,7 +346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.0", + "crossbeam-utils 0.8.1", ] [[package]] @@ -351,18 +357,18 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.0", + "crossbeam-utils 0.8.1", ] [[package]] name = "crossbeam-epoch" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" dependencies = [ "cfg-if 1.0.0", "const_fn", - "crossbeam-utils 0.8.0", + "crossbeam-utils 0.8.1", "lazy_static", "memoffset", "scopeguard", @@ -381,13 +387,12 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ "autocfg 1.0.1", "cfg-if 1.0.0", - "const_fn", "lazy_static", ] @@ -418,6 +423,7 @@ dependencies = [ "bk-tree", "blake3", "crossbeam-channel 0.4.4", + "futures", "hamming", "humansize", "image", @@ -432,6 +438,7 @@ dependencies = [ "chrono", "crossbeam-channel 0.4.4", "czkawka_core", + "futures", "gdk", "gio", "glib", @@ -492,7 +499,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -644,9 +651,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "futures" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" dependencies = [ "futures-channel", "futures-core", @@ -659,9 +666,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" dependencies = [ "futures-core", "futures-sink", @@ -669,15 +676,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" [[package]] name = "futures-executor" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" dependencies = [ "futures-core", "futures-task", @@ -686,42 +693,42 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] name = "futures-macro" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] name = "futures-sink" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" [[package]] name = "futures-task" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" dependencies = [ "once_cell", ] [[package]] name = "futures-util" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" dependencies = [ "futures-channel", "futures-core", @@ -899,7 +906,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -1020,9 +1027,9 @@ dependencies = [ [[package]] name = "image" -version = "0.23.11" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f0a8345b33b082aedec2f4d7d4a926b845cee184cbe78b703413066564431b" +checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5" dependencies = [ "bytemuck", "byteorder", @@ -1140,9 +1147,9 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memoffset" -version = "0.5.6" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" dependencies = [ "autocfg 1.0.1", ] @@ -1300,9 +1307,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "open" @@ -1373,7 +1380,7 @@ dependencies = [ "case", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -1503,22 +1510,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841" +checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86" +checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -1581,7 +1588,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", "version_check", ] @@ -1833,7 +1840,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel 0.5.0", "crossbeam-deque", - "crossbeam-utils 0.8.0", + "crossbeam-utils 0.8.1", "lazy_static", "num_cpus", ] @@ -1886,14 +1893,14 @@ dependencies = [ [[package]] name = "rust-argon2" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" dependencies = [ - "base64 0.12.3", + "base64 0.13.0", "blake2b_simd", "constant_time_eq", - "crossbeam-utils 0.7.2", + "crossbeam-utils 0.8.1", ] [[package]] @@ -2028,7 +2035,7 @@ checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -2056,9 +2063,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" +checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" [[package]] name = "spin_sleep" @@ -2105,7 +2112,7 @@ dependencies = [ "quote 1.0.7", "serde", "serde_derive", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -2121,7 +2128,7 @@ dependencies = [ "serde_derive", "serde_json", "sha1", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -2144,9 +2151,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8" +checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" dependencies = [ "clap", "lazy_static", @@ -2155,15 +2162,15 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8" +checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" dependencies = [ "heck", "proc-macro-error", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -2181,7 +2188,7 @@ dependencies = [ "heck", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -2209,9 +2216,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.48" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", @@ -2273,7 +2280,7 @@ checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", ] [[package]] @@ -2337,9 +2344,9 @@ checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "unicode-segmentation" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" [[package]] name = "unicode-width" @@ -2397,34 +2404,34 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" +checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" +checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" +checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" dependencies = [ "quote 1.0.7", "wasm-bindgen-macro-support", @@ -2432,22 +2439,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" +checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.48", + "syn 1.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.68" +version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" +checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" [[package]] name = "wayland-client" @@ -2521,9 +2528,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8795d6e0e17485803cc10ef126bb8c0d59b7c61b219d66cfe0b3216dd0e8580a" +checksum = "3e2bb9fc8309084dd7cd651336673844c1d47f8ef6d2091ec160b27f5c4aa277" [[package]] name = "winapi" diff --git a/Cargo.toml b/Cargo.toml index a44d2f7..cd4fc6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,6 @@ members = [ "czkawka_cli", "czkawka_gui", "czkawka_gui_orbtk", -] \ No newline at end of file +] +#[profile.release] +#lto = true \ No newline at end of file diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index eae0c49..733d6ea 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -52,7 +52,7 @@ fn main() { df.set_delete_method(delete_method); df.set_recursive_search(!not_recursive.not_recursive); - df.find_duplicates(None); + df.find_duplicates(None, None); if let Some(file_name) = file_to_save.file_name() { if !df.save_results_to_file(file_name) { @@ -79,7 +79,7 @@ fn main() { ef.set_excluded_items(path_list_to_str(excluded_items.excluded_items)); ef.set_delete_folder(delete_folders); - ef.find_empty_folders(None); + ef.find_empty_folders(None, None); if let Some(file_name) = file_to_save.file_name() { if !ef.save_results_to_file(file_name) { @@ -114,7 +114,7 @@ fn main() { bf.set_delete_method(big_file::DeleteMethod::Delete); } - bf.find_big_files(None); + bf.find_big_files(None, None); if let Some(file_name) = file_to_save.file_name() { if !bf.save_results_to_file(file_name) { @@ -148,7 +148,7 @@ fn main() { ef.set_delete_method(empty_files::DeleteMethod::Delete); } - ef.find_empty_files(None); + ef.find_empty_files(None, None); if let Some(file_name) = file_to_save.file_name() { if !ef.save_results_to_file(file_name) { @@ -180,7 +180,7 @@ fn main() { tf.set_delete_method(temporary::DeleteMethod::Delete); } - tf.find_temporary_files(None); + tf.find_temporary_files(None, None); if let Some(file_name) = file_to_save.file_name() { if !tf.save_results_to_file(file_name) { @@ -211,7 +211,7 @@ fn main() { sf.set_recursive_search(!not_recursive.not_recursive); sf.set_similarity(similarity); - sf.find_similar_images(None); + sf.find_similar_images(None, None); if let Some(file_name) = file_to_save.file_name() { if !sf.save_results_to_file(file_name) { @@ -247,7 +247,7 @@ fn main() { zf.set_delete_method(zeroed::DeleteMethod::Delete); } - zf.find_zeroed_files(None); + zf.find_zeroed_files(None, None); if let Some(file_name) = file_to_save.file_name() { if !zf.save_results_to_file(file_name) { @@ -283,7 +283,7 @@ fn main() { // // TODO mf.set_delete_method(same_music::DeleteMethod::Delete); // } - mf.find_same_music(None); + mf.find_same_music(None, None); if let Some(file_name) = file_to_save.file_name() { if !mf.save_results_to_file(file_name) { diff --git a/czkawka_core/Cargo.toml b/czkawka_core/Cargo.toml index 57f9d8f..c9a5b29 100644 --- a/czkawka_core/Cargo.toml +++ b/czkawka_core/Cargo.toml @@ -25,3 +25,6 @@ hamming = "0.1" # Needed by same music bitflags = "1.2.1" audiotags = "0.2.7182" + +# Futures - needed by async progress sender +futures = "0.3.8" \ No newline at end of file diff --git a/czkawka_core/src/big_file.rs b/czkawka_core/src/big_file.rs index 921d0bc..9bb56b0 100644 --- a/czkawka_core/src/big_file.rs +++ b/czkawka_core/src/big_file.rs @@ -8,11 +8,21 @@ use crossbeam_channel::Receiver; use humansize::{file_size_opts as options, FileSize}; use std::collections::BTreeMap; use std::ffi::OsStr; -use std::fs; use std::fs::{File, Metadata}; use std::io::Write; use std::path::PathBuf; +use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicBool, AtomicU64}; +use std::sync::Arc; +use std::thread::sleep; +use std::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; + +#[derive(Debug)] +pub struct ProgressData { + pub files_checked: usize, +} #[derive(Clone)] pub struct FileEntry { @@ -73,9 +83,9 @@ impl BigFile { } } - pub fn find_big_files(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_big_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.optimize_directories(); - if !self.look_for_big_files(stop_receiver) { + if !self.look_for_big_files(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -111,7 +121,7 @@ impl BigFile { self.allowed_extensions.set_allowed_extensions(allowed_extensions, &mut self.text_messages); } - fn look_for_big_files(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn look_for_big_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -121,10 +131,42 @@ impl BigFile { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicU64::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + + //// PROGRESS THREAD END + while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // Be sure that every thread is closed + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } + let current_folder = folders_to_check.pop().unwrap(); let read_dir = match fs::read_dir(¤t_folder) { Ok(t) => t, @@ -162,6 +204,7 @@ impl BigFile { folders_to_check.push(next_folder); } else if metadata.is_file() { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); // Extracting file extension let file_extension = entry_data.path().extension().and_then(OsStr::to_str).map(str::to_lowercase); @@ -211,6 +254,10 @@ impl BigFile { } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + // Extract n biggest files to new TreeMap let mut new_map: BTreeMap> = Default::default(); diff --git a/czkawka_core/src/duplicate.rs b/czkawka_core/src/duplicate.rs index fae2705..386d94b 100644 --- a/czkawka_core/src/duplicate.rs +++ b/czkawka_core/src/duplicate.rs @@ -1,11 +1,11 @@ use crossbeam_channel::Receiver; use humansize::{file_size_opts as options, FileSize}; use std::collections::{BTreeMap, HashMap}; -use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; use crate::common::Common; use crate::common_directory::Directories; @@ -14,10 +14,21 @@ use crate::common_items::ExcludedItems; use crate::common_messages::Messages; use crate::common_traits::*; use rayon::prelude::*; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread::sleep; const HASH_MB_LIMIT_BYTES: u64 = 1024 * 1024; // 1MB +#[derive(Debug)] +pub struct ProgressData { + pub checking_method: CheckingMethod, + pub current_stage: u8, + pub max_stage: u8, + pub files_checked: usize, + pub files_to_check: usize, +} + #[derive(PartialEq, Eq, Clone, Debug)] pub enum CheckingMethod { None, @@ -116,28 +127,28 @@ impl DuplicateFinder { } } - pub fn find_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); match self.check_method { CheckingMethod::Name => { - if !self.check_files_name(stop_receiver) { + if !self.check_files_name(stop_receiver, progress_sender) { self.stopped_search = true; return; } } CheckingMethod::Size => { - if !self.check_files_size(stop_receiver) { + if !self.check_files_size(stop_receiver, progress_sender) { self.stopped_search = true; return; } } CheckingMethod::HashMB | CheckingMethod::Hash => { - if !self.check_files_size(stop_receiver) { + if !self.check_files_size(stop_receiver, progress_sender) { self.stopped_search = true; return; } - if !self.check_files_hash(stop_receiver) { + if !self.check_files_hash(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -212,7 +223,7 @@ impl DuplicateFinder { self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); } - fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_files_name(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -222,10 +233,46 @@ impl DuplicateFinder { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + checking_method: CheckingMethod::Name, + current_stage: 0, + max_stage: 0, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + files_to_check: 0, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + + //// PROGRESS THREAD END + while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } + let current_folder = folders_to_check.pop().unwrap(); // Read current dir, if permission are denied just go to next @@ -271,6 +318,7 @@ impl DuplicateFinder { folders_to_check.push(next_folder); } else if metadata.is_file() { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); // let mut have_valid_extension: bool; let file_name_lowercase: String = match entry_data.file_name().into_string() { Ok(t) => t, @@ -329,6 +377,10 @@ impl DuplicateFinder { } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + // Create new BTreeMap without single size entries(files have not duplicates) let mut new_map: BTreeMap> = Default::default(); @@ -349,7 +401,7 @@ impl DuplicateFinder { /// 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<()>>) -> bool { + fn check_files_size(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -359,10 +411,52 @@ impl DuplicateFinder { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + let checking_method = self.check_method.clone(); + let max_stage = match self.check_method { + CheckingMethod::Size => 0, + CheckingMethod::HashMB | CheckingMethod::Hash => 2, + _ => 255, + }; + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + checking_method: checking_method.clone(), + current_stage: 0, + max_stage, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + files_to_check: 0, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + + //// PROGRESS THREAD END + while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } + let current_folder = folders_to_check.pop().unwrap(); // Read current dir, if permission are denied just go to next @@ -408,6 +502,7 @@ impl DuplicateFinder { folders_to_check.push(next_folder); } else if metadata.is_file() { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); // let mut have_valid_extension: bool; let file_name_lowercase: String = match entry_data.file_name().into_string() { Ok(t) => t, @@ -465,6 +560,9 @@ impl DuplicateFinder { } } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); // Create new BTreeMap without single size entries(files have not duplicates) let mut new_map: BTreeMap> = Default::default(); @@ -484,15 +582,49 @@ impl DuplicateFinder { } /// The slowest checking type, which must be applied after checking for size - fn check_files_hash(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_files_hash(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { if self.hash_type != HashType::Blake3 { panic!(); // TODO Add more hash types } let start_time: SystemTime = SystemTime::now(); - let check_was_breaked = AtomicBool::new(false); + let check_was_breaked = AtomicBool::new(false); // Used for breaking from GUI and ending check thread let mut pre_checked_map: BTreeMap> = Default::default(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + let files_to_check = self.files_with_identical_size.iter().map(|e| e.1.len()).sum(); + let checking_method = self.check_method.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + checking_method: checking_method.clone(), + current_stage: 1, + max_stage: 2, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + files_to_check, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + + //// PROGRESS THREAD END + #[allow(clippy::type_complexity)] let pre_hash_results: Vec<(u64, HashMap>, Vec, u64)> = self .files_with_identical_size @@ -502,6 +634,7 @@ impl DuplicateFinder { let mut errors: Vec = Vec::new(); let mut file_handler: File; let mut bytes_read: u64 = 0; + atomic_file_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed); 'fe: for file_entry in vec_file_entry { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { check_was_breaked.store(true, Ordering::Relaxed); @@ -537,6 +670,10 @@ impl DuplicateFinder { .while_some() .collect(); + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + // Check if user aborted search(only from GUI) if check_was_breaked.load(Ordering::Relaxed) { return false; @@ -562,6 +699,40 @@ impl DuplicateFinder { ///////////////////////// + //// PROGRESS THREAD START + // const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + let files_to_check = pre_checked_map.iter().map(|e| e.1.len()).sum(); + let checking_method = self.check_method.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + checking_method: checking_method.clone(), + current_stage: 2, + max_stage: 2, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + files_to_check, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + + //// PROGRESS THREAD END + #[allow(clippy::type_complexity)] let full_hash_results: Vec<(u64, HashMap>, Vec, u64)> = pre_checked_map .par_iter() @@ -570,6 +741,7 @@ impl DuplicateFinder { let mut errors: Vec = Vec::new(); let mut file_handler: File; let mut bytes_read: u64 = 0; + atomic_file_counter.fetch_add(vec_file_entry.len(), Ordering::Relaxed); 'fe: for file_entry in vec_file_entry { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { check_was_breaked.store(true, Ordering::Relaxed); @@ -617,6 +789,10 @@ impl DuplicateFinder { .while_some() .collect(); + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + // Check if user aborted search(only from GUI) if check_was_breaked.load(Ordering::Relaxed) { return false; diff --git a/czkawka_core/src/empty_files.rs b/czkawka_core/src/empty_files.rs index 33fd331..0b706af 100644 --- a/czkawka_core/src/empty_files.rs +++ b/czkawka_core/src/empty_files.rs @@ -1,8 +1,8 @@ -use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; use crate::common::Common; use crate::common_directory::Directories; @@ -11,6 +11,16 @@ use crate::common_items::ExcludedItems; use crate::common_messages::Messages; use crate::common_traits::*; use crossbeam_channel::Receiver; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread::sleep; + +#[derive(Debug)] +pub struct ProgressData { + pub current_stage: u8, + pub max_stage: u8, + pub files_checked: usize, +} #[derive(Eq, PartialEq, Clone, Debug)] pub enum DeleteMethod { @@ -70,9 +80,9 @@ impl EmptyFiles { } /// Finding empty files, save results to internal struct variables - pub fn find_empty_files(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_empty_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); - if !self.check_files(stop_receiver) { + if !self.check_files(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -120,7 +130,7 @@ impl EmptyFiles { } /// Check files for any with size == 0 - fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -130,8 +140,40 @@ impl EmptyFiles { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 0, + max_stage: 0, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let current_folder = folders_to_check.pop().unwrap(); @@ -175,6 +217,7 @@ impl EmptyFiles { folders_to_check.push(next_folder); } else if metadata.is_file() { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); let file_name_lowercase: String = match entry_data.file_name().into_string() { Ok(t) => t, Err(_) => continue, @@ -229,6 +272,9 @@ impl EmptyFiles { } } self.information.number_of_empty_files = self.empty_files.len(); + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string()); true diff --git a/czkawka_core/src/empty_folder.rs b/czkawka_core/src/empty_folder.rs index 93d804e..0b5372a 100644 --- a/czkawka_core/src/empty_folder.rs +++ b/czkawka_core/src/empty_folder.rs @@ -5,11 +5,21 @@ use crate::common_messages::Messages; use crate::common_traits::{DebugPrint, PrintResults, SaveResults}; use crossbeam_channel::Receiver; use std::collections::BTreeMap; -use std::fs; use std::fs::{File, Metadata}; use std::io::Write; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread::sleep; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; + +#[derive(Debug)] +pub struct ProgressData { + pub current_stage: u8, + pub max_stage: u8, + pub folders_checked: usize, +} /// Enum with values which show if folder is empty. /// In function "optimize_folders" automatically "Maybe" is changed to "Yes", so it is not necessary to put it here @@ -88,9 +98,9 @@ impl EmptyFolder { self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); } /// Public function used by CLI to search for empty folders - pub fn find_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.directories.optimize_directories(true, &mut self.text_messages); - if !self.check_for_empty_folders(stop_receiver) { + if !self.check_for_empty_folders(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -128,11 +138,40 @@ impl EmptyFolder { /// Function to check if folder are empty. /// Parameter initial_checking for second check before deleting to be sure that checked folder is still empty - fn check_for_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_for_empty_folders(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 folders_checked: BTreeMap = Default::default(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_folder_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_folder_counter = atomic_folder_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 0, + max_stage: 0, + folders_checked: atomic_folder_counter.load(Ordering::Relaxed) as usize, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + // Add root folders for finding for id in &self.directories.included_directories { folders_checked.insert( @@ -148,6 +187,9 @@ impl EmptyFolder { while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } self.information.number_of_checked_folders += 1; @@ -178,6 +220,7 @@ impl EmptyFolder { }; // If child is dir, still folder may be considered as empty if all children are only directories. if metadata.is_dir() { + atomic_folder_counter.fetch_add(1, Ordering::Relaxed); let next_folder = current_folder.join(entry_data.file_name()); if self.excluded_items.is_excluded(&next_folder) || self.directories.is_excluded(&next_folder) { set_as_not_empty_folder(&mut folders_checked, ¤t_folder); @@ -187,7 +230,7 @@ impl EmptyFolder { folders_checked.insert( next_folder.clone(), FolderEntry { - parent_path: Option::from(current_folder.clone()), + parent_path: Some(current_folder.clone()), is_empty: FolderEmptiness::Maybe, modified_date: match metadata.modified() { Ok(t) => match t.duration_since(UNIX_EPOCH) { @@ -211,6 +254,9 @@ impl EmptyFolder { } } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); // We need to set empty folder list #[allow(unused_mut)] // Used is later by Windows build diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs index b8169bf..dcfc164 100644 --- a/czkawka_core/src/same_music.rs +++ b/czkawka_core/src/same_music.rs @@ -1,8 +1,8 @@ -use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; use crate::common::Common; use crate::common_directory::Directories; @@ -13,6 +13,17 @@ use audiotags::Tag; use crossbeam_channel::Receiver; use rayon::prelude::*; use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread::sleep; + +#[derive(Debug)] +pub struct ProgressData { + pub current_stage: u8, + pub max_stage: u8, + pub music_checked: usize, + pub music_to_check: usize, +} #[derive(Eq, PartialEq, Clone, Debug)] pub enum DeleteMethod { @@ -104,17 +115,17 @@ impl SameMusic { } } - pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_same_music(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); - if !self.check_files(stop_receiver) { + if !self.check_files(stop_receiver, progress_sender) { self.stopped_search = true; return; } - if !self.check_records_multithreaded(stop_receiver) { + if !self.check_records_multithreaded(stop_receiver, progress_sender) { self.stopped_search = true; return; } - if !self.check_for_duplicates(stop_receiver) { + if !self.check_for_duplicates(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -166,7 +177,7 @@ impl SameMusic { } /// Check files for any with size == 0 - fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -176,8 +187,40 @@ impl SameMusic { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 0, + max_stage: 2, + music_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + music_to_check: 0, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let current_folder = folders_to_check.pop().unwrap(); @@ -221,6 +264,7 @@ impl SameMusic { folders_to_check.push(next_folder); } else if metadata.is_file() { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); // Checking files if metadata.len() >= self.minimal_file_size { let current_file_name = current_folder.join(entry_data.file_name()); @@ -274,21 +318,58 @@ impl SameMusic { } } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); self.information.number_of_music_entries = self.music_entries.len(); Common::print_time(start_time, SystemTime::now(), "check_files".to_string()); true } - fn check_records_multithreaded(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_records_multithreaded(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); + let check_was_breaked = AtomicBool::new(false); // Used for breaking from GUI and ending check thread + + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + let music_to_check = self.music_to_check.len(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 1, + max_stage: 2, + music_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + music_to_check, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + let vec_file_entry = self .music_to_check .par_iter() .map(|file_entry| { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { - // This will not break + check_was_breaked.store(true, Ordering::Relaxed); return None; } let mut file_entry = file_entry.clone(); @@ -326,24 +407,68 @@ impl SameMusic { .map(|file_entry| file_entry.unwrap()) .collect::>(); + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + + // Check if user aborted search(only from GUI) + if check_was_breaked.load(Ordering::Relaxed) { + return false; + } + // Adding files to Vector self.music_entries = vec_file_entry; Common::print_time(start_time, SystemTime::now(), "check_records_multithreaded".to_string()); true } - fn check_for_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_for_duplicates(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { if MusicSimilarity::NONE == self.music_similarity { panic!("This can't be none"); } let start_time: SystemTime = SystemTime::now(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + let music_to_check = self.music_to_check.len(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 2, + max_stage: 2, + music_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + music_to_check, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + let mut old_duplicates: Vec> = vec![self.music_entries.clone()]; let mut new_duplicates: Vec> = Vec::new(); if (self.music_similarity & MusicSimilarity::TITLE) == MusicSimilarity::TITLE { for vec_file_entry in old_duplicates { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let mut hash_map: HashMap> = Default::default(); @@ -366,7 +491,11 @@ impl SameMusic { if (self.music_similarity & MusicSimilarity::ARTIST) == MusicSimilarity::ARTIST { for vec_file_entry in old_duplicates { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let mut hash_map: HashMap> = Default::default(); @@ -389,7 +518,11 @@ impl SameMusic { if (self.music_similarity & MusicSimilarity::ALBUM_TITLE) == MusicSimilarity::ALBUM_TITLE { for vec_file_entry in old_duplicates { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let mut hash_map: HashMap> = Default::default(); @@ -412,7 +545,11 @@ impl SameMusic { if (self.music_similarity & MusicSimilarity::ALBUM_ARTIST) == MusicSimilarity::ALBUM_ARTIST { for vec_file_entry in old_duplicates { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let mut hash_map: HashMap> = Default::default(); @@ -435,7 +572,11 @@ impl SameMusic { if (self.music_similarity & MusicSimilarity::YEAR) == MusicSimilarity::YEAR { for vec_file_entry in old_duplicates { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let mut hash_map: HashMap> = Default::default(); @@ -461,6 +602,9 @@ impl SameMusic { for vec in &self.duplicated_music_entries { self.information.number_of_duplicates_music_files += vec.len() - 1; } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); Common::print_time(start_time, SystemTime::now(), "check_for_duplicates".to_string()); true diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 0f58032..0bebaf1 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -10,11 +10,22 @@ use image::GenericImageView; use img_hash::HasherConfig; use rayon::prelude::*; use std::collections::HashMap; -use std::fs; use std::fs::{File, Metadata}; use std::io::Write; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread::sleep; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; + +#[derive(Debug)] +pub struct ProgressData { + pub current_stage: u8, + pub max_stage: u8, + pub images_checked: usize, + pub images_to_check: usize, +} #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] pub enum Similarity { @@ -133,13 +144,13 @@ impl SimilarImages { } /// Public function used by CLI to search for empty folders - pub fn find_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.directories.optimize_directories(true, &mut self.text_messages); - if !self.check_for_similar_images(stop_receiver) { + if !self.check_for_similar_images(stop_receiver, progress_sender) { self.stopped_search = true; return; } - if !self.sort_images(stop_receiver) { + if !self.sort_images(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -155,7 +166,7 @@ impl SimilarImages { /// Function to check if folder are empty. /// Parameter initial_checking for second check before deleting to be sure that checked folder is still empty - fn check_for_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_for_similar_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -165,8 +176,41 @@ impl SimilarImages { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 0, + max_stage: 1, + images_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + images_to_check: 0, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let current_folder = folders_to_check.pop().unwrap(); @@ -214,7 +258,8 @@ impl SimilarImages { folders_to_check.push(next_folder); } else if metadata.is_file() { - // let mut have_valid_extension: bool; + atomic_file_counter.fetch_add(1, Ordering::Relaxed); + let file_name_lowercase: String = match entry_data.file_name().into_string() { Ok(t) => t, Err(_) => continue, @@ -222,7 +267,7 @@ impl SimilarImages { .to_lowercase(); // Checking allowed image extensions - let allowed_image_extensions = ["jpg", "png", "bmp"]; + let allowed_image_extensions = ["jpg", "png", "bmp", "ico", "webp", "tiff", "dds"]; if !allowed_image_extensions.iter().any(|e| file_name_lowercase.ends_with(e)) { self.information.number_of_ignored_files += 1; continue 'dir; @@ -270,17 +315,52 @@ impl SimilarImages { } } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); Common::print_time(start_time, SystemTime::now(), "check_for_similar_images".to_string()); true } - fn sort_images(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn sort_images(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let hash_map_modification = SystemTime::now(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + let images_to_check = self.images_to_check.len(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 1, + max_stage: 1, + images_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + images_to_check, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + let vec_file_entry: Vec<(FileEntry, Node)> = self .images_to_check .par_iter() .map(|file_entry| { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { // This will not break return None; @@ -307,6 +387,10 @@ impl SimilarImages { .map(|file_entry| file_entry.unwrap()) .collect::>(); + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + Common::print_time(hash_map_modification, SystemTime::now(), "sort_images - reading data from files in parralell".to_string()); let hash_map_modification = SystemTime::now(); @@ -335,6 +419,9 @@ impl SimilarImages { // And A is checked before D // Then C is shown that is similar group A, not D + // TODO + // Maybe also add here progress report + let mut new_vector: Vec> = Vec::new(); let mut hashes_to_check = self.image_hashes.clone(); for (hash, vec_file_entry) in &self.image_hashes { diff --git a/czkawka_core/src/temporary.rs b/czkawka_core/src/temporary.rs index 71d0ad9..3da9958 100644 --- a/czkawka_core/src/temporary.rs +++ b/czkawka_core/src/temporary.rs @@ -1,8 +1,8 @@ -use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; use crate::common::Common; use crate::common_directory::Directories; @@ -10,6 +10,16 @@ use crate::common_items::ExcludedItems; use crate::common_messages::Messages; use crate::common_traits::*; use crossbeam_channel::Receiver; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread::sleep; + +#[derive(Debug)] +pub struct ProgressData { + pub current_stage: u8, + pub max_stage: u8, + pub files_checked: usize, +} #[derive(Eq, PartialEq, Clone, Debug)] pub enum DeleteMethod { @@ -67,9 +77,9 @@ impl Temporary { } /// Finding temporary files, save results to internal struct variables - pub fn find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_temporary_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); - if !self.check_files(stop_receiver) { + if !self.check_files(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -111,7 +121,7 @@ impl Temporary { self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); } - fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -121,8 +131,40 @@ impl Temporary { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 0, + max_stage: 0, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } @@ -166,6 +208,7 @@ impl Temporary { folders_to_check.push(next_folder); } else if metadata.is_file() { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); let file_name_lowercase: String = match entry_data.file_name().into_string() { Ok(t) => t, Err(_) => continue, @@ -213,6 +256,9 @@ impl Temporary { } } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); self.information.number_of_temporary_files = self.temporary_files.len(); Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string()); diff --git a/czkawka_core/src/zeroed.rs b/czkawka_core/src/zeroed.rs index 1989747..a0b3333 100644 --- a/czkawka_core/src/zeroed.rs +++ b/czkawka_core/src/zeroed.rs @@ -1,8 +1,8 @@ -use std::fs; use std::fs::{File, Metadata}; use std::io::prelude::*; use std::path::PathBuf; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{fs, thread}; use crate::common::Common; use crate::common_directory::Directories; @@ -12,6 +12,17 @@ use crate::common_messages::Messages; use crate::common_traits::*; use crossbeam_channel::Receiver; use rayon::prelude::*; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::thread::sleep; + +#[derive(Debug)] +pub struct ProgressData { + pub current_stage: u8, + pub max_stage: u8, + pub files_checked: usize, + pub files_to_check: usize, +} #[derive(Eq, PartialEq, Clone, Debug)] pub enum DeleteMethod { @@ -75,13 +86,13 @@ impl ZeroedFiles { } } - pub fn find_zeroed_files(&mut self, stop_receiver: Option<&Receiver<()>>) { + pub fn find_zeroed_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) { self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); - if !self.check_files(stop_receiver) { + if !self.check_files(stop_receiver, progress_sender) { self.stopped_search = true; return; } - if !self.check_for_zeroed_files(stop_receiver) { + if !self.check_for_zeroed_files(stop_receiver, progress_sender) { self.stopped_search = true; return; } @@ -136,7 +147,7 @@ impl ZeroedFiles { } /// Check files for files which have 0 - fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); 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 @@ -146,8 +157,40 @@ impl ZeroedFiles { } self.information.number_of_checked_folders += folders_to_check.len(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 0, + max_stage: 1, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + files_to_check: 0, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END while !folders_to_check.is_empty() { if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); return false; } let current_folder = folders_to_check.pop().unwrap(); @@ -191,6 +234,7 @@ impl ZeroedFiles { folders_to_check.push(next_folder); } else if metadata.is_file() { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if metadata.len() == 0 || metadata.len() < self.minimal_file_size { self.information.number_of_ignored_files += 1; continue 'dir; @@ -246,19 +290,54 @@ impl ZeroedFiles { } } } + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); Common::print_time(start_time, SystemTime::now(), "check_files".to_string()); true } /// Check files for files which have 0 - fn check_for_zeroed_files(&mut self, stop_receiver: Option<&Receiver<()>>) -> bool { + fn check_for_zeroed_files(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&futures::channel::mpsc::Sender>) -> bool { let start_time: SystemTime = SystemTime::now(); + //// PROGRESS THREAD START + const LOOP_DURATION: u32 = 200; //in ms + let progress_thread_run = Arc::new(AtomicBool::new(true)); + + let atomic_file_counter = Arc::new(AtomicUsize::new(0)); + + let progress_thread_handle; + if let Some(progress_sender) = progress_sender { + let mut progress_send = progress_sender.clone(); + let progress_thread_run = progress_thread_run.clone(); + let atomic_file_counter = atomic_file_counter.clone(); + let files_to_check = self.files_to_check.len(); + progress_thread_handle = thread::spawn(move || loop { + progress_send + .try_send(ProgressData { + current_stage: 1, + max_stage: 1, + files_checked: atomic_file_counter.load(Ordering::Relaxed) as usize, + files_to_check, + }) + .unwrap(); + if !progress_thread_run.load(Ordering::Relaxed) { + break; + } + sleep(Duration::from_millis(LOOP_DURATION as u64)); + }); + } else { + progress_thread_handle = thread::spawn(|| {}); + } + //// PROGRESS THREAD END + self.zeroed_files = self .files_to_check .par_iter() .map(|file_entry| { + atomic_file_counter.fetch_add(1, Ordering::Relaxed); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { // This will not break return None; @@ -312,6 +391,10 @@ impl ZeroedFiles { .map(|file_entry| file_entry.unwrap()) .collect::>(); + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + self.information.number_of_zeroed_files = self.zeroed_files.len(); Common::print_time(start_time, SystemTime::now(), "search for zeroed_files".to_string()); diff --git a/czkawka_gui/Cargo.toml b/czkawka_gui/Cargo.toml index 1b90d7f..f2e0882 100644 --- a/czkawka_gui/Cargo.toml +++ b/czkawka_gui/Cargo.toml @@ -19,6 +19,8 @@ chrono = "0.4" crossbeam-channel = "0.4.4" +futures = "0.3.8" + # For opening files open = "1.4.0" diff --git a/czkawka_gui/czkawka.glade b/czkawka_gui/czkawka.glade index 65ea0fd..b14bb26 100644 --- a/czkawka_gui/czkawka.glade +++ b/czkawka_gui/czkawka.glade @@ -3,7 +3,7 @@ The MIT License (MIT) -Copyright (c) +Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -30,8 +30,157 @@ Author: Rafał Mikrut - + + + False + dialog + + + False + 10 + 10 + 10 + 10 + vertical + 10 + + + False + end + + + True + True + True + + + True + False + + + True + False + + + True + False + dialog-cancel + + + False + True + 0 + + + + + True + False + Stop + + + True + True + 1 + + + + + + + + + True + True + 0 + + + + + + + + False + False + 0 + + + + + True + False + + + True + False + Current stage: + + + 0 + 0 + + + + + True + False + All stages: + + + 0 + 1 + + + + + True + False + True + + + 1 + 0 + + + + + True + False + True + 0.09999999977648258 + True + + + 1 + 1 + + + + + True + True + 0 + + + + + True + False + Stage 1/2 + + + False + True + 1 + + + + + + + + False @@ -229,7 +378,7 @@ Author: Rafał Mikrut True False - gtk-add + list-add False @@ -280,7 +429,7 @@ Author: Rafał Mikrut True False - gtk-remove + list-remove False @@ -1520,7 +1669,7 @@ Author: Rafał Mikrut True False - gtk-find + edit-find False @@ -1569,7 +1718,7 @@ Author: Rafał Mikrut True False - gtk-cancel + application-exit False @@ -1618,7 +1767,7 @@ Author: Rafał Mikrut True False - gtk-media-pause + media-playback-pause False @@ -1667,7 +1816,7 @@ Author: Rafał Mikrut True False - gtk-media-play + media-playback-start False @@ -1728,7 +1877,7 @@ Author: Rafał Mikrut True False - gtk-edit + network-transmit-receive False @@ -1777,7 +1926,7 @@ Author: Rafał Mikrut True False - gtk-remove + list-remove False @@ -1826,7 +1975,7 @@ Author: Rafał Mikrut True False - gtk-floppy + document-save False diff --git a/czkawka_gui/src/connect_button_delete.rs b/czkawka_gui/src/connect_button_delete.rs index 6320313..b321f23 100644 --- a/czkawka_gui/src/connect_button_delete.rs +++ b/czkawka_gui/src/connect_button_delete.rs @@ -24,12 +24,7 @@ pub fn connect_button_delete(gui_data: &GuiData) { buttons_delete.connect_clicked(move |_| { if *shared_confirmation_dialog_delete_dialog_showing_state.borrow_mut() { - let confirmation_dialog_delete = gtk::Dialog::with_buttons( - Option::from("Delete confirmation"), - Option::from(&window_main), - gtk::DialogFlags::MODAL, - &[("Ok", gtk::ResponseType::Ok), ("Close", gtk::ResponseType::Cancel)], - ); + let confirmation_dialog_delete = gtk::Dialog::with_buttons(Some("Delete confirmation"), Some(&window_main), gtk::DialogFlags::MODAL, &[("Ok", gtk::ResponseType::Ok), ("Close", gtk::ResponseType::Cancel)]); let label: gtk::Label = gtk::Label::new(Some("Are you sure that you want to delete files?")); let check_button: gtk::CheckButton = gtk::CheckButton::with_label("Ask in future"); check_button.set_active(true); diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 67a5ec5..c946c7f 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -15,7 +15,19 @@ use glib::Sender; use gtk::prelude::*; use std::thread; -pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { +#[allow(clippy::too_many_arguments)] +pub fn connect_button_search( + gui_data: &GuiData, + glib_stop_sender: Sender, + futures_sender_duplicate_files: futures::channel::mpsc::Sender, + futures_sender_empty_files: futures::channel::mpsc::Sender, + futures_sender_empty_folder: futures::channel::mpsc::Sender, + futures_sender_big_file: futures::channel::mpsc::Sender, + futures_sender_same_music: futures::channel::mpsc::Sender, + futures_sender_similar_images: futures::channel::mpsc::Sender, + futures_sender_temporary: futures::channel::mpsc::Sender, + futures_sender_zeroed: futures::channel::mpsc::Sender, +) { let entry_info = gui_data.entry_info.clone(); let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); let notebook_main = gui_data.notebook_main.clone(); @@ -56,6 +68,11 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone(); let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone(); let text_view_errors = gui_data.text_view_errors.clone(); + let dialog_progress = gui_data.dialog_progress.clone(); + let label_stage = gui_data.label_stage.clone(); + let grid_progress_stages = gui_data.grid_progress_stages.clone(); + let progress_bar_current_stage = gui_data.progress_bar_current_stage.clone(); + let progress_bar_all_stages = gui_data.progress_bar_all_stages.clone(); buttons_search_clone.connect_clicked(move |_| { let included_directories = get_string_from_list_store(&scrolled_window_included_directories); @@ -66,13 +83,21 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { hide_all_buttons_except("stop", &buttons_array, &buttons_names); - // Disable main notebook from any iteraction until search will end + // Disable main notebook from any iteration until search will end notebook_main.set_sensitive(false); entry_info.set_text("Searching data, it may take a while, please wait..."); + // Resets progress bars + progress_bar_all_stages.set_fraction(0 as f64); + progress_bar_current_stage.set_fraction(0 as f64); + match notebook_main_children_names.get(notebook_main.get_current_page().unwrap() as usize).unwrap().as_str() { "notebook_main_duplicate_finder_label" => { + label_stage.show(); + grid_progress_stages.show_all(); + dialog_progress.resize(1, 1); + get_list_store(&scrolled_window_duplicate_finder).clear(); text_view_errors.get_buffer().unwrap().set_text(""); @@ -93,8 +118,10 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { Err(_) => 1024, // By default }; - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); + + let futures_sender_duplicate_files = futures_sender_duplicate_files.clone(); // Find duplicates thread::spawn(move || { let mut df = DuplicateFinder::new(); @@ -105,34 +132,22 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { df.set_allowed_extensions(allowed_extensions); df.set_minimal_file_size(minimal_file_size); df.set_check_method(check_method); - df.find_duplicates(Option::from(&receiver_stop)); - let _ = sender.send(Message::Duplicates(df)); - }); - } - "scrolled_window_main_empty_folder_finder" => { - get_list_store(&scrolled_window_main_empty_folder_finder).clear(); - text_view_errors.get_buffer().unwrap().set_text(""); - - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); - - // Find empty folders - thread::spawn(move || { - let mut ef = EmptyFolder::new(); - ef.set_included_directory(included_directories); - ef.set_excluded_directory(excluded_directories); - ef.set_excluded_items(excluded_items); - ef.find_empty_folders(Option::from(&receiver_stop)); - let _ = sender.send(Message::EmptyFolders(ef)); + df.find_duplicates(Some(&stop_receiver), Some(&futures_sender_duplicate_files)); + let _ = glib_stop_sender.send(Message::Duplicates(df)); }); } "scrolled_window_main_empty_files_finder" => { + label_stage.show(); + grid_progress_stages.hide(); + dialog_progress.resize(1, 1); + get_list_store(&scrolled_window_main_empty_files_finder).clear(); text_view_errors.get_buffer().unwrap().set_text(""); - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); + let futures_sender_empty_files = futures_sender_empty_files.clone(); // Find empty files thread::spawn(move || { let mut vf = EmptyFiles::new(); @@ -142,30 +157,37 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { vf.set_recursive_search(recursive_search); vf.set_excluded_items(excluded_items); vf.set_allowed_extensions(allowed_extensions); - vf.find_empty_files(Option::from(&receiver_stop)); - let _ = sender.send(Message::EmptyFiles(vf)); + vf.find_empty_files(Some(&stop_receiver), Some(&futures_sender_empty_files)); + let _ = glib_stop_sender.send(Message::EmptyFiles(vf)); }); } - "scrolled_window_main_temporary_files_finder" => { - get_list_store(&scrolled_window_main_temporary_files_finder).clear(); + "scrolled_window_main_empty_folder_finder" => { + label_stage.show(); + grid_progress_stages.hide(); + dialog_progress.resize(1, 1); + + get_list_store(&scrolled_window_main_empty_folder_finder).clear(); text_view_errors.get_buffer().unwrap().set_text(""); - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); - // Find temporary files + let futures_sender_empty_folder = futures_sender_empty_folder.clone(); + // Find empty folders thread::spawn(move || { - let mut tf = Temporary::new(); - - tf.set_included_directory(included_directories); - tf.set_excluded_directory(excluded_directories); - tf.set_recursive_search(recursive_search); - tf.set_excluded_items(excluded_items); - tf.find_temporary_files(Option::from(&receiver_stop)); - let _ = sender.send(Message::Temporary(tf)); + let mut ef = EmptyFolder::new(); + ef.set_included_directory(included_directories); + ef.set_excluded_directory(excluded_directories); + ef.set_excluded_items(excluded_items); + ef.find_empty_folders(Some(&stop_receiver), Some(&futures_sender_empty_folder)); + let _ = glib_stop_sender.send(Message::EmptyFolders(ef)); }); } "notebook_big_main_file_finder" => { + label_stage.show(); + grid_progress_stages.hide(); + dialog_progress.resize(1, 1); + get_list_store(&scrolled_window_big_files_finder).clear(); text_view_errors.get_buffer().unwrap().set_text(""); @@ -174,9 +196,9 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { Err(_) => 50, // By default }; - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); - + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); + let futures_sender_big_file = futures_sender_big_file.clone(); // Find big files thread::spawn(move || { let mut bf = BigFile::new(); @@ -186,17 +208,44 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { bf.set_recursive_search(recursive_search); bf.set_excluded_items(excluded_items); bf.set_number_of_files_to_check(numbers_of_files_to_check); - bf.find_big_files(Option::from(&receiver_stop)); - let _ = sender.send(Message::BigFiles(bf)); + bf.find_big_files(Some(&stop_receiver), Some(&futures_sender_big_file)); + let _ = glib_stop_sender.send(Message::BigFiles(bf)); }); } + "scrolled_window_main_temporary_files_finder" => { + label_stage.show(); + grid_progress_stages.hide(); + dialog_progress.resize(1, 1); + get_list_store(&scrolled_window_main_temporary_files_finder).clear(); + text_view_errors.get_buffer().unwrap().set_text(""); + + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); + + let futures_sender_temporary = futures_sender_temporary.clone(); + // Find temporary files + thread::spawn(move || { + let mut tf = Temporary::new(); + + tf.set_included_directory(included_directories); + tf.set_excluded_directory(excluded_directories); + tf.set_recursive_search(recursive_search); + tf.set_excluded_items(excluded_items); + tf.find_temporary_files(Some(&stop_receiver), Some(&futures_sender_temporary)); + let _ = glib_stop_sender.send(Message::Temporary(tf)); + }); + } "notebook_main_similar_images_finder_label" => { + label_stage.show(); + grid_progress_stages.show_all(); + dialog_progress.resize(1, 1); + get_list_store(&scrolled_window_similar_images_finder).clear(); text_view_errors.get_buffer().unwrap().set_text(""); - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); let minimal_file_size = match entry_similar_images_minimal_size.get_text().as_str().parse::() { Ok(t) => t, @@ -218,6 +267,7 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { panic!("No radio button is pressed"); } + let futures_sender_similar_images = futures_sender_similar_images.clone(); // Find similar images thread::spawn(move || { let mut sf = SimilarImages::new(); @@ -228,17 +278,22 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { sf.set_excluded_items(excluded_items); sf.set_minimal_file_size(minimal_file_size); sf.set_similarity(similarity); - sf.find_similar_images(Option::from(&receiver_stop)); - let _ = sender.send(Message::SimilarImages(sf)); + sf.find_similar_images(Some(&stop_receiver), Some(&futures_sender_similar_images)); + let _ = glib_stop_sender.send(Message::SimilarImages(sf)); }); } "notebook_main_zeroed_files_finder" => { + label_stage.show(); + grid_progress_stages.show_all(); + dialog_progress.resize(1, 1); + get_list_store(&scrolled_window_zeroed_files_finder).clear(); text_view_errors.get_buffer().unwrap().set_text(""); - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); + let futures_sender_zeroed = futures_sender_zeroed.clone(); // Find zeroed files thread::spawn(move || { let mut zf = ZeroedFiles::new(); @@ -248,11 +303,15 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { zf.set_recursive_search(recursive_search); zf.set_excluded_items(excluded_items); zf.set_allowed_extensions(allowed_extensions); - zf.find_zeroed_files(Option::from(&receiver_stop)); - let _ = sender.send(Message::ZeroedFiles(zf)); + zf.find_zeroed_files(Some(&stop_receiver), Some(&futures_sender_zeroed)); + let _ = glib_stop_sender.send(Message::ZeroedFiles(zf)); }); } "notebook_main_same_music_finder" => { + label_stage.show(); + grid_progress_stages.show_all(); + dialog_progress.resize(1, 1); + get_list_store(&scrolled_window_same_music_finder).clear(); text_view_errors.get_buffer().unwrap().set_text(""); @@ -279,10 +338,11 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { } if music_similarity != MusicSimilarity::NONE { - let sender = sender.clone(); - let receiver_stop = stop_receiver.clone(); + let glib_stop_sender = glib_stop_sender.clone(); + let stop_receiver = stop_receiver.clone(); - // Find temporary files + let futures_sender_same_music = futures_sender_same_music.clone(); + // Find Similar music thread::spawn(move || { let mut mf = SameMusic::new(); @@ -292,8 +352,8 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { mf.set_minimal_file_size(minimal_file_size); mf.set_recursive_search(recursive_search); mf.set_music_similarity(music_similarity); - mf.find_same_music(Option::from(&receiver_stop)); - let _ = sender.send(Message::SameMusic(mf)); + mf.find_same_music(Some(&stop_receiver), Some(&futures_sender_same_music)); + let _ = glib_stop_sender.send(Message::SameMusic(mf)); }); } else { notebook_main.set_sensitive(true); @@ -303,5 +363,8 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { } e => panic!("Not existent {}", e), } + + // Show progress dialog + dialog_progress.show(); }); } diff --git a/czkawka_gui/src/connect_button_stop.rs b/czkawka_gui/src/connect_button_stop.rs index ac62f7a..7fbdae0 100644 --- a/czkawka_gui/src/connect_button_stop.rs +++ b/czkawka_gui/src/connect_button_stop.rs @@ -3,9 +3,18 @@ use crate::gui_data::GuiData; use gtk::prelude::*; pub fn connect_button_stop(gui_data: &GuiData) { - let buttons_stop = gui_data.buttons_stop.clone(); + // TODO remove it when it will not be used + { + let buttons_stop = gui_data.buttons_stop.clone(); + let stop_sender = gui_data.stop_sender.clone(); + buttons_stop.connect_clicked(move |_| { + stop_sender.send(()).unwrap(); + }); + } + + let button_stop_in_dialog = gui_data.button_stop_in_dialog.clone(); let stop_sender = gui_data.stop_sender.clone(); - buttons_stop.connect_clicked(move |_| { + button_stop_in_dialog.connect_clicked(move |_| { stop_sender.send(()).unwrap(); }); } diff --git a/czkawka_gui/src/connect_compute_results.rs b/czkawka_gui/src/connect_compute_results.rs index 654fd35..f2b3685 100644 --- a/czkawka_gui/src/connect_compute_results.rs +++ b/czkawka_gui/src/connect_compute_results.rs @@ -9,7 +9,7 @@ use czkawka_core::same_music::MusicSimilarity; use glib::Receiver; use gtk::prelude::*; -pub fn connect_compute_results(gui_data: &GuiData, receiver: Receiver) { +pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver) { let buttons_search = gui_data.buttons_search.clone(); let buttons_stop = gui_data.buttons_stop.clone(); let notebook_main = gui_data.notebook_main.clone(); @@ -34,11 +34,14 @@ pub fn connect_compute_results(gui_data: &GuiData, receiver: Receiver) let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone(); let shared_same_music_state = gui_data.shared_same_music_state.clone(); let buttons_names = gui_data.buttons_names.clone(); + let dialog_progress = gui_data.dialog_progress.clone(); - receiver.attach(None, move |msg| { + glib_stop_receiver.attach(None, move |msg| { buttons_search.show(); buttons_stop.hide(); + dialog_progress.hide(); + // Restore clickability to main notebook notebook_main.set_sensitive(true); diff --git a/czkawka_gui/src/connect_progress_window.rs b/czkawka_gui/src/connect_progress_window.rs new file mode 100644 index 0000000..a03b4a8 --- /dev/null +++ b/czkawka_gui/src/connect_progress_window.rs @@ -0,0 +1,203 @@ +use crate::gui_data::GuiData; + +use czkawka_core::{big_file, duplicate, empty_files, empty_folder, same_music, similar_images, temporary, zeroed}; + +use futures::StreamExt; +use gtk::{LabelExt, ProgressBarExt, WidgetExt}; + +#[allow(clippy::too_many_arguments)] +pub fn connect_progress_window( + gui_data: &GuiData, + mut futures_receiver_duplicate_files: futures::channel::mpsc::Receiver, + mut futures_receiver_empty_files: futures::channel::mpsc::Receiver, + mut futures_receiver_empty_folder: futures::channel::mpsc::Receiver, + mut futures_receiver_big_files: futures::channel::mpsc::Receiver, + mut futures_receiver_same_music: futures::channel::mpsc::Receiver, + mut futures_receiver_similar_images: futures::channel::mpsc::Receiver, + mut futures_receiver_temporary: futures::channel::mpsc::Receiver, + mut futures_receiver_zeroed: futures::channel::mpsc::Receiver, +) { + let main_context = glib::MainContext::default(); + + { + // Duplicate Files + let label_stage = gui_data.label_stage.clone(); + let progress_bar_current_stage = gui_data.progress_bar_current_stage.clone(); + let progress_bar_all_stages = gui_data.progress_bar_all_stages.clone(); + let grid_progress_stages = gui_data.grid_progress_stages.clone(); + let future = async move { + while let Some(item) = futures_receiver_duplicate_files.next().await { + match item.checking_method { + duplicate::CheckingMethod::Hash | duplicate::CheckingMethod::HashMB => { + label_stage.show(); + match item.current_stage { + // Checking Size + 0 => { + progress_bar_current_stage.hide(); + // progress_bar_all_stages.hide(); + progress_bar_all_stages.set_fraction(0 as f64); + label_stage.set_text(format!("Scanned size of {} files", item.files_checked).as_str()); + } + // Hash - first 1KB file + 1 => { + progress_bar_current_stage.show(); + // progress_bar_all_stages.show(); + progress_bar_all_stages.set_fraction((1f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); + progress_bar_current_stage.set_fraction((item.files_checked) as f64 / item.files_to_check as f64); + label_stage.set_text(format!("Analyzed partial hash of {}/{} files", item.files_checked, item.files_to_check).as_str()); + } + // Hash - first 1MB of file or normal hash + 2 => { + progress_bar_all_stages.set_fraction((2f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); + progress_bar_current_stage.set_fraction((item.files_checked) as f64 / item.files_to_check as f64); + label_stage.set_text(format!("Analyzed full hash of {}/{} files", item.files_checked, item.files_to_check).as_str()); + } + _ => { + panic!("Not available current_stage"); + } + } + } + duplicate::CheckingMethod::Name => { + label_stage.show(); + grid_progress_stages.hide(); + + label_stage.set_text(format!("Scanned name of {} files", item.files_checked).as_str()); + } + duplicate::CheckingMethod::Size => { + label_stage.show(); + grid_progress_stages.hide(); + + label_stage.set_text(format!("Scanned size {} files", item.files_checked).as_str()); + } + duplicate::CheckingMethod::None => { + panic!(); + } + }; + } + }; + main_context.spawn_local(future); + } + { + // Empty Files + let label_stage = gui_data.label_stage.clone(); + let future = async move { + while let Some(item) = futures_receiver_empty_files.next().await { + label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + } + }; + main_context.spawn_local(future); + } + { + // Empty Folder + let label_stage = gui_data.label_stage.clone(); + let future = async move { + while let Some(item) = futures_receiver_empty_folder.next().await { + label_stage.set_text(format!("Scanned {} folders", item.folders_checked).as_str()); + } + }; + main_context.spawn_local(future); + } + { + // Big Files + let label_stage = gui_data.label_stage.clone(); + let future = async move { + while let Some(item) = futures_receiver_big_files.next().await { + label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + } + }; + main_context.spawn_local(future); + } + { + // Same Music + let label_stage = gui_data.label_stage.clone(); + let progress_bar_current_stage = gui_data.progress_bar_current_stage.clone(); + let progress_bar_all_stages = gui_data.progress_bar_all_stages.clone(); + let future = async move { + while let Some(item) = futures_receiver_same_music.next().await { + match item.current_stage { + 0 => { + progress_bar_current_stage.hide(); + label_stage.set_text(format!("Scanned {} files", item.music_checked).as_str()); + } + 1 => { + progress_bar_current_stage.show(); + progress_bar_all_stages.set_fraction((1f64 + (item.music_checked) as f64 / item.music_to_check as f64) / (item.max_stage + 1) as f64); + progress_bar_current_stage.set_fraction((item.music_checked) as f64 / item.music_to_check as f64); + label_stage.set_text(format!("Reading tags of {}/{} music files", item.music_checked, item.music_to_check).as_str()); + } + 2 => { + progress_bar_all_stages.set_fraction((2f64 + (item.music_checked) as f64 / item.music_to_check as f64) / (item.max_stage + 1) as f64); + progress_bar_current_stage.set_fraction((item.music_checked) as f64 / item.music_to_check as f64); + label_stage.set_text(format!("Checking for duplicates of {}/{} music files", item.music_checked, item.music_to_check).as_str()); + } + _ => { + panic!(); + } + } + } + }; + main_context.spawn_local(future); + } + { + // Similar Images + let label_stage = gui_data.label_stage.clone(); + let progress_bar_current_stage = gui_data.progress_bar_current_stage.clone(); + let progress_bar_all_stages = gui_data.progress_bar_all_stages.clone(); + let future = async move { + while let Some(item) = futures_receiver_similar_images.next().await { + match item.current_stage { + 0 => { + progress_bar_current_stage.hide(); + label_stage.set_text(format!("Scanned {} files", item.images_checked).as_str()); + } + 1 => { + progress_bar_current_stage.show(); + progress_bar_all_stages.set_fraction((1f64 + (item.images_checked) as f64 / item.images_to_check as f64) / (item.max_stage + 1) as f64); + progress_bar_current_stage.set_fraction((item.images_checked) as f64 / item.images_to_check as f64); + label_stage.set_text(format!("Hashing {}/{} image", item.images_checked, item.images_to_check).as_str()); + } + _ => { + panic!(); + } + } + } + }; + main_context.spawn_local(future); + } + { + // Temporary + let label_stage = gui_data.label_stage.clone(); + let future = async move { + while let Some(item) = futures_receiver_temporary.next().await { + label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + } + }; + main_context.spawn_local(future); + } + { + // Zeroed Files + let label_stage = gui_data.label_stage.clone(); + let progress_bar_current_stage = gui_data.progress_bar_current_stage.clone(); + let progress_bar_all_stages = gui_data.progress_bar_all_stages.clone(); + let future = async move { + while let Some(item) = futures_receiver_zeroed.next().await { + match item.current_stage { + 0 => { + progress_bar_current_stage.hide(); + label_stage.set_text(format!("Scanned {} files", item.files_checked).as_str()); + } + 1 => { + progress_bar_current_stage.show(); + progress_bar_all_stages.set_fraction((1f64 + (item.files_checked) as f64 / item.files_to_check as f64) / (item.max_stage + 1) as f64); + progress_bar_current_stage.set_fraction((item.files_checked) as f64 / item.files_to_check as f64); + label_stage.set_text(format!("Checking {}/{} file", item.files_checked, item.files_to_check).as_str()); + } + _ => { + panic!(); + } + } + } + }; + main_context.spawn_local(future); + } +} diff --git a/czkawka_gui/src/connect_upper_notebook.rs b/czkawka_gui/src/connect_upper_notebook.rs index 0f042ac..e52f8db 100644 --- a/czkawka_gui/src/connect_upper_notebook.rs +++ b/czkawka_gui/src/connect_upper_notebook.rs @@ -10,8 +10,8 @@ pub fn connect_upper_notebook(gui_data: &GuiData) { let buttons_add_included_directory = gui_data.buttons_add_included_directory.clone(); buttons_add_included_directory.connect_clicked(move |_| { let chooser = gtk::FileChooserDialog::with_buttons( - Option::from("Folders to include"), - Option::from(&window_main), + Some("Folders to include"), + Some(&window_main), gtk::FileChooserAction::SelectFolder, &[("Ok", gtk::ResponseType::Ok), ("Close", gtk::ResponseType::Cancel)], ); @@ -38,8 +38,8 @@ pub fn connect_upper_notebook(gui_data: &GuiData) { let buttons_add_excluded_directory = gui_data.buttons_add_excluded_directory.clone(); buttons_add_excluded_directory.connect_clicked(move |_| { let chooser = gtk::FileChooserDialog::with_buttons( - Option::from("Folders to exclude"), - Option::from(&window_main), + Some("Folders to exclude"), + Some(&window_main), gtk::FileChooserAction::SelectFolder, &[("Ok", gtk::ResponseType::Ok), ("Close", gtk::ResponseType::Cancel)], ); diff --git a/czkawka_gui/src/gui_data.rs b/czkawka_gui/src/gui_data.rs index 03e09fc..6636a74 100644 --- a/czkawka_gui/src/gui_data.rs +++ b/czkawka_gui/src/gui_data.rs @@ -137,6 +137,17 @@ pub struct GuiData { pub scrolled_window_included_directories: gtk::ScrolledWindow, pub scrolled_window_excluded_directories: gtk::ScrolledWindow, + //// Dialog State - dialog with progress state, which allows to stop task + pub dialog_progress: gtk::Dialog, + + pub progress_bar_current_stage: gtk::ProgressBar, + pub progress_bar_all_stages: gtk::ProgressBar, + + pub label_stage: gtk::Label, + + pub grid_progress_stages: gtk::Grid, + + pub button_stop_in_dialog: gtk::Button, //// Threads // Used for sending stop signal to thread @@ -328,6 +339,18 @@ impl GuiData { let scrolled_window_included_directories: gtk::ScrolledWindow = builder.get_object("scrolled_window_included_directories").unwrap(); let scrolled_window_excluded_directories: gtk::ScrolledWindow = builder.get_object("scrolled_window_excluded_directories").unwrap(); + //// Dialog State - dialog with progress state, which allows to stop task + let dialog_progress: gtk::Dialog = builder.get_object("dialog_progress").unwrap(); + + let progress_bar_current_stage: gtk::ProgressBar = builder.get_object("progress_bar_current_stage").unwrap(); + let progress_bar_all_stages: gtk::ProgressBar = builder.get_object("progress_bar_all_stages").unwrap(); + + let label_stage: gtk::Label = builder.get_object("label_stage").unwrap(); + + let grid_progress_stages: gtk::Grid = builder.get_object("grid_progress_stages").unwrap(); + + let button_stop_in_dialog: gtk::Button = builder.get_object("button_stop_in_dialog").unwrap(); + //// Threads // Types of messages to send to main thread where gui can be draw. @@ -414,6 +437,12 @@ impl GuiData { scrolled_window_same_music_finder, scrolled_window_included_directories, scrolled_window_excluded_directories, + dialog_progress, + progress_bar_current_stage, + progress_bar_all_stages, + label_stage, + grid_progress_stages, + button_stop_in_dialog, stop_sender, stop_receiver, } diff --git a/czkawka_gui/src/main.rs b/czkawka_gui/src/main.rs index 2ecd075..cd9fc0d 100644 --- a/czkawka_gui/src/main.rs +++ b/czkawka_gui/src/main.rs @@ -6,6 +6,7 @@ mod connect_button_stop; mod connect_compute_results; mod connect_notebook_tabs; mod connect_popovers; +mod connect_progress_window; mod connect_upper_notebook; mod create_tree_view; mod double_click_opening; @@ -24,6 +25,7 @@ use crate::connect_button_stop::*; use crate::connect_compute_results::*; use crate::connect_notebook_tabs::*; use crate::connect_popovers::*; +use crate::connect_progress_window::*; use crate::connect_upper_notebook::*; use crate::gui_data::*; use crate::startup_configuration::*; @@ -52,18 +54,50 @@ fn main() { let gui_data: GuiData = GuiData::new(); // Used for getting data from thread - let (sender, receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + let (glib_stop_sender, glib_stop_receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + + // Futures progress report + let (futures_sender_duplicate_files, futures_receiver_duplicate_files): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); + let (futures_sender_empty_files, futures_receiver_empty_files): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); + let (futures_sender_empty_folder, futures_receiver_empty_folder): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); + let (futures_sender_big_file, futures_receiver_big_file): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); + let (futures_sender_same_music, futures_receiver_same_music): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); + let (futures_sender_similar_images, futures_receiver_similar_images): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); + let (futures_sender_temporary, futures_receiver_temporary): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); + let (futures_sender_zeroed, futures_receiver_zeroed): (futures::channel::mpsc::Sender, futures::channel::mpsc::Receiver) = futures::channel::mpsc::channel(20); startup_configuration(&gui_data); connect_button_delete(&gui_data); connect_button_save(&gui_data); - connect_button_search(&gui_data, sender); + connect_button_search( + &gui_data, + glib_stop_sender, + futures_sender_duplicate_files, + futures_sender_empty_files, + futures_sender_empty_folder, + futures_sender_big_file, + futures_sender_same_music, + futures_sender_similar_images, + futures_sender_temporary, + futures_sender_zeroed, + ); connect_button_select(&gui_data); connect_button_stop(&gui_data); connect_notebook_tabs(&gui_data); connect_upper_notebook(&gui_data); connect_popovers(&gui_data); - connect_compute_results(&gui_data, receiver); + connect_compute_results(&gui_data, glib_stop_receiver); + connect_progress_window( + &gui_data, + futures_receiver_duplicate_files, + futures_receiver_empty_files, + futures_receiver_empty_folder, + futures_receiver_big_file, + futures_receiver_same_music, + futures_receiver_similar_images, + futures_receiver_temporary, + futures_receiver_zeroed, + ); // Quit the program when X in main window was clicked gui_data.window_main.connect_delete_event(|_, _| {