Add progress bar (#106)

This commit is contained in:
Rafał Mikrut 2020-12-02 10:25:27 +01:00 committed by GitHub
parent 4dfb960e85
commit 7ac4a26229
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1407 additions and 233 deletions

167
Cargo.lock generated
View File

@ -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"

View File

@ -4,4 +4,6 @@ members = [
"czkawka_cli",
"czkawka_gui",
"czkawka_gui_orbtk",
]
]
#[profile.release]
#lto = true

View File

@ -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) {

View File

@ -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"

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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(&current_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<u64, Vec<FileEntry>> = Default::default();

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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<String, Vec<FileEntry>> = 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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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<u64, Vec<FileEntry>> = 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<ProgressData>>) -> 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<u64, Vec<FileEntry>> = 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<String, Vec<FileEntry>>, Vec<String>, u64)> = self
.files_with_identical_size
@ -502,6 +634,7 @@ impl DuplicateFinder {
let mut errors: Vec<String> = 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<String, Vec<FileEntry>>, Vec<String>, u64)> = pre_checked_map
.par_iter()
@ -570,6 +741,7 @@ impl DuplicateFinder {
let mut errors: Vec<String> = 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;

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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<PathBuf, FolderEntry> = 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, &current_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

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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<ProgressData>>) -> 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::<Vec<_>>();
// 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<ProgressData>>) -> 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<FileEntry>> = vec![self.music_entries.clone()];
let mut new_duplicates: Vec<Vec<FileEntry>> = 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<String, Vec<FileEntry>> = 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<String, Vec<FileEntry>> = 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<String, Vec<FileEntry>> = 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<String, Vec<FileEntry>> = 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<i32, Vec<FileEntry>> = 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

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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<ProgressData>>) -> 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::<Vec<(FileEntry, Node)>>();
// 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<FileEntry>> = Vec::new();
let mut hashes_to_check = self.image_hashes.clone();
for (hash, vec_file_entry) in &self.image_hashes {

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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());

View File

@ -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<ProgressData>>) {
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<ProgressData>>) -> bool {
let start_time: SystemTime = SystemTime::now();
let mut folders_to_check: Vec<PathBuf> = 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<ProgressData>>) -> 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::<Vec<_>>();
// 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());

View File

@ -19,6 +19,8 @@ chrono = "0.4"
crossbeam-channel = "0.4.4"
futures = "0.3.8"
# For opening files
open = "1.4.0"

View File

@ -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
<requires lib="gtk+" version="3.22"/>
<!-- interface-license-type mit -->
<!-- interface-name Czkawka -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders etc. -->
<!-- interface-description Czkawka is simple and fast app to find duplicates, empty folders, similar images etc. -->
<!-- interface-authors Rafa\305\202 Mikrut -->
<object class="GtkDialog" id="dialog_progress">
<property name="can_focus">False</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="margin_start">10</property>
<property name="margin_end">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_stop_in_dialog">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<child>
<object class="GtkAlignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">dialog-cancel</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Stop</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid_progress_stages">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current stage: </property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">All stages: </property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar_current_stage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="show_text">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar_all_stages">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="pulse_step">0.09999999977648258</property>
<property name="show_text">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_stage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Stage 1/2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<child type="titlebar">
<placeholder/>
</child>
</object>
<object class="GtkPopover" id="popover_select_duplicate">
<property name="can_focus">False</property>
<child>
@ -229,7 +378,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
<property name="icon_name">list-add</property>
</object>
<packing>
<property name="expand">False</property>
@ -280,7 +429,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-remove</property>
<property name="icon_name">list-remove</property>
</object>
<packing>
<property name="expand">False</property>
@ -1520,7 +1669,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-find</property>
<property name="icon_name">edit-find</property>
</object>
<packing>
<property name="expand">False</property>
@ -1569,7 +1718,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-cancel</property>
<property name="icon_name">application-exit</property>
</object>
<packing>
<property name="expand">False</property>
@ -1618,7 +1767,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-pause</property>
<property name="icon_name">media-playback-pause</property>
</object>
<packing>
<property name="expand">False</property>
@ -1667,7 +1816,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-play</property>
<property name="icon_name">media-playback-start</property>
</object>
<packing>
<property name="expand">False</property>
@ -1728,7 +1877,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-edit</property>
<property name="icon_name">network-transmit-receive</property>
</object>
<packing>
<property name="expand">False</property>
@ -1777,7 +1926,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-remove</property>
<property name="icon_name">list-remove</property>
</object>
<packing>
<property name="expand">False</property>
@ -1826,7 +1975,7 @@ Author: Rafał Mikrut
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-floppy</property>
<property name="icon_name">document-save</property>
</object>
<packing>
<property name="expand">False</property>

View File

@ -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);

View File

@ -15,7 +15,19 @@ use glib::Sender;
use gtk::prelude::*;
use std::thread;
pub fn connect_button_search(gui_data: &GuiData, sender: Sender<Message>) {
#[allow(clippy::too_many_arguments)]
pub fn connect_button_search(
gui_data: &GuiData,
glib_stop_sender: Sender<Message>,
futures_sender_duplicate_files: futures::channel::mpsc::Sender<duplicate::ProgressData>,
futures_sender_empty_files: futures::channel::mpsc::Sender<empty_files::ProgressData>,
futures_sender_empty_folder: futures::channel::mpsc::Sender<empty_folder::ProgressData>,
futures_sender_big_file: futures::channel::mpsc::Sender<big_file::ProgressData>,
futures_sender_same_music: futures::channel::mpsc::Sender<same_music::ProgressData>,
futures_sender_similar_images: futures::channel::mpsc::Sender<similar_images::ProgressData>,
futures_sender_temporary: futures::channel::mpsc::Sender<temporary::ProgressData>,
futures_sender_zeroed: futures::channel::mpsc::Sender<zeroed::ProgressData>,
) {
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<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
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::<u64>() {
Ok(t) => t,
@ -218,6 +267,7 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
}
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<Message>) {
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<Message>) {
}
e => panic!("Not existent {}", e),
}
// Show progress dialog
dialog_progress.show();
});
}

View File

@ -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();
});
}

View File

@ -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<Message>) {
pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<Message>) {
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<Message>)
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);

View File

@ -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<duplicate::ProgressData>,
mut futures_receiver_empty_files: futures::channel::mpsc::Receiver<empty_files::ProgressData>,
mut futures_receiver_empty_folder: futures::channel::mpsc::Receiver<empty_folder::ProgressData>,
mut futures_receiver_big_files: futures::channel::mpsc::Receiver<big_file::ProgressData>,
mut futures_receiver_same_music: futures::channel::mpsc::Receiver<same_music::ProgressData>,
mut futures_receiver_similar_images: futures::channel::mpsc::Receiver<similar_images::ProgressData>,
mut futures_receiver_temporary: futures::channel::mpsc::Receiver<temporary::ProgressData>,
mut futures_receiver_zeroed: futures::channel::mpsc::Receiver<zeroed::ProgressData>,
) {
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);
}
}

View File

@ -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)],
);

View File

@ -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,
}

View File

@ -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<duplicate::ProgressData>, futures::channel::mpsc::Receiver<duplicate::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_empty_files, futures_receiver_empty_files): (futures::channel::mpsc::Sender<empty_files::ProgressData>, futures::channel::mpsc::Receiver<empty_files::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_empty_folder, futures_receiver_empty_folder): (futures::channel::mpsc::Sender<empty_folder::ProgressData>, futures::channel::mpsc::Receiver<empty_folder::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_big_file, futures_receiver_big_file): (futures::channel::mpsc::Sender<big_file::ProgressData>, futures::channel::mpsc::Receiver<big_file::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_same_music, futures_receiver_same_music): (futures::channel::mpsc::Sender<same_music::ProgressData>, futures::channel::mpsc::Receiver<same_music::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_similar_images, futures_receiver_similar_images): (futures::channel::mpsc::Sender<similar_images::ProgressData>, futures::channel::mpsc::Receiver<similar_images::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_temporary, futures_receiver_temporary): (futures::channel::mpsc::Sender<temporary::ProgressData>, futures::channel::mpsc::Receiver<temporary::ProgressData>) = futures::channel::mpsc::channel(20);
let (futures_sender_zeroed, futures_receiver_zeroed): (futures::channel::mpsc::Sender<zeroed::ProgressData>, futures::channel::mpsc::Receiver<zeroed::ProgressData>) = 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(|_, _| {