1
0
Fork 0
mirror of synced 2024-04-29 01:52:39 +12:00

Implement finding duplicated music by tags/artist/year etc. (#95)

This commit is contained in:
Rafał Mikrut 2020-11-02 15:56:07 -05:00 committed by GitHub
parent 8ba780ded6
commit ecebb3a1af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1572 additions and 137 deletions

407
Cargo.lock generated
View file

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

View file

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

View file

@ -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<u64, String> {
}
}
fn parse_music_duplicate_type(src: &str) -> Result<MusicSimilarity, String> {
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"#;

View file

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

View file

@ -20,4 +20,8 @@ crossbeam-channel = "0.4.4"
img_hash = "3.1"
bk-tree = "0.3"
image = "0.23"
hamming = "0.1"
hamming = "0.1"
# Needed by same music
bitflags = "1.2.1"
audiotags = "0.2.7182"

View file

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

View file

@ -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<FileEntry>,
duplicated_music_entries: Vec<Vec<FileEntry>>,
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<Vec<FileEntry>> {
&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<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
// 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(&current_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(&current_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(&current_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<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 {
if rx.is_some() && rx.unwrap().try_recv().is_ok() {
return false;
}
let mut hash_map: HashMap<String, Vec<FileEntry>> = 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<String, Vec<FileEntry>> = 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<String, Vec<FileEntry>> = 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<String, Vec<FileEntry>> = 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<i32, Vec<FileEntry>> = 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());
}
}

View file

@ -1097,6 +1097,165 @@ Author: Rafał Mikrut
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="notebook_main_same_music_finder">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Minimal file size(in bytes)</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_same_music_minimal_size">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">15</property>
<property name="text" translatable="yes">1024</property>
<property name="caps_lock_warning">False</property>
<property name="input_purpose">number</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkCheckButton" id="check_button_music_title">
<property name="label" translatable="yes">Title</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_music_artist">
<property name="label" translatable="yes">Artist</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_music_album_title">
<property name="label" translatable="yes">Album Title</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_music_album_artist">
<property name="label" translatable="yes">Album Artist</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_button_music_year">
<property name="label" translatable="yes">Year</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolled_window_same_music_finder">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="position">6</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Music Duplicates</property>
</object>
<packing>
<property name="position">6</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="notebook_main_zeroed_files_finder">
<property name="visible">True</property>
@ -1158,7 +1317,7 @@ Author: Rafał Mikrut
</child>
</object>
<packing>
<property name="position">6</property>
<property name="position">7</property>
</packing>
</child>
<child type="tab">
@ -1168,7 +1327,7 @@ Author: Rafał Mikrut
<property name="label" translatable="yes">Zeroed Files</property>
</object>
<packing>
<property name="position">6</property>
<property name="position">7</property>
<property name="tab_fill">False</property>
</packing>
</child>

View file

@ -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::<gtk::TreeView>().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::<gtk::ListStore>().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::<String>().unwrap().unwrap();
let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSameMusic::Path as i32).get::<String>().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

View file

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

View file

@ -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<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
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<Message>) {
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::<u64>() {
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),
}
});

View file

@ -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<Message>)
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>)
}
}
}
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>)
}
}
}
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::<gtk::TreeView>()
.unwrap()
.get_model()
.unwrap()
.downcast::<gtk::ListStore>()
.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::<gtk::TreeView>()
.unwrap()
.get_model()
.unwrap()
.downcast::<gtk::ListStore>()
.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)

View file

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

View file

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

View file

@ -117,3 +117,20 @@ pub fn opening_double_click_function_zeroed_files(tree_view: &gtk::TreeView, eve
}
gtk::Inhibit(false)
}
pub fn opening_double_click_function_same_music(tree_view: &gtk::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::<String>().unwrap().unwrap();
let path = tree_model.get_value(&tree_model.get_iter(tree_path).unwrap(), ColumnsSameMusic::Path as i32).get::<String>().unwrap().unwrap();
if open::that(format!("{}/{}", path, name)).is_err() {
println!("Failed to open {}/{}", path, name);
}
}
}
gtk::Inhibit(false)
}

View file

@ -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<RefCell<BigFile>>,
pub shared_similar_images_state: Rc<RefCell<SimilarImages>>,
pub shared_zeroed_files_state: Rc<RefCell<ZeroedFiles>>,
pub shared_same_music_state: Rc<RefCell<SameMusic>>,
// State of confirmation dialogs
pub shared_confirmation_dialog_delete_dialog_showing_state: Rc<RefCell<bool>>,
@ -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<RefCell<_>> = Rc::new(RefCell::new(BigFile::new()));
let shared_similar_images_state: Rc<RefCell<_>> = Rc::new(RefCell::new(SimilarImages::new()));
let shared_zeroed_files_state: Rc<RefCell<_>> = Rc::new(RefCell::new(ZeroedFiles::new()));
let shared_same_music_state: Rc<RefCell<_>> = Rc::new(RefCell::new(SameMusic::new()));
// State of confirmation dialogs
let shared_confirmation_dialog_delete_dialog_showing_state: Rc<RefCell<_>> = 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,

View file

@ -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: &gtk::TreeSelection, tree_mod
true
}
pub fn select_function_same_music(_tree_selection: &gtk::TreeSelection, tree_model: &gtk::TreeModel, tree_path: &gtk::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::<String>().unwrap().unwrap();
if color == HEADER_ROW_COLOR {
return false;
}
true
}
pub fn set_buttons(hashmap: &mut HashMap<String, bool>, buttons_array: &[gtk::Button], button_names: &[String]) {
for (index, button) in buttons_array.iter().enumerate() {

View file

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