diff --git a/Cargo.lock b/Cargo.lock index da20a61..c483d48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,11 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + [[package]] name = "adler32" version = "1.2.0" @@ -38,9 +44,9 @@ checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "atk" @@ -79,6 +85,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "audiotags" +version = "0.2.7182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87d111118f42416bb5f13c5dd2e2d879925964702a435be711d4f78fa9ce6d8" +dependencies = [ + "audiotags-dev-macro", + "id3", + "metaflac", + "mp4ameta", + "thiserror", +] + +[[package]] +name = "audiotags-dev-macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79298591161f312f06327df7963063ee07466be303dcc3084a44ec293cb36e" + [[package]] name = "autocfg" version = "0.1.7" @@ -123,9 +148,9 @@ checksum = "5488039ea2c6de8668351415e39a0218a8955bffadcff0cf01d1293a20854584" [[package]] name = "blake2b_simd" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" dependencies = [ "arrayref", "arrayvec", @@ -141,7 +166,7 @@ dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if", + "cfg-if 0.1.10", "constant_time_eq", "crypto-mac", "digest", @@ -218,6 +243,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "chrono" version = "0.4.19" @@ -267,10 +298,16 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "wasm-bindgen", ] +[[package]] +name = "const_fn" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -279,11 +316,11 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "crc32fast" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -292,32 +329,41 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" dependencies = [ - "crossbeam-utils", + "crossbeam-utils 0.7.2", "maybe-uninit", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.0", +] + [[package]] name = "crossbeam-deque" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ + "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils", - "maybe-uninit", + "crossbeam-utils 0.8.0", ] [[package]] name = "crossbeam-epoch" -version = "0.8.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" dependencies = [ - "autocfg 1.0.1", - "cfg-if", - "crossbeam-utils", + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils 0.8.0", "lazy_static", - "maybe-uninit", "memoffset", "scopeguard", ] @@ -329,7 +375,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.0.1", - "cfg-if", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +dependencies = [ + "autocfg 1.0.1", + "cfg-if 1.0.0", + "const_fn", "lazy_static", ] @@ -355,9 +413,11 @@ dependencies = [ name = "czkawka_core" version = "1.2.1" dependencies = [ + "audiotags", + "bitflags", "bk-tree", "blake3", - "crossbeam-channel", + "crossbeam-channel 0.4.4", "hamming", "humansize", "image", @@ -369,7 +429,7 @@ name = "czkawka_gui" version = "1.2.1" dependencies = [ "chrono", - "crossbeam-channel", + "crossbeam-channel 0.4.4", "czkawka_core", "gdk", "gio", @@ -431,7 +491,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -490,6 +550,70 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "euclid" version = "0.20.14" @@ -499,6 +623,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "flate2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide 0.4.3", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -507,9 +643,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "futures" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8e3078b7b2a8a671cb7a3d17b4760e4181ea243227776ba83fd043b4ca034e" +checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797" dependencies = [ "futures-channel", "futures-core", @@ -522,9 +658,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74" +checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151" dependencies = [ "futures-core", "futures-sink", @@ -532,15 +668,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b" +checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46" [[package]] name = "futures-executor" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc709ca1da6f66143b8c9bec8e6260181869893714e9b5a490b169b0414144ab" +checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb" dependencies = [ "futures-core", "futures-task", @@ -549,42 +685,42 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c" +checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b" [[package]] name = "futures-macro" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f57ed14da4603b2554682e9f2ff3c65d7567b53188db96cb71538217fc64581b" +checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] name = "futures-sink" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd" +checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11" [[package]] name = "futures-task" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd26820a9f3637f1302da8bceba3ff33adbe53464b54ca24d4e2d4f1db30f94" +checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c" dependencies = [ "once_cell", ] [[package]] name = "futures-util" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a894a0acddba51a2d49a6f4263b1e64b8c579ece8af50fa86503d52cd1eea34" +checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34" dependencies = [ "futures-channel", "futures-core", @@ -681,7 +817,7 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -762,7 +898,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -856,6 +992,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + [[package]] name = "humansize" version = "1.1.0" @@ -863,13 +1005,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" [[package]] -name = "image" -version = "0.23.10" +name = "id3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985fc06b1304d19c28d5c562ed78ef5316183f2b0053b46763a0b94862373c34" +checksum = "02c11bb50ce1568516aefbe4b6564c3feaf15a8e5ccbea90fa652012446ae9bf" +dependencies = [ + "bitflags", + "byteorder", + "encoding", + "flate2", + "lazy_static", +] + +[[package]] +name = "image" +version = "0.23.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f0a8345b33b082aedec2f4d7d4a926b845cee184cbe78b703413066564431b" dependencies = [ "bytemuck", "byteorder", + "color_quant", "gif", "jpeg-decoder", "num-iter", @@ -935,17 +1091,17 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "libloading" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3557c9384f7f757f6d139cd3a4c62ef4e850696c16bf27924a5538c8a09717a1" +checksum = "1090080fe06ec2648d0da3881d9453d97e71a45f00eb179af7fdd7e3f686fdb0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "winapi", ] @@ -955,7 +1111,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", ] [[package]] @@ -969,12 +1125,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "lzw" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" - [[package]] name = "maybe-uninit" version = "2.0.0" @@ -983,9 +1133,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memoffset" @@ -996,6 +1146,17 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "metaflac" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4685bf0039a9d2919c2dbb281cba1c58d168dce58f519cbd70c468ce2c36a748" +dependencies = [ + "byteorder", + "hex", + "log", +] + [[package]] name = "minifb" version = "0.17.0" @@ -1025,6 +1186,32 @@ dependencies = [ "adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg 1.0.1", +] + +[[package]] +name = "mp4ameta" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ef7a2ba51feaa9e7be2209b6e5d7472d08427ef0ce88616ed93c66dae9601b2" +dependencies = [ + "lazy_static", + "mp4ameta_proc", +] + +[[package]] +name = "mp4ameta_proc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ae83441f6b67e3b7f009295618e90f03228b0f1a149f56ee8cd0f6bb5602c5" + [[package]] name = "nix" version = "0.17.0" @@ -1033,7 +1220,7 @@ checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" dependencies = [ "bitflags", "cc", - "cfg-if", + "cfg-if 0.1.10", "libc", "void", ] @@ -1061,9 +1248,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", "num-traits", @@ -1071,9 +1258,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ "autocfg 1.0.1", "num-integer", @@ -1082,9 +1269,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" +checksum = "e5fa6d5f418879385b213d905f7cf5bf4aa553d4c380f0152d1d4f2749186fa9" dependencies = [ "autocfg 1.0.1", "num-integer", @@ -1093,9 +1280,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg 1.0.1", ] @@ -1185,7 +1372,7 @@ dependencies = [ "case", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -1315,22 +1502,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.27" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.27" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -1366,14 +1553,14 @@ dependencies = [ "bitflags", "crc32fast", "deflate 0.8.6", - "miniz_oxide", + "miniz_oxide 0.3.7", ] [[package]] name = "ppv-lite86" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro-crate" @@ -1393,7 +1580,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", "version_check", ] @@ -1410,9 +1597,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.18" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" @@ -1627,9 +1814,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf6960dc9a5b4ee8d3e4c5787b4a112a8818e0290a42ff664ad60692fdf2032" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ "autocfg 1.0.1", "crossbeam-deque", @@ -1639,13 +1826,13 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ - "crossbeam-channel", + "crossbeam-channel 0.5.0", "crossbeam-deque", - "crossbeam-utils", + "crossbeam-utils 0.8.0", "lazy_static", "num_cpus", ] @@ -1705,7 +1892,7 @@ dependencies = [ "base64 0.12.3", "blake2b_simd", "constant_time_eq", - "crossbeam-utils", + "crossbeam-utils 0.7.2", ] [[package]] @@ -1804,7 +1991,7 @@ version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34e71125077d297d57e4c1acfe8981b5bdfbf5a20e7b589abfdcb33bf1127f86" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", ] @@ -1840,7 +2027,7 @@ checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -1917,7 +2104,7 @@ dependencies = [ "quote 1.0.7", "serde", "serde_derive", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -1933,7 +2120,7 @@ dependencies = [ "serde_derive", "serde_json", "sha1", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -1975,7 +2162,7 @@ dependencies = [ "proc-macro-error", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -1993,7 +2180,7 @@ dependencies = [ "heck", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] @@ -2021,9 +2208,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.44" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" +checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", @@ -2051,7 +2238,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "rand 0.7.3", "redox_syscall", @@ -2085,18 +2272,18 @@ checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", ] [[package]] name = "tiff" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" +checksum = "abeb4e3f32a8973722c0254189e6890358e72b1bf11becb287ee0b23c595a41d" dependencies = [ - "byteorder", - "lzw", - "miniz_oxide", + "jpeg-decoder", + "miniz_oxide 0.4.3", + "weezl", ] [[package]] @@ -2213,7 +2400,7 @@ version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "wasm-bindgen-macro", ] @@ -2228,7 +2415,7 @@ dependencies = [ "log", "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", "wasm-bindgen-shared", ] @@ -2250,7 +2437,7 @@ checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.7", - "syn 1.0.44", + "syn 1.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2333,9 +2520,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e26e7a4d998e3d7949c69444b8b4916bac810da0d3a82ae612c89e952782f4" +checksum = "8795d6e0e17485803cc10ef126bb8c0d59b7c61b219d66cfe0b3216dd0e8580a" [[package]] name = "winapi" diff --git a/README.md b/README.md index 89a292e..31a6cee 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ But the most important thing for me was to learn Rust and create a program usefu - Temporary Files - Allows finding temporary files - Similar Files - Finds files which are not exactly the same - Zeroed Files - Find files which are filled with zeros(usually corrupted) + - Same Musc - Search for music with same artist, album etc. ## Usage and requirements @@ -168,7 +169,7 @@ So still is a big room for improvements. | Big files | X | | | | Similar images | X | | X | | Zeroed Files| X | | | -| Checking files EXIF| | | X | +| Music duplicates(EXIF) | X | | X | | Installed packages | | X | | | Invalid names | | X | | | Names conflict | | X | | diff --git a/czkawka_cli/src/commands.rs b/czkawka_cli/src/commands.rs index ee185fd..05734ab 100644 --- a/czkawka_cli/src/commands.rs +++ b/czkawka_cli/src/commands.rs @@ -1,6 +1,8 @@ use czkawka_core::duplicate::{CheckingMethod, DeleteMethod}; +use czkawka_core::same_music::MusicSimilarity; use std::path::PathBuf; use structopt::StructOpt; + #[derive(Debug, StructOpt)] #[structopt(name = "czkawka", help_message = HELP_MESSAGE, template = HELP_TEMPLATE)] pub enum Commands { @@ -123,6 +125,25 @@ pub enum Commands { #[structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = "1024", help = "Minimum size in bytes", long_help = "Minimum size of checked files in bytes, assigning bigger value may speed up searching")] minimal_file_size: u64, }, + #[structopt(name = "music", about = "Finds same music by tags", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka music -d /home/rafal -f results.txt")] + SameMusic { + #[structopt(flatten)] + directories: Directories, + #[structopt(flatten)] + excluded_directories: ExcludedDirectories, + #[structopt(flatten)] + excluded_items: ExcludedItems, + // #[structopt(short = "D", long, help = "Delete found files")] + // delete_files: bool, TODO + #[structopt(short = "z", long, default_value = "artist,title", parse(try_from_str = parse_music_duplicate_type), help = "Search method (title, artist, album_title, album_artist, year)", long_help = "Sets which rows must be equal to set this files as duplicates(may be mixed, but must be divided by commas).")] + music_similarity: MusicSimilarity, + #[structopt(flatten)] + file_to_save: FileToSave, + #[structopt(flatten)] + not_recursive: NotRecursive, + #[structopt(short, long, parse(try_from_str = parse_minimal_file_size), default_value = "1024", help = "Minimum size in bytes", long_help = "Minimum size of checked files in bytes, assigning bigger value may speed up searching")] + minimal_file_size: u64, + }, } #[derive(Debug, StructOpt)] @@ -210,6 +231,38 @@ fn parse_minimal_file_size(src: &str) -> Result { } } +fn parse_music_duplicate_type(src: &str) -> Result { + if src.is_empty() { + return Ok(MusicSimilarity::NONE); + } + + let mut similarity: MusicSimilarity = MusicSimilarity::NONE; + + let parts: Vec<&str> = src.split(',').collect(); + + if parts.iter().any(|e| e.to_lowercase().contains("title") && !e.to_lowercase().contains("album")) { + similarity |= MusicSimilarity::TITLE; + } + if parts.iter().any(|e| e.to_lowercase().contains("artist") && !e.to_lowercase().contains("album")) { + similarity |= MusicSimilarity::ARTIST; + } + if parts.iter().any(|e| e.to_lowercase().contains("title") && e.to_lowercase().contains("album")) { + similarity |= MusicSimilarity::ALBUM_TITLE; + } + if parts.iter().any(|e| e.to_lowercase().contains("artist") && e.to_lowercase().contains("album")) { + similarity |= MusicSimilarity::ALBUM_ARTIST; + } + if parts.iter().any(|e| e.to_lowercase().contains("year")) { + similarity |= MusicSimilarity::YEAR; + } + + if similarity == MusicSimilarity::NONE { + return Err("Couldn't parse the music search method (allowed: title,artist,album_title,album_artist,year)".to_string()); + } + + Ok(similarity) +} + static HELP_MESSAGE: &str = "Prints help information (--help will give more information)"; const HELP_TEMPLATE: &str = r#" @@ -233,4 +286,5 @@ EXAMPLES: {bin} empty-files -d /home/rafal /home/szczekacz -e /home/rafal/Pulpit -R -f results.txt {bin} temp -d /home/rafal/ -E */.git */tmp* *Pulpit -f results.txt -D {bin} image -d /home/rafal -e /home/rafal/Pulpit -f results.txt - {bin} zeroed -d /home/rafal -e /home/rafal/Pulpit -f results.txt"#; + {bin} zeroed -d /home/rafal -e /home/krzak -f results.txt" + {bin} music -d /home/rafal -e /home/rafal/Pulpit -z "artist,year, ARTISTALBUM, ALBUM___tiTlE" -f results.txt"#; diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index d99cb1d..9fc54ea 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -10,6 +10,7 @@ use czkawka_core::{ duplicate::DuplicateFinder, empty_files::{self, EmptyFiles}, empty_folder::EmptyFolder, + same_music::SameMusic, similar_files::SimilarImages, temporary::{self, Temporary}, zeroed::{self, ZeroedFiles}, @@ -221,7 +222,6 @@ fn main() { sf.print_results(); sf.get_text_messages().print_messages(); } - Commands::ZeroedFiles { directories, excluded_directories, @@ -258,5 +258,41 @@ fn main() { zf.print_results(); zf.get_text_messages().print_messages(); } + Commands::SameMusic { + directories, + excluded_directories, + excluded_items, + // delete_files, + file_to_save, + not_recursive, + minimal_file_size, + music_similarity, + } => { + let mut mf = SameMusic::new(); + + mf.set_included_directory(path_list_to_str(directories.directories)); + mf.set_excluded_directory(path_list_to_str(excluded_directories.excluded_directories)); + mf.set_excluded_items(path_list_to_str(excluded_items.excluded_items)); + mf.set_minimal_file_size(minimal_file_size); + mf.set_recursive_search(!not_recursive.not_recursive); + mf.set_music_similarity(music_similarity); + + // if delete_files { + // // TODO mf.set_delete_method(same_music::DeleteMethod::Delete); + // } + + mf.find_same_music(None); + + if let Some(file_name) = file_to_save.file_name() { + if !mf.save_results_to_file(file_name) { + mf.get_text_messages().print_messages(); + process::exit(1); + } + } + + #[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed + mf.print_results(); + mf.get_text_messages().print_messages(); + } } } diff --git a/czkawka_core/Cargo.toml b/czkawka_core/Cargo.toml index 2ae287e..7f9b9ee 100644 --- a/czkawka_core/Cargo.toml +++ b/czkawka_core/Cargo.toml @@ -20,4 +20,8 @@ crossbeam-channel = "0.4.4" img_hash = "3.1" bk-tree = "0.3" image = "0.23" -hamming = "0.1" \ No newline at end of file +hamming = "0.1" + +# Needed by same music +bitflags = "1.2.1" +audiotags = "0.2.7182" diff --git a/czkawka_core/src/lib.rs b/czkawka_core/src/lib.rs index 42c0b50..130ef37 100644 --- a/czkawka_core/src/lib.rs +++ b/czkawka_core/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate bitflags; + pub mod big_file; pub mod duplicate; pub mod empty_files; @@ -10,6 +13,7 @@ pub mod common_extensions; pub mod common_items; pub mod common_messages; pub mod common_traits; +pub mod same_music; pub mod similar_files; pub mod zeroed; diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs new file mode 100644 index 0000000..0a240df --- /dev/null +++ b/czkawka_core/src/same_music.rs @@ -0,0 +1,557 @@ +use std::fs; +use std::fs::{File, Metadata}; +use std::io::prelude::*; +use std::path::PathBuf; +use std::time::{SystemTime, UNIX_EPOCH}; + +use crate::common::Common; +use crate::common_directory::Directories; +use crate::common_items::ExcludedItems; +use crate::common_messages::Messages; +use crate::common_traits::*; +use audiotags::Tag; +use crossbeam_channel::Receiver; +use std::collections::HashMap; + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum DeleteMethod { + None, + Delete, +} + +bitflags! { + pub struct MusicSimilarity : u32 { + const NONE = 0; + + const TITLE = 0b1; + const ARTIST = 0b10; + + const ALBUM_TITLE = 0b100; + const ALBUM_ARTIST = 0b1000; + + const YEAR = 0b10000; + // const Time = 0b100000; + } +} + +#[derive(Clone, Debug)] +pub struct FileEntry { + pub size: u64, + + pub path: PathBuf, + pub modified_date: u64, + + pub title: String, + pub artist: String, + + pub album_title: String, + pub album_artist: String, + + pub year: i32, + // pub time: u32, +} + +/// Info struck with helpful information's about results +#[derive(Default)] +pub struct Info { + pub number_of_checked_files: usize, + pub number_of_checked_folders: usize, + pub number_of_ignored_files: usize, + pub number_of_ignored_things: usize, + pub number_of_music_entries: usize, + pub number_of_removed_files: usize, + pub number_of_failed_to_remove_files: usize, + pub number_of_duplicates_music_files: usize, +} +impl Info { + pub fn new() -> Self { + Default::default() + } +} + +/// Struct with required information's to work +pub struct SameMusic { + text_messages: Messages, + information: Info, + music_entries: Vec, + duplicated_music_entries: Vec>, + directories: Directories, + excluded_items: ExcludedItems, + minimal_file_size: u64, + recursive_search: bool, + delete_method: DeleteMethod, + music_similarity: MusicSimilarity, + stopped_search: bool, +} + +impl SameMusic { + pub fn new() -> Self { + Self { + text_messages: Messages::new(), + information: Info::new(), + recursive_search: true, + directories: Directories::new(), + excluded_items: ExcludedItems::new(), + music_entries: vec![], + delete_method: DeleteMethod::None, + music_similarity: MusicSimilarity::NONE, + stopped_search: false, + minimal_file_size: 1024, + duplicated_music_entries: vec![], + } + } + + pub fn find_same_music(&mut self, rx: Option<&Receiver<()>>) { + self.directories.optimize_directories(self.recursive_search, &mut self.text_messages); + if !self.check_files(rx) { + self.stopped_search = true; + return; + } + if !self.check_for_duplicates(rx) { + self.stopped_search = true; + return; + } + self.delete_files(); + self.debug_print(); + } + + pub fn get_stopped_search(&self) -> bool { + self.stopped_search + } + + pub const fn get_duplicated_music_entries(&self) -> &Vec> { + &self.duplicated_music_entries + } + pub const fn get_music_similarity(&self) -> &MusicSimilarity { + &self.music_similarity + } + + pub const fn get_text_messages(&self) -> &Messages { + &self.text_messages + } + + pub const fn get_information(&self) -> &Info { + &self.information + } + + pub fn set_delete_method(&mut self, delete_method: DeleteMethod) { + self.delete_method = delete_method; + } + + pub fn set_recursive_search(&mut self, recursive_search: bool) { + self.recursive_search = recursive_search; + } + + pub fn set_included_directory(&mut self, included_directory: String) -> bool { + self.directories.set_included_directory(included_directory, &mut self.text_messages) + } + + pub fn set_excluded_directory(&mut self, excluded_directory: String) { + self.directories.set_excluded_directory(excluded_directory, &mut self.text_messages); + } + + pub fn set_excluded_items(&mut self, excluded_items: String) { + self.excluded_items.set_excluded_items(excluded_items, &mut self.text_messages); + } + + pub fn set_music_similarity(&mut self, music_similarity: MusicSimilarity) { + self.music_similarity = music_similarity; + } + + /// Check files for any with size == 0 + fn check_files(&mut self, rx: Option<&Receiver<()>>) -> bool { + let start_time: SystemTime = SystemTime::now(); + let mut folders_to_check: Vec = Vec::with_capacity(1024 * 2); // This should be small enough too not see to big difference and big enough to store most of paths without needing to resize vector + + // Add root folders for finding + for id in &self.directories.included_directories { + folders_to_check.push(id.clone()); + } + self.information.number_of_checked_folders += folders_to_check.len(); + + while !folders_to_check.is_empty() { + if rx.is_some() && rx.unwrap().try_recv().is_ok() { + return false; + } + let current_folder = folders_to_check.pop().unwrap(); + + // Read current dir, if permission are denied just go to next + let read_dir = match fs::read_dir(¤t_folder) { + Ok(t) => t, + Err(_) => { + self.text_messages.warnings.push(format!("Cannot open dir {}", current_folder.display())); + continue; + } // Permissions denied + }; + + // Check every sub folder/file/link etc. + 'dir: for entry in read_dir { + let entry_data = match entry { + Ok(t) => t, + Err(_) => { + self.text_messages.warnings.push(format!("Cannot read entry in dir {}", current_folder.display())); + continue 'dir; + } //Permissions denied + }; + let metadata: Metadata = match entry_data.metadata() { + Ok(t) => t, + Err(_) => { + self.text_messages.warnings.push(format!("Cannot read metadata in dir {}", current_folder.display())); + continue 'dir; + } //Permissions denied + }; + if metadata.is_dir() { + self.information.number_of_checked_folders += 1; + + if !self.recursive_search { + continue 'dir; + } + + let next_folder = current_folder.join(entry_data.file_name()); + if self.directories.is_excluded(&next_folder) || self.excluded_items.is_excluded(&next_folder) { + continue 'dir; + } + + folders_to_check.push(next_folder); + } else if metadata.is_file() { + // Checking files + if metadata.len() >= self.minimal_file_size { + let current_file_name = current_folder.join(entry_data.file_name()); + if self.excluded_items.is_excluded(¤t_file_name) { + self.information.number_of_ignored_files += 1; + continue 'dir; + } + + let allowed_extensions = [".mp3", ".flac"]; + + if !allowed_extensions.iter().any(|r| current_file_name.to_string_lossy().ends_with(r)) { + self.information.number_of_ignored_files += 1; + continue 'dir; + } + + let tag = Tag::new().read_from_path(¤t_file_name).unwrap(); + + // Creating new file entry + let fe: FileEntry = FileEntry { + size: metadata.len(), + path: current_file_name.clone(), + modified_date: match metadata.modified() { + Ok(t) => match t.duration_since(UNIX_EPOCH) { + Ok(d) => d.as_secs(), + Err(_) => { + self.text_messages.warnings.push(format!("File {} seems to be modified before Unix Epoch.", current_file_name.display())); + 0 + } + }, + Err(_) => { + self.text_messages.warnings.push(format!("Unable to get modification date from file {}", current_file_name.display())); + continue 'dir; + } // Permissions Denied + }, + title: match tag.title() { + Some(t) => t.to_string(), + None => "".to_string(), + }, + artist: match tag.artist() { + Some(t) => t.to_string(), + None => "".to_string(), + }, + album_title: match tag.album_title() { + Some(t) => t.to_string(), + None => "".to_string(), + }, + album_artist: match tag.album_artist() { + Some(t) => t.to_string(), + None => "".to_string(), + }, + year: match tag.year() { + Some(t) => t, + None => 0, + }, + }; + + // Adding files to Vector + self.music_entries.push(fe); + + self.information.number_of_checked_files += 1; + } else { + self.information.number_of_ignored_files += 1; + } + } else { + // Probably this is symbolic links so we are free to ignore this + self.information.number_of_ignored_things += 1; + } + } + } + self.information.number_of_music_entries = self.music_entries.len(); + + Common::print_time(start_time, SystemTime::now(), "check_files_size".to_string()); + true + } + + fn check_for_duplicates(&mut self, rx: Option<&Receiver<()>>) -> bool { + if MusicSimilarity::NONE == self.music_similarity { + panic!("This can't be none"); + } + let start_time: SystemTime = SystemTime::now(); + + let mut old_duplicates: Vec> = vec![self.music_entries.clone()]; + let mut new_duplicates: Vec> = Vec::new(); + + if (self.music_similarity & MusicSimilarity::TITLE) == MusicSimilarity::TITLE { + for vec_file_entry in old_duplicates { + if rx.is_some() && rx.unwrap().try_recv().is_ok() { + return false; + } + let mut hash_map: HashMap> = Default::default(); + for file_entry in vec_file_entry { + let title = file_entry.title.to_lowercase().trim().to_string(); + if title != "" { + hash_map.entry(title.clone()).or_insert_with(Vec::new); + hash_map.get_mut(title.as_str()).unwrap().push(file_entry); + } + } + for (_title, vec_file_entry) in hash_map { + if vec_file_entry.len() > 1 { + new_duplicates.push(vec_file_entry); + } + } + } + old_duplicates = new_duplicates; + new_duplicates = Vec::new(); + } + + if (self.music_similarity & MusicSimilarity::ARTIST) == MusicSimilarity::ARTIST { + for vec_file_entry in old_duplicates { + if rx.is_some() && rx.unwrap().try_recv().is_ok() { + return false; + } + let mut hash_map: HashMap> = Default::default(); + for file_entry in vec_file_entry { + let artist = file_entry.artist.to_lowercase().trim().to_string(); + if artist != "" { + hash_map.entry(artist.clone()).or_insert_with(Vec::new); + hash_map.get_mut(artist.as_str()).unwrap().push(file_entry); + } + } + for (_artist, vec_file_entry) in hash_map { + if vec_file_entry.len() > 1 { + new_duplicates.push(vec_file_entry); + } + } + } + old_duplicates = new_duplicates; + new_duplicates = Vec::new(); + } + + if (self.music_similarity & MusicSimilarity::ALBUM_TITLE) == MusicSimilarity::ALBUM_TITLE { + for vec_file_entry in old_duplicates { + if rx.is_some() && rx.unwrap().try_recv().is_ok() { + return false; + } + let mut hash_map: HashMap> = Default::default(); + for file_entry in vec_file_entry { + let album_title = file_entry.album_title.to_lowercase().trim().to_string(); + if album_title != "" { + hash_map.entry(album_title.clone()).or_insert_with(Vec::new); + hash_map.get_mut(album_title.as_str()).unwrap().push(file_entry); + } + } + for (_album_title, vec_file_entry) in hash_map { + if vec_file_entry.len() > 1 { + new_duplicates.push(vec_file_entry); + } + } + } + old_duplicates = new_duplicates; + new_duplicates = Vec::new(); + } + + if (self.music_similarity & MusicSimilarity::ALBUM_ARTIST) == MusicSimilarity::ALBUM_ARTIST { + for vec_file_entry in old_duplicates { + if rx.is_some() && rx.unwrap().try_recv().is_ok() { + return false; + } + let mut hash_map: HashMap> = Default::default(); + for file_entry in vec_file_entry { + let album_artist = file_entry.album_artist.to_lowercase().trim().to_string(); + if album_artist != "" { + hash_map.entry(album_artist.clone()).or_insert_with(Vec::new); + hash_map.get_mut(album_artist.as_str()).unwrap().push(file_entry); + } + } + for (_album_artist, vec_file_entry) in hash_map { + if vec_file_entry.len() > 1 { + new_duplicates.push(vec_file_entry); + } + } + } + old_duplicates = new_duplicates; + new_duplicates = Vec::new(); + } + + if (self.music_similarity & MusicSimilarity::YEAR) == MusicSimilarity::YEAR { + for vec_file_entry in old_duplicates { + if rx.is_some() && rx.unwrap().try_recv().is_ok() { + return false; + } + let mut hash_map: HashMap> = Default::default(); + for file_entry in vec_file_entry { + let year = file_entry.year; + if year != 0 { + hash_map.entry(year).or_insert_with(Vec::new); + hash_map.get_mut(&year).unwrap().push(file_entry); + } + } + for (_year, vec_file_entry) in hash_map { + if vec_file_entry.len() > 1 { + new_duplicates.push(vec_file_entry); + } + } + } + old_duplicates = new_duplicates; + // new_duplicates = Vec::new(); + } + + self.duplicated_music_entries = old_duplicates; + + for vec in &self.duplicated_music_entries { + self.information.number_of_duplicates_music_files += vec.len() - 1; + } + + Common::print_time(start_time, SystemTime::now(), "check_for_duplicates".to_string()); + true + } + + pub fn set_minimal_file_size(&mut self, minimal_file_size: u64) { + self.minimal_file_size = match minimal_file_size { + 0 => 1, + t => t, + }; + } + + /// Function to delete files, from filed Vector + fn delete_files(&mut self) { + let start_time: SystemTime = SystemTime::now(); + // TODO + // match self.delete_method { + // DeleteMethod::Delete => { + // for file_entry in &self.music_entries { + // if fs::remove_file(file_entry.path.clone()).is_err() { + // self.text_messages.warnings.push(file_entry.path.display().to_string()); + // } + // } + // } + // DeleteMethod::None => { + // //Just do nothing + // } + // } + + Common::print_time(start_time, SystemTime::now(), "delete_files".to_string()); + } +} +impl Default for SameMusic { + fn default() -> Self { + Self::new() + } +} + +impl DebugPrint for SameMusic { + #[allow(dead_code)] + #[allow(unreachable_code)] + /// Debugging printing - only available on debug build + fn debug_print(&self) { + #[cfg(not(debug_assertions))] + { + return; + } + println!("---------------DEBUG PRINT---------------"); + println!("### Information's"); + + println!("Errors size - {}", self.text_messages.errors.len()); + println!("Warnings size - {}", self.text_messages.warnings.len()); + println!("Messages size - {}", self.text_messages.messages.len()); + println!("Number of checked files - {}", self.information.number_of_checked_files); + println!("Number of checked folders - {}", self.information.number_of_checked_folders); + println!("Number of ignored files - {}", self.information.number_of_ignored_files); + println!("Number of ignored things(like symbolic links) - {}", self.information.number_of_ignored_things); + println!("Number of removed files - {}", self.information.number_of_removed_files); + println!("Number of failed to remove files - {}", self.information.number_of_failed_to_remove_files); + println!("Number of duplicated music files - {}", self.information.number_of_duplicates_music_files); + + println!("### Other"); + + println!("Excluded items - {:?}", self.excluded_items.items); + println!("Minimum file size - {:?}", self.minimal_file_size); + println!("Found files music - {}", self.music_entries.len()); + println!("Found duplicated files music - {}", self.duplicated_music_entries.len()); + println!("Included directories - {:?}", self.directories.included_directories); + println!("Excluded directories - {:?}", self.directories.excluded_directories); + println!("Recursive search - {}", self.recursive_search.to_string()); + println!("Delete Method - {:?}", self.delete_method); + println!("-----------------------------------------"); + } +} +impl SaveResults for SameMusic { + fn save_results_to_file(&mut self, file_name: &str) -> bool { + let start_time: SystemTime = SystemTime::now(); + let file_name: String = match file_name { + "" => "results.txt".to_string(), + k => k.to_string(), + }; + + let mut file = match File::create(&file_name) { + Ok(t) => t, + Err(_) => { + self.text_messages.errors.push(format!("Failed to create file {}", file_name)); + return false; + } + }; + + if writeln!( + file, + "Results of searching {:?} with excluded directories {:?} and excluded items {:?}", + self.directories.included_directories, self.directories.excluded_directories, self.excluded_items.items + ) + .is_err() + { + self.text_messages.errors.push(format!("Failed to save results to file {}", file_name)); + return false; + } + + if !self.music_entries.is_empty() { + writeln!(file, "Found {} same music files.", self.information.number_of_music_entries).unwrap(); + for file_entry in self.music_entries.iter() { + writeln!(file, "{}", file_entry.path.display()).unwrap(); + } + } else { + write!(file, "Not found any empty files.").unwrap(); + } + Common::print_time(start_time, SystemTime::now(), "save_results_to_file".to_string()); + true + } +} +impl PrintResults for SameMusic { + /// Print information's about duplicated entries + /// Only needed for CLI + fn print_results(&self) { + let start_time: SystemTime = SystemTime::now(); + println!("Found {} similar music files.\n", self.duplicated_music_entries.len()); + for vec_file_entry in self.duplicated_music_entries.iter() { + for file_entry in vec_file_entry { + println!( + "T: {} - A: {} - AT: {} - AA: {} - Y: {} - P: {}", + file_entry.title, + file_entry.artist, + file_entry.album_title, + file_entry.album_artist, + file_entry.year, + file_entry.path.display() + ); + } + println!(); + } + + Common::print_time(start_time, SystemTime::now(), "print_entries".to_string()); + } +} diff --git a/czkawka_gui/czkawka.glade b/czkawka_gui/czkawka.glade index 10f0a8f..ea4d541 100644 --- a/czkawka_gui/czkawka.glade +++ b/czkawka_gui/czkawka.glade @@ -1097,6 +1097,165 @@ Author: Rafał Mikrut False + + + True + False + vertical + + + True + False + 8 + + + True + False + Minimal file size(in bytes) + + + False + True + 0 + + + + + True + True + 15 + 1024 + False + number + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + 8 + + + Title + True + True + False + True + True + + + False + True + 0 + + + + + Artist + True + True + False + True + True + + + False + True + 1 + + + + + Album Title + True + True + False + True + + + False + True + 2 + + + + + Album Artist + True + True + False + True + + + False + True + 3 + + + + + Year + True + True + False + True + + + False + True + 4 + + + + + False + True + 1 + + + + + True + True + in + + + + + + True + True + 2 + + + + + 6 + + + + + True + False + Music Duplicates + + + 6 + False + + True @@ -1158,7 +1317,7 @@ Author: Rafał Mikrut - 6 + 7 @@ -1168,7 +1327,7 @@ Author: Rafał Mikrut Zeroed Files - 6 + 7 False diff --git a/czkawka_gui/src/connect_button_delete.rs b/czkawka_gui/src/connect_button_delete.rs index 15288f1..6dc8952 100644 --- a/czkawka_gui/src/connect_button_delete.rs +++ b/czkawka_gui/src/connect_button_delete.rs @@ -19,6 +19,7 @@ pub fn connect_button_delete(gui_data: &GuiData) { let scrolled_window_main_temporary_files_finder = gui_data.scrolled_window_main_temporary_files_finder.clone(); 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 scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone(); buttons_delete.connect_clicked(move |_| { if *shared_confirmation_dialog_delete_dialog_showing_state.borrow_mut() { @@ -396,7 +397,38 @@ pub fn connect_button_delete(gui_data: &GuiData) { text_view_errors.get_buffer().unwrap().set_text(messages.as_str()); selection.unselect_all(); } + "notebook_main_same_music_finder" => { + let tree_view = scrolled_window_same_music_finder.get_children().get(0).unwrap().clone().downcast::().unwrap(); + let selection = tree_view.get_selection(); + + let (selection_rows, tree_model) = selection.get_selected_rows(); + if selection_rows.is_empty() { + return; + } + let list_store = tree_model.clone().downcast::().unwrap(); + + // let new_tree_model = TreeModel::new(); // TODO - maybe create new model when inserting a new data, because this seems to be not optimal when using thousands of rows + + let mut messages: String = "".to_string(); + + // Must be deleted from end to start, because when deleting entries, TreePath(and also TreeIter) will points to invalid data + for tree_path in selection_rows.iter().rev() { + let name = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSameMusic::Name as i32).get::().unwrap().unwrap(); + let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSameMusic::Path as i32).get::().unwrap().unwrap(); + + match fs::remove_file(format!("{}/{}", path, name)) { + Ok(_) => { + list_store.remove(&list_store.get_iter(tree_path).unwrap()); + } + Err(_) => messages += format!("Failed to remove file {}/{} because file doesn't exists or you don't have permissions.\n", path, name).as_str(), + } + } + + text_view_errors.get_buffer().unwrap().set_text(messages.as_str()); + selection.unselect_all(); + } e => panic!("Not existent {}", e), } }); } +// fn basic_remove(tree_view: gtk::TreeView, column_name: i32, column_path: i32) {} // TODO, will replace simple remove of things diff --git a/czkawka_gui/src/connect_button_save.rs b/czkawka_gui/src/connect_button_save.rs index c6c21d5..d22a81b 100644 --- a/czkawka_gui/src/connect_button_save.rs +++ b/czkawka_gui/src/connect_button_save.rs @@ -14,6 +14,7 @@ pub fn connect_button_save(gui_data: &GuiData) { let shared_temporary_files_state = gui_data.shared_temporary_files_state.clone(); let shared_empty_files_state = gui_data.shared_empty_files_state.clone(); let shared_similar_images_state = gui_data.shared_similar_images_state.clone(); + let shared_same_music_state = gui_data.shared_same_music_state.clone(); let shared_zeroed_files_state = gui_data.shared_zeroed_files_state.clone(); let notebook_main_children_names = gui_data.notebook_main_children_names.clone(); let notebook_main = gui_data.notebook_main.clone(); @@ -96,7 +97,7 @@ pub fn connect_button_save(gui_data: &GuiData) { *shared_buttons.borrow_mut().get_mut("similar_images").unwrap().get_mut("save").unwrap() = false; } } - "notebook_main_zeroed_files_finder_label" => { + "notebook_main_zeroed_files_finder" => { let file_name = "results_zeroed_files.txt"; let mut zf = shared_zeroed_files_state.borrow_mut(); @@ -109,6 +110,19 @@ pub fn connect_button_save(gui_data: &GuiData) { *shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap().get_mut("save").unwrap() = false; } } + "notebook_main_same_music_finder" => { + let file_name = "results_same_music.txt"; + + let mut mf = shared_same_music_state.borrow_mut(); + mf.save_results_to_file(file_name); + + entry_info.set_text(format!("Saved results to file {}", file_name).as_str()); + // Set state + { + buttons_save.hide(); + *shared_buttons.borrow_mut().get_mut("zeroed_files").unwrap().get_mut("save").unwrap() = false; + } + } e => panic!("Not existent {}", e), }); } diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 40c6835..a33b7e0 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -7,6 +7,7 @@ use czkawka_core::big_file::BigFile; use czkawka_core::duplicate::DuplicateFinder; use czkawka_core::empty_files::EmptyFiles; use czkawka_core::empty_folder::EmptyFolder; +use czkawka_core::same_music::{MusicSimilarity, SameMusic}; use czkawka_core::similar_files::SimilarImages; use czkawka_core::temporary::Temporary; use czkawka_core::zeroed::ZeroedFiles; @@ -24,6 +25,7 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { let buttons_array = gui_data.buttons_array.clone(); let check_button_recursive = gui_data.check_button_recursive.clone(); let entry_excluded_items = gui_data.entry_excluded_items.clone(); + let entry_same_music_minimal_size = gui_data.entry_same_music_minimal_size.clone(); let entry_allowed_extensions = gui_data.entry_allowed_extensions.clone(); let buttons_names = gui_data.buttons_names.clone(); let radio_button_name = gui_data.radio_button_name.clone(); @@ -31,10 +33,16 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { let radio_button_hashmb = gui_data.radio_button_hashmb.clone(); let radio_button_hash = gui_data.radio_button_hash.clone(); let entry_duplicate_minimal_size = gui_data.entry_duplicate_minimal_size.clone(); - // let sender = gui_data.sender.clone(); let rx = gui_data.rx.clone(); let entry_big_files_number = gui_data.entry_big_files_number.clone(); let entry_similar_images_minimal_size = gui_data.entry_similar_images_minimal_size.clone(); + let check_button_music_title: gtk::CheckButton = gui_data.check_button_music_title.clone(); + let check_button_music_artist: gtk::CheckButton = gui_data.check_button_music_artist.clone(); + let check_button_music_album_title: gtk::CheckButton = gui_data.check_button_music_album_title.clone(); + let check_button_music_album_artist: gtk::CheckButton = gui_data.check_button_music_album_artist.clone(); + let check_button_music_year: gtk::CheckButton = gui_data.check_button_music_year.clone(); + let shared_buttons = gui_data.shared_buttons.clone(); + buttons_search_clone.connect_clicked(move |_| { let included_directories = get_string_from_list_store(&scrolled_window_included_directories); let excluded_directories = get_string_from_list_store(&scrolled_window_excluded_directories); @@ -82,7 +90,7 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { df.set_minimal_file_size(minimal_file_size); df.set_check_method(check_method); df.set_delete_method(delete_method); - df.find_duplicates(Option::from(&receiver_stop)); //&rc_stop_signal.borrow().1); + df.find_duplicates(Option::from(&receiver_stop)); let _ = sender.send(Message::Duplicates(df)); }); } @@ -94,7 +102,6 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { thread::spawn(move || { let mut ef = EmptyFolder::new(); ef.set_included_directory(included_directories); - ef.set_delete_folder(false); ef.find_empty_folders(Option::from(&receiver_stop)); let _ = sender.send(Message::EmptyFolders(ef)); }); @@ -181,7 +188,7 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { let sender = sender.clone(); let receiver_stop = rx.clone(); - // Find temporary files + // Find zeroed files thread::spawn(move || { let mut zf = ZeroedFiles::new(); @@ -189,10 +196,57 @@ pub fn connect_button_search(gui_data: &GuiData, sender: Sender) { zf.set_excluded_directory(excluded_directories); 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)); }); } + "notebook_main_same_music_finder" => { + let minimal_file_size = match entry_same_music_minimal_size.get_text().as_str().parse::() { + Ok(t) => t, + Err(_) => 1024, // By default + }; + let mut music_similarity: MusicSimilarity = MusicSimilarity::NONE; + + if check_button_music_title.get_active() { + music_similarity |= MusicSimilarity::TITLE; + } + if check_button_music_artist.get_active() { + music_similarity |= MusicSimilarity::ARTIST; + } + if check_button_music_album_title.get_active() { + music_similarity |= MusicSimilarity::ALBUM_TITLE; + } + if check_button_music_album_artist.get_active() { + music_similarity |= MusicSimilarity::ALBUM_ARTIST; + } + if check_button_music_year.get_active() { + music_similarity |= MusicSimilarity::YEAR; + } + + if music_similarity != MusicSimilarity::NONE { + let sender = sender.clone(); + let receiver_stop = rx.clone(); + + // Find temporary files + thread::spawn(move || { + let mut mf = SameMusic::new(); + + mf.set_included_directory(included_directories); + mf.set_excluded_directory(excluded_directories); + mf.set_excluded_items(excluded_items); + 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)); + }); + } else { + notebook_main.set_sensitive(true); + set_buttons(&mut *shared_buttons.borrow_mut().get_mut("same_music").unwrap(), &buttons_array, &buttons_names); + entry_info.set_text("ERROR: You must select at least one checkbox with music searching types."); + } + } e => panic!("Not existent {}", e), } }); diff --git a/czkawka_gui/src/connect_compute_results.rs b/czkawka_gui/src/connect_compute_results.rs index 3dbe6c1..45e78b7 100644 --- a/czkawka_gui/src/connect_compute_results.rs +++ b/czkawka_gui/src/connect_compute_results.rs @@ -5,6 +5,7 @@ use crate::gui_data::GuiData; use crate::help_functions::*; use chrono::NaiveDateTime; use czkawka_core::duplicate::CheckingMethod; +use czkawka_core::same_music::MusicSimilarity; use glib::Receiver; use gtk::prelude::*; @@ -30,6 +31,8 @@ pub fn connect_compute_results(gui_data: &GuiData, receiver: Receiver) let shared_temporary_files_state = gui_data.shared_temporary_files_state.clone(); let shared_similar_images_state = gui_data.shared_similar_images_state.clone(); let shared_zeroed_files_state = gui_data.shared_zeroed_files_state.clone(); + 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(); receiver.attach(None, move |msg| { @@ -586,7 +589,6 @@ pub fn connect_compute_results(gui_data: &GuiData, receiver: Receiver) } } } - Message::ZeroedFiles(zf) => { if zf.get_stopped_search() { entry_info.set_text("Searching for zeroed files was stopped by user"); @@ -659,6 +661,128 @@ pub fn connect_compute_results(gui_data: &GuiData, receiver: Receiver) } } } + Message::SameMusic(mf) => { + if mf.get_stopped_search() { + entry_info.set_text("Searching for empty files was stopped by user"); + + //Also clear list + scrolled_window_same_music_finder + .get_children() + .get(0) + .unwrap() + .clone() + .downcast::() + .unwrap() + .get_model() + .unwrap() + .downcast::() + .unwrap() + .clear(); + } else { + let information = mf.get_information(); + let text_messages = mf.get_text_messages(); + + let same_music_number: usize = information.number_of_duplicates_music_files; + + entry_info.set_text(format!("Found {} duplicated music files.", same_music_number).as_str()); + + // Create GUI + { + let list_store = scrolled_window_same_music_finder + .get_children() + .get(0) + .unwrap() + .clone() + .downcast::() + .unwrap() + .get_model() + .unwrap() + .downcast::() + .unwrap(); + list_store.clear(); + + let col_indices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + + let vector = mf.get_duplicated_music_entries(); + + let music_similarity = *mf.get_music_similarity(); + + let is_title = (MusicSimilarity::TITLE & music_similarity) != MusicSimilarity::NONE; + let is_artist = (MusicSimilarity::ARTIST & music_similarity) != MusicSimilarity::NONE; + let is_album_title = (MusicSimilarity::ALBUM_TITLE & music_similarity) != MusicSimilarity::NONE; + let is_album_artist = (MusicSimilarity::ALBUM_ARTIST & music_similarity) != MusicSimilarity::NONE; + let is_year = (MusicSimilarity::YEAR & music_similarity) != MusicSimilarity::NONE; + + let text: String = "-----".to_string(); + + for vec_file_entry in vector { + let values: [&dyn ToValue; 12] = [ + &"".to_string(), + &"".to_string(), + &"".to_string(), + &(match is_title { + true => text.clone(), + false => "".to_string(), + }), + &(match is_artist { + true => text.clone(), + false => "".to_string(), + }), + &(match is_album_title { + true => text.clone(), + false => "".to_string(), + }), + &(match is_album_artist { + true => text.clone(), + false => "".to_string(), + }), + &(match is_year { + true => text.clone(), + false => "".to_string(), + }), + &"".to_string(), + &"".to_string(), + &(HEADER_ROW_COLOR.to_string()), + &(TEXT_COLOR.to_string()), + ]; + list_store.set(&list_store.append(), &col_indices, &values); + for file_entry in vec_file_entry { + let (directory, file) = split_path(&file_entry.path); + let values: [&dyn ToValue; 12] = [ + &file_entry.size.file_size(options::BINARY).unwrap(), + &file, + &directory, + &file_entry.title, + &file_entry.artist, + &file_entry.album_title, + &file_entry.album_artist, + &file_entry.year.to_string(), + &(NaiveDateTime::from_timestamp(file_entry.modified_date as i64, 0).to_string()), + &(file_entry.modified_date), + &(MAIN_ROW_COLOR.to_string()), + &(TEXT_COLOR.to_string()), + ]; + list_store.set(&list_store.append(), &col_indices, &values); + } + } + print_text_messages_to_text_view(text_messages, &text_view_errors); + } + + // Set state + { + *shared_same_music_state.borrow_mut() = mf; + + if same_music_number > 0 { + *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("save").unwrap() = true; + *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("delete").unwrap() = true; + } else { + *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("save").unwrap() = false; + *shared_buttons.borrow_mut().get_mut("same_music").unwrap().get_mut("delete").unwrap() = false; + } + set_buttons(&mut *shared_buttons.borrow_mut().get_mut("same_music").unwrap(), &buttons_array, &buttons_names); + } + } + } } // Returning false here would close the receiver and have senders fail glib::Continue(true) diff --git a/czkawka_gui/src/connect_notebook_tabs.rs b/czkawka_gui/src/connect_notebook_tabs.rs index 1f87ef6..cde3ce2 100644 --- a/czkawka_gui/src/connect_notebook_tabs.rs +++ b/czkawka_gui/src/connect_notebook_tabs.rs @@ -29,6 +29,7 @@ pub fn connect_notebook_tabs(gui_data: &GuiData) { "notebook_big_main_file_finder" => page = "big_file", "notebook_main_similar_images_finder_label" => page = "similar_images", "notebook_main_zeroed_files_finder" => page = "zeroed_files", + "notebook_main_same_music_finder" => page = "same_music", e => { panic!("Not existent page {}", e); } diff --git a/czkawka_gui/src/create_tree_view.rs b/czkawka_gui/src/create_tree_view.rs index 83fbe82..aa4340c 100644 --- a/czkawka_gui/src/create_tree_view.rs +++ b/czkawka_gui/src/create_tree_view.rs @@ -19,7 +19,7 @@ pub fn create_tree_view_duplicates(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Path"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsDuplicates::Path as i32); column.add_attribute(&renderer, "background", ColumnsDuplicates::Color as i32); column.add_attribute(&renderer, "foreground", ColumnsDuplicates::TextColor as i32); @@ -30,7 +30,7 @@ pub fn create_tree_view_duplicates(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Modification Date"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsDuplicates::Modification as i32); column.add_attribute(&renderer, "background", ColumnsDuplicates::Color as i32); column.add_attribute(&renderer, "foreground", ColumnsDuplicates::TextColor as i32); @@ -54,7 +54,7 @@ pub fn create_tree_view_empty_folders(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Path"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsEmptyFolders::Path as i32); tree_view.append_column(&column); @@ -63,7 +63,7 @@ pub fn create_tree_view_empty_folders(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Modification Date"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsEmptyFolders::Modification as i32); tree_view.append_column(&column); @@ -94,7 +94,7 @@ pub fn create_tree_view_big_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Path"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsBigFiles::Path as i32); tree_view.append_column(&column); @@ -103,7 +103,7 @@ pub fn create_tree_view_big_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Modification Date"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsBigFiles::Modification as i32); tree_view.append_column(&column); @@ -125,7 +125,7 @@ pub fn create_tree_view_temporary_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Path"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsTemporaryFiles::Path as i32); tree_view.append_column(&column); @@ -134,7 +134,7 @@ pub fn create_tree_view_temporary_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Modification Date"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsTemporaryFiles::Modification as i32); tree_view.append_column(&column); @@ -156,7 +156,7 @@ pub fn create_tree_view_empty_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Path"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsEmptyFiles::Path as i32); tree_view.append_column(&column); @@ -165,7 +165,7 @@ pub fn create_tree_view_empty_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Modification Date"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsEmptyFiles::Modification as i32); tree_view.append_column(&column); @@ -222,7 +222,7 @@ pub fn create_tree_view_similar_images(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Path"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsSimilarImages::Path as i32); column.add_attribute(&renderer, "background", ColumnsSimilarImages::Color as i32); column.add_attribute(&renderer, "foreground", ColumnsSimilarImages::TextColor as i32); @@ -233,7 +233,7 @@ pub fn create_tree_view_similar_images(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Modification Date"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsSimilarImages::Modification as i32); column.add_attribute(&renderer, "background", ColumnsSimilarImages::Color as i32); column.add_attribute(&renderer, "foreground", ColumnsSimilarImages::TextColor as i32); @@ -276,7 +276,7 @@ pub fn create_tree_view_zeroed_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Path"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsZeroedFiles::Path as i32); tree_view.append_column(&column); @@ -285,9 +285,112 @@ pub fn create_tree_view_zeroed_files(tree_view: &mut gtk::TreeView) { column.pack_start(&renderer, true); column.set_title("Modification Date"); column.set_resizable(true); - column.set_min_width(100); + column.set_min_width(50); column.add_attribute(&renderer, "text", ColumnsZeroedFiles::Modification as i32); tree_view.append_column(&column); tree_view.set_vexpand(true); } + +pub fn create_tree_view_same_music(tree_view: &mut gtk::TreeView) { + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Size"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::Size as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("File Name"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::Name as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Path"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::Path as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Title"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::Title as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Artist"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::Artist as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Album Title"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::AlbumTitle as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Album Artist"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::AlbumArtist as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Year"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::Year as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + let renderer = gtk::CellRendererText::new(); + let column: gtk::TreeViewColumn = TreeViewColumn::new(); + column.pack_start(&renderer, true); + column.set_title("Modification Date"); + column.set_resizable(true); + column.set_min_width(50); + column.add_attribute(&renderer, "text", ColumnsSameMusic::Modification as i32); + column.add_attribute(&renderer, "background", ColumnsSameMusic::Color as i32); + column.add_attribute(&renderer, "foreground", ColumnsSameMusic::TextColor as i32); + tree_view.append_column(&column); + + tree_view.set_vexpand(true); +} diff --git a/czkawka_gui/src/double_click_opening.rs b/czkawka_gui/src/double_click_opening.rs index 8007b28..822de11 100644 --- a/czkawka_gui/src/double_click_opening.rs +++ b/czkawka_gui/src/double_click_opening.rs @@ -117,3 +117,20 @@ pub fn opening_double_click_function_zeroed_files(tree_view: >k::TreeView, eve } gtk::Inhibit(false) } + +pub fn opening_double_click_function_same_music(tree_view: >k::TreeView, event: &gdk::EventButton) -> gtk::Inhibit { + if event.get_event_type() == gdk::EventType::DoubleButtonPress { + let selection = tree_view.get_selection(); + let (selection_rows, tree_model) = selection.get_selected_rows(); + + for tree_path in selection_rows.iter().rev() { + let name = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSameMusic::Name as i32).get::().unwrap().unwrap(); + let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSameMusic::Path as i32).get::().unwrap().unwrap(); + + if open::that(format!("{}/{}", path, name)).is_err() { + println!("Failed to open {}/{}", path, name); + } + } + } + gtk::Inhibit(false) +} diff --git a/czkawka_gui/src/gui_data.rs b/czkawka_gui/src/gui_data.rs index 745f830..b4814fc 100644 --- a/czkawka_gui/src/gui_data.rs +++ b/czkawka_gui/src/gui_data.rs @@ -1,9 +1,11 @@ +extern crate gdk; extern crate gtk; use crossbeam_channel::unbounded; use czkawka_core::big_file::BigFile; use czkawka_core::duplicate::DuplicateFinder; use czkawka_core::empty_files::EmptyFiles; use czkawka_core::empty_folder::EmptyFolder; +use czkawka_core::same_music::SameMusic; use czkawka_core::similar_files::SimilarImages; use czkawka_core::temporary::Temporary; use czkawka_core::zeroed::ZeroedFiles; @@ -15,12 +17,15 @@ use std::rc::Rc; #[derive(Clone)] pub struct GuiData { + // Glade builder pub glade_src: String, pub builder: Builder, + // Windows pub window_main: gtk::Window, + // States - pub main_notebooks_labels: [String; 7], + pub main_notebooks_labels: [String; 8], pub upper_notebooks_labels: [String; 4], pub buttons_labels: [String; 7], // Buttons state @@ -37,6 +42,7 @@ pub struct GuiData { pub shared_big_files_state: Rc>, pub shared_similar_images_state: Rc>, pub shared_zeroed_files_state: Rc>, + pub shared_same_music_state: Rc>, // State of confirmation dialogs pub shared_confirmation_dialog_delete_dialog_showing_state: Rc>, @@ -47,6 +53,7 @@ pub struct GuiData { pub entry_allowed_extensions: gtk::Entry, pub entry_excluded_items: gtk::Entry, pub entry_big_files_number: gtk::Entry, + pub entry_same_music_minimal_size: gtk::Entry, //// GUI Buttons pub buttons_search: gtk::Button, @@ -78,7 +85,14 @@ pub struct GuiData { //// Check Buttons pub check_button_recursive: gtk::CheckButton, + pub check_button_music_title: gtk::CheckButton, + pub check_button_music_artist: gtk::CheckButton, + pub check_button_music_album_title: gtk::CheckButton, + pub check_button_music_album_artist: gtk::CheckButton, + pub check_button_music_year: gtk::CheckButton, + //// Radio Buttons + // Duplicates pub radio_button_name: gtk::RadioButton, pub radio_button_size: gtk::RadioButton, pub radio_button_hashmb: gtk::RadioButton, @@ -106,6 +120,7 @@ pub struct GuiData { pub scrolled_window_big_files_finder: gtk::ScrolledWindow, pub scrolled_window_similar_images_finder: gtk::ScrolledWindow, pub scrolled_window_zeroed_files_finder: gtk::ScrolledWindow, + pub scrolled_window_same_music_finder: gtk::ScrolledWindow, // Upper notebook pub scrolled_window_included_directories: gtk::ScrolledWindow, @@ -139,6 +154,7 @@ impl GuiData { "big_file".to_string(), "similar_images".to_string(), "zeroed_files".to_string(), + "same_music".to_string(), ]; let upper_notebooks_labels = [ /*"general",*/ "included_directories".to_string(), @@ -189,6 +205,7 @@ impl GuiData { let shared_big_files_state: Rc> = Rc::new(RefCell::new(BigFile::new())); let shared_similar_images_state: Rc> = Rc::new(RefCell::new(SimilarImages::new())); let shared_zeroed_files_state: Rc> = Rc::new(RefCell::new(ZeroedFiles::new())); + let shared_same_music_state: Rc> = Rc::new(RefCell::new(SameMusic::new())); // State of confirmation dialogs let shared_confirmation_dialog_delete_dialog_showing_state: Rc> = Rc::new(RefCell::new(true)); @@ -201,6 +218,7 @@ impl GuiData { let entry_allowed_extensions: gtk::Entry = builder.get_object("entry_allowed_extensions").unwrap(); let entry_excluded_items: gtk::Entry = builder.get_object("entry_excluded_items").unwrap(); let entry_big_files_number: gtk::Entry = builder.get_object("entry_big_files_number").unwrap(); + let entry_same_music_minimal_size: gtk::Entry = builder.get_object("entry_same_music_minimal_size").unwrap(); //// GUI Buttons let buttons_search: gtk::Button = builder.get_object("buttons_search").unwrap(); @@ -241,6 +259,11 @@ impl GuiData { //// Check Buttons let check_button_recursive: gtk::CheckButton = builder.get_object("check_button_recursive").unwrap(); + let check_button_music_title: gtk::CheckButton = builder.get_object("check_button_music_title").unwrap(); + let check_button_music_artist: gtk::CheckButton = builder.get_object("check_button_music_artist").unwrap(); + let check_button_music_album_title: gtk::CheckButton = builder.get_object("check_button_music_album_title").unwrap(); + let check_button_music_album_artist: gtk::CheckButton = builder.get_object("check_button_music_album_artist").unwrap(); + let check_button_music_year: gtk::CheckButton = builder.get_object("check_button_music_year").unwrap(); //// Radio Buttons let radio_button_name: gtk::RadioButton = builder.get_object("radio_button_name").unwrap(); @@ -277,6 +300,7 @@ impl GuiData { let scrolled_window_big_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_big_files_finder").unwrap(); let scrolled_window_similar_images_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_similar_images_finder").unwrap(); let scrolled_window_zeroed_files_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_zeroed_files_finder").unwrap(); + let scrolled_window_same_music_finder: gtk::ScrolledWindow = builder.get_object("scrolled_window_same_music_finder").unwrap(); // Upper notebook let scrolled_window_included_directories: gtk::ScrolledWindow = builder.get_object("scrolled_window_included_directories").unwrap(); @@ -304,12 +328,14 @@ impl GuiData { shared_big_files_state, shared_similar_images_state, shared_zeroed_files_state, + shared_same_music_state, shared_confirmation_dialog_delete_dialog_showing_state, entry_similar_images_minimal_size, entry_duplicate_minimal_size, entry_allowed_extensions, entry_excluded_items, entry_big_files_number, + entry_same_music_minimal_size, buttons_search, buttons_stop, buttons_resume, @@ -332,6 +358,11 @@ impl GuiData { buttons_popover_select_one_newest, popover_select, check_button_recursive, + check_button_music_title, + check_button_music_artist, + check_button_music_album_title, + check_button_music_album_artist, + check_button_music_year, radio_button_name, radio_button_size, radio_button_hashmb, @@ -349,6 +380,7 @@ impl GuiData { scrolled_window_big_files_finder, scrolled_window_similar_images_finder, scrolled_window_zeroed_files_finder, + scrolled_window_same_music_finder, scrolled_window_included_directories, scrolled_window_excluded_directories, sx, diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index 8c02d53..e2afeb9 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -3,6 +3,7 @@ use czkawka_core::common_messages::Messages; use czkawka_core::duplicate::DuplicateFinder; use czkawka_core::empty_files::EmptyFiles; use czkawka_core::empty_folder::EmptyFolder; +use czkawka_core::same_music::SameMusic; use czkawka_core::similar_files::{SimilarImages, Similarity}; use czkawka_core::temporary::Temporary; use czkawka_core::zeroed::ZeroedFiles; @@ -18,6 +19,7 @@ pub enum Message { Temporary(Temporary), SimilarImages(SimilarImages), ZeroedFiles(ZeroedFiles), + SameMusic(SameMusic), } pub enum ColumnsDuplicates { @@ -72,6 +74,20 @@ pub enum ColumnsZeroedFiles { Path, Modification, } +pub enum ColumnsSameMusic { + Size = 0, + Name, + Path, + Title, + Artist, + AlbumTitle, + AlbumArtist, + Year, + Modification, + _ModificationAsSecs, + Color, + TextColor, +} pub const TEXT_COLOR: &str = "#ffffff"; pub const MAIN_ROW_COLOR: &str = "#343434"; @@ -157,6 +173,15 @@ pub fn select_function_duplicates(_tree_selection: >k::TreeSelection, tree_mod true } +pub fn select_function_same_music(_tree_selection: >k::TreeSelection, tree_model: >k::TreeModel, tree_path: >k::TreePath, _is_path_currently_selected: bool) -> bool { + let color = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSameMusic::Color as i32).get::().unwrap().unwrap(); + + if color == HEADER_ROW_COLOR { + return false; + } + + true +} pub fn set_buttons(hashmap: &mut HashMap, buttons_array: &[gtk::Button], button_names: &[String]) { for (index, button) in buttons_array.iter().enumerate() { diff --git a/czkawka_gui/src/startup_configuration.rs b/czkawka_gui/src/startup_configuration.rs index 7368053..b28a0d2 100644 --- a/czkawka_gui/src/startup_configuration.rs +++ b/czkawka_gui/src/startup_configuration.rs @@ -23,6 +23,7 @@ pub fn startup_configuration(gui_data: &GuiData) { let scrolled_window_main_temporary_files_finder = gui_data.scrolled_window_main_temporary_files_finder.clone(); let scrolled_window_big_files_finder = gui_data.scrolled_window_big_files_finder.clone(); let scrolled_window_similar_images_finder = gui_data.scrolled_window_similar_images_finder.clone(); + let scrolled_window_same_music_finder = gui_data.scrolled_window_same_music_finder.clone(); let scrolled_window_zeroed_files_finder = gui_data.scrolled_window_zeroed_files_finder.clone(); let scrolled_window_included_directories = gui_data.scrolled_window_included_directories.clone(); let scrolled_window_excluded_directories = gui_data.scrolled_window_excluded_directories.clone(); @@ -171,6 +172,36 @@ pub fn startup_configuration(gui_data: &GuiData) { scrolled_window_zeroed_files_finder.add(&tree_view); scrolled_window_zeroed_files_finder.show_all(); } + // Same Files + { + let col_types: [glib::types::Type; 12] = [ + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + glib::types::Type::String, + ]; + let list_store: gtk::ListStore = gtk::ListStore::new(&col_types); + + let mut tree_view: gtk::TreeView = TreeView::with_model(&list_store); + + tree_view.get_selection().set_mode(SelectionMode::Multiple); + + create_tree_view_same_music(&mut tree_view); + tree_view.get_selection().set_select_function(Some(Box::new(select_function_same_music))); + + tree_view.connect_button_press_event(opening_double_click_function_same_music); + + scrolled_window_same_music_finder.add(&tree_view); + scrolled_window_same_music_finder.show_all(); + } } // Set Included Directory