diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a3c45a4a8..bc80ef669 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -296,21 +296,18 @@ jobs: ci_tester/target/release/ci_tester target/debug/czkawka_cli - # TODO - currently not working - # No need to check it on every commit, because android build is not officially supported - # But it is good to have it checked in master branch during merge - # android: - # if: ${{ github.ref == 'refs/heads/master' }} - # runs-on: ubuntu-22.04 - # steps: - # - uses: actions/checkout@v4 - # - # - name: Setup rust version and target - # run: | - # rustup default 1.92.0 - # rustup target add aarch64-linux-android - # - # - name: Check for Android - # run: | - # cd czkawka_core; - # cargo check --target aarch64-linux-android; + android: + if: ${{ github.ref == 'refs/heads/master' }} + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Setup rust version and target + run: | + rustup default 1.92.0 + rustup target add aarch64-linux-android + + - name: Check for Android + run: | + cd czkawka_core + cargo check --target aarch64-linux-android --features "blake_pure" diff --git a/Cargo.lock b/Cargo.lock index 91c3ff541..9bf8ab628 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,9 +257,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" dependencies = [ "rustversion", ] @@ -311,6 +311,23 @@ dependencies = [ "libloading", ] +[[package]] +name = "ashpd" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618a409b91d5265798a99e3d1d0b226911605e581c4e7255e83c1e397b172bce" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.2", + "serde", + "serde_repr", + "tokio", + "url", + "zbus", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -777,9 +794,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -809,9 +826,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bzip2" @@ -913,9 +930,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.54" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -1043,9 +1060,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -1053,9 +1070,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -1065,9 +1082,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1351,9 +1368,9 @@ dependencies = [ [[package]] name = "criterion" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ "alloca", "anes", @@ -1374,9 +1391,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", "itertools 0.13.0", @@ -1449,9 +1466,9 @@ dependencies = [ [[package]] name = "ctor-lite" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" +checksum = "b29fccfdaeb0f9bd90da5759b1d0eaa2f6cfcfe90960124cfc83141ed4e111fd" [[package]] name = "ctrlc" @@ -1487,7 +1504,7 @@ dependencies = [ name = "czkawka_core" version = "10.0.0" dependencies = [ - "anyhow", + "ashpd", "bincode 1.3.3", "bitflags 2.10.0", "bk-tree", @@ -1539,6 +1556,7 @@ dependencies = [ "static_assertions", "symphonia", "tempfile", + "tokio", "trash", "vid_dup_finder_lib", "xxhash-rust", @@ -2219,15 +2237,15 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -3434,9 +3452,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3588,7 +3606,7 @@ dependencies = [ "rgb", "tiff", "zune-core 0.5.1", - "zune-jpeg 0.5.11", + "zune-jpeg 0.5.12", ] [[package]] @@ -4612,6 +4630,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "moxcms" version = "0.7.11" @@ -4776,9 +4805,9 @@ dependencies = [ [[package]] name = "nom-exif" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bb43c48207f47dd4e11b9eaffe8cf26a275445d58c7c254453fee467d8e3b1" +checksum = "0e78a8215f056e78a6887b4872e61bab5690dec4648b37396b6721be6f89c4ea" dependencies = [ "bytes", "chrono", @@ -4841,9 +4870,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" @@ -5692,18 +5721,18 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" dependencies = [ "critical-section", ] [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -6136,9 +6165,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -6148,9 +6177,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -6159,9 +6188,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "resvg" @@ -6649,9 +6678,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "skia-bindings" @@ -6693,9 +6722,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slabbin" @@ -6843,6 +6872,16 @@ dependencies = [ "serde_core", ] +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "softbuffer" version = "0.4.8" @@ -7364,9 +7403,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.46" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", @@ -7382,15 +7421,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.26" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -7471,6 +7510,22 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "toml" version = "0.5.11" @@ -7675,9 +7730,9 @@ dependencies = [ [[package]] name = "typed-path" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43ffa54726cdc9ea78392023ffe9fe9cf9ac779e1c6fcb0d23f9862e3879d20" +checksum = "3015e6ce46d5ad8751e4a772543a30c7511468070e98e64e20165f8f81155b64" [[package]] name = "typenum" @@ -7687,9 +7742,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "tz-rs" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2df019c305771deac375d9657fc75fa394bde69102a870c923e779c3746642" +checksum = "4fc6c929ffa10fb34f4a3c7e9a73620a83ef2e85e47f9ec3381b8289e6762f42" [[package]] name = "udev" @@ -7836,6 +7891,7 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -9137,6 +9193,7 @@ dependencies = [ "rustix 1.1.3", "serde", "serde_repr", + "tokio", "tracing", "uds_windows", "uuid", @@ -9181,18 +9238,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" dependencies = [ "proc-macro2", "quote", @@ -9276,16 +9333,15 @@ dependencies = [ [[package]] name = "zip" -version = "7.2.0" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +checksum = "268bf6f9ceb991e07155234071501490bb41fd1e39c6a588106dad10ae2a5804" dependencies = [ "aes", "bzip2", "constant_time_eq 0.3.1", "crc32fast", "flate2", - "generic-array", "getrandom 0.3.4", "hmac", "indexmap", @@ -9300,15 +9356,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" +checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zopfli" @@ -9354,9 +9410,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2959ca473aae96a14ecedf501d20b3608d2825ba280d5adb57d651721885b0c2" +checksum = "410e9ecef634c709e3831c2cfdb8d9c32164fae1c67496d5b68fff728eec37fe" dependencies = [ "zune-core 0.5.1", ] @@ -9370,6 +9426,7 @@ dependencies = [ "endi", "enumflags2", "serde", + "url", "winnow", "zvariant_derive", "zvariant_utils", diff --git a/czkawka_cli/Cargo.toml b/czkawka_cli/Cargo.toml index ef3c3e1cd..017f7f8e8 100644 --- a/czkawka_cli/Cargo.toml +++ b/czkawka_cli/Cargo.toml @@ -24,6 +24,7 @@ default = [] heif = ["czkawka_core/heif"] libraw = ["czkawka_core/libraw"] libavif = ["czkawka_core/libavif"] +xdg_portal_trash = ["czkawka_core/xdg_portal_trash"] no_colors = [] diff --git a/czkawka_cli/src/commands.rs b/czkawka_cli/src/commands.rs index f565ce8d2..644e06c85 100644 --- a/czkawka_cli/src/commands.rs +++ b/czkawka_cli/src/commands.rs @@ -11,6 +11,7 @@ use czkawka_core::re_exported::{Cropdetect, FilterType, HashAlg}; use czkawka_core::tools::broken_files::CheckedTypes; use czkawka_core::tools::same_music::MusicSimilarity; use czkawka_core::tools::similar_videos::{ALLOWED_SKIP_FORWARD_AMOUNT, ALLOWED_VID_HASH_DURATION, DEFAULT_SKIP_FORWARD_AMOUNT, crop_detect_from_str_opt}; +use czkawka_core::tools::video_optimizer::VideoCodec; #[cfg(not(feature = "no_colors"))] pub const CLAP_STYLING: Styles = Styles::styled() @@ -616,7 +617,7 @@ pub struct TranscodeArgs { help = "Target codec (h264, h265, av1, vp9)", long_help = "Target video codec for transcoding (h264, h265, av1, vp9). Only used with -F flag." )] - pub target_codec: String, + pub target_codec: VideoCodec, #[clap( long, default_value = "23", @@ -719,7 +720,7 @@ pub struct CropArgs { help = "Target codec (h264, h265, av1, vp9)", long_help = "Optional: Also transcode to different codec while cropping. Only used with -F flag." )] - pub target_codec: Option, + pub target_codec: Option, #[clap( long, value_parser = clap::value_parser!(u32).range(0..=51), @@ -1088,9 +1089,12 @@ fn parse_checking_method_same_music(src: &str) -> Result Result { +fn parse_video_codec(src: &str) -> Result { match src.to_ascii_lowercase().as_str() { - "h264" | "h265" | "av1" | "vp9" => Ok(src.to_ascii_lowercase()), + "h264" => Ok(VideoCodec::H264), + "h265" => Ok(VideoCodec::H265), + "av1" => Ok(VideoCodec::Av1), + "vp9" => Ok(VideoCodec::Vp9), _ => Err("Couldn't parse the video codec (allowed: h264, h265, av1, vp9)"), } } diff --git a/czkawka_cli/src/main.rs b/czkawka_cli/src/main.rs index a0ab0cb28..34ed2f8d2 100644 --- a/czkawka_cli/src/main.rs +++ b/czkawka_cli/src/main.rs @@ -26,7 +26,9 @@ use czkawka_core::tools::same_music::{SameMusic, SameMusicParameters}; use czkawka_core::tools::similar_images::{SimilarImages, SimilarImagesParameters}; use czkawka_core::tools::similar_videos::{SimilarVideos, SimilarVideosParameters}; use czkawka_core::tools::temporary::Temporary; -use czkawka_core::tools::video_optimizer::{VideoCropParams, VideoCroppingMechanism, VideoOptimizer, VideoOptimizerFixParams, VideoOptimizerParameters, VideoTranscodeParams}; +use czkawka_core::tools::video_optimizer::{ + VideoCropFixParams, VideoCropParams, VideoCroppingMechanism, VideoOptimizer, VideoOptimizerFixParams, VideoOptimizerParameters, VideoTranscodeFixParams, VideoTranscodeParams, +}; use log::{debug, error, info}; use crate::commands::{ @@ -129,7 +131,6 @@ fn duplicates(duplicates: DuplicatesArgs, stop_flag: &Arc, progress_ let params = DuplicateFinderParameters::new( search_method, hash_type, - !allow_hard_links.allow_hard_links, use_prehash_cache, minimal_cached_file_size, minimal_prehash_cache_file_size, @@ -140,6 +141,7 @@ fn duplicates(duplicates: DuplicatesArgs, stop_flag: &Arc, progress_ set_common_settings(&mut tool, &common_cli_items, Some(reference_directories.reference_directories.as_ref())); tool.set_minimal_file_size(minimal_file_size); tool.set_maximal_file_size(maximal_file_size); + tool.set_hide_hard_links(!allow_hard_links.allow_hard_links); set_advanced_delete(&mut tool, delete_method); tool.search(stop_flag, Some(progress_sender)); @@ -221,19 +223,13 @@ fn similar_images(similar_images: SimilarImagesArgs, stop_flag: &Arc ignore_same_size, } = similar_images; - let params = SimilarImagesParameters::new( - max_difference, - hash_size, - hash_alg, - image_filter, - ignore_same_size.ignore_same_size, - !allow_hard_links.allow_hard_links, - ); + let params = SimilarImagesParameters::new(max_difference, hash_size, hash_alg, image_filter, ignore_same_size.ignore_same_size); let mut tool = SimilarImages::new(params); set_common_settings(&mut tool, &common_cli_items, Some(reference_directories.reference_directories.as_ref())); tool.set_minimal_file_size(minimal_file_size); tool.set_maximal_file_size(maximal_file_size); + tool.set_hide_hard_links(!allow_hard_links.allow_hard_links); set_advanced_delete(&mut tool, delete_method); tool.search(stop_flag, Some(progress_sender)); @@ -329,7 +325,6 @@ fn similar_videos(similar_videos: SimilarVideosArgs, stop_flag: &Arc let params = SimilarVideosParameters::new( tolerance, ignore_same_size.ignore_same_size, - !allow_hard_links.allow_hard_links, skip_forward_amount, scan_duration, crop_detect, @@ -342,6 +337,7 @@ fn similar_videos(similar_videos: SimilarVideosArgs, stop_flag: &Arc set_common_settings(&mut tool, &common_cli_items, Some(reference_directories.reference_directories.as_ref())); tool.set_minimal_file_size(minimal_file_size); tool.set_maximal_file_size(maximal_file_size); + tool.set_hide_hard_links(!allow_hard_links.allow_hard_links); set_advanced_delete(&mut tool, delete_method); tool.search(stop_flag, Some(progress_sender)); @@ -413,9 +409,6 @@ fn bad_names(bad_names: BadNamesArgs, stop_flag: &Arc, progress_send } fn video_optimizer(video_optimizer: VideoOptimizerArgs, stop_flag: &Arc, progress_sender: &Sender) -> CliOutput { - use czkawka_core::common::traits::FixingItems; - use czkawka_core::tools::video_optimizer::{VideoCodec, VideoCropFixParams, VideoTranscodeFixParams}; - use crate::commands::{CropArgs, TranscodeArgs, VideoOptimizerMode as CliVideoOptimizerMode}; let VideoOptimizerArgs { common_cli_items, mode } = video_optimizer; @@ -449,9 +442,8 @@ fn video_optimizer(video_optimizer: VideoOptimizerArgs, stop_flag: &Arc().unwrap_or(VideoCodec::H265); let fix_params = VideoOptimizerFixParams::VideoTranscode(VideoTranscodeFixParams { - codec, + codec: target_codec, quality, fail_if_not_smaller, overwrite_original, @@ -503,10 +495,9 @@ fn video_optimizer(video_optimizer: VideoOptimizerArgs, stop_flag: &Arc().ok()); let fix_params = VideoOptimizerFixParams::VideoCrop(VideoCropFixParams { overwrite_original, - target_codec: target_codec_parsed, + target_codec, quality, crop_mechanism: crop_mech, }); @@ -574,7 +565,7 @@ fn save_and_write_results_to_writer(component: &T, } let mut cli_output = CliOutput { - found_any_files: component.found_any_broken_files(), + found_any_files: component.found_any_items(), ignored_error_code_on_found: common_cli_items.ignore_error_code_on_found, output: String::new(), }; diff --git a/czkawka_core/Cargo.toml b/czkawka_core/Cargo.toml index 3a8db1c21..aba0a7d2f 100644 --- a/czkawka_core/Cargo.toml +++ b/czkawka_core/Cargo.toml @@ -76,7 +76,6 @@ infer = "0.19" # Heif/Heic libheif-rs = { version = "=0.18.0", optional = true } # Do not upgrade now, since Ubuntu 22.04 not works with newer version libheif-sys = { version = "=1.14.2", optional = true } # 1.14.3 brake compilation on Ubuntu 22.04, so pin it to this version -anyhow = { version = "1.0.89" } nom-exif = "2.1.0" @@ -101,6 +100,9 @@ glibc_musl_version = "0.1.0" rand = "0.9.2" +ashpd = { version = "0.12.1", optional = true } +tokio = { version = "1.49.0", optional = true } + [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] trash = "5.1" @@ -123,6 +125,9 @@ default = [] heif = ["dep:libheif-rs", "dep:libheif-sys"] libraw = ["dep:libraw-rs"] libavif = ["image/avif-native", "image/avif"] - +blake_pure = ["blake3/pure"] +# Allows to use trash on Linux when using xdg-portal, needed by e.g. flatpak where normal trash access always fails +# No-op on other OSes +xdg_portal_trash = ["ashpd", "tokio"] [lints] workspace = true diff --git a/czkawka_core/i18n/en/czkawka_core.ftl b/czkawka_core/i18n/en/czkawka_core.ftl index 3388e5b46..ce74be8f2 100644 --- a/czkawka_core/i18n/en/czkawka_core.ftl +++ b/czkawka_core/i18n/en/czkawka_core.ftl @@ -40,4 +40,65 @@ core_messages_limit_reached_lines = Number of messages exceeded the set limit ({ core_error_moving_to_trash = Error while moving "{ $file }" to the trash: { $error } core_error_removing = Error while removing "{ $file }": { $error } -core_no_similarity_method_selected = Cannot find similar music files without a selected similarity method \ No newline at end of file +core_no_similarity_method_selected = Cannot find similar music files without a selected similarity method + +core_failed_to_spawn_command = Failed to spawn command: { $reason } +core_failed_to_check_process_status = Failed to check process status: { $reason } +core_failed_to_wait_for_process = Failed to wait for process: { $reason } +core_failed_to_read_video_properties = Failed to read video properties: { $reason } +core_failed_to_execute_ffmpeg = Failed to execute ffmpeg: { $reason } +core_ffmpeg_failed_with_status = ffmpeg failed with status { $status }: { $stderr } (command: { $command }) +core_failed_to_load_image_frame = Failed to load image frame: { $reason } +core_failed_to_extract_frame = Failed to extract frame at { $time } seconds from "{ $file }": { $reason } +core_failed_to_save_thumbnail = Failed to save thumbnail for "{ $file }": { $reason } +core_failed_get_frame_at_timestamp = Failed to get frame at timestamp { $timestamp } from "{ $file }": { $reason } +core_failed_get_frame_from_file = Failed to get frame from "{ $file }" at timestamp { $timestamp }: { $reason } +core_invalid_crop_rectangle = Invalid crop rectangle: left={ $left }, top={ $top }, right={ $right }, bottom={ $bottom } +core_failed_to_crop_video_file = Failed to crop video file "{ $file }": { $reason } +core_cropped_video_not_created = Cropped video file was not created: { $temp } +core_unable_check_hash_of_file = Unable to check hash of file "{ $file }", reason { $reason } +core_error_checking_hash_of_file = Error happened when checking hash of file "{ $file }", reason { $reason } +core_image_zero_dimensions = Image has zero width or height "{ $path }" +core_image_open_failed = Cannot open image file "{ $path }": { $reason } +core_not_directory_remove = Trying to remove folder "{ $path }" which is not a directory +core_cannot_read_directory = Cannot read directory "{ $path }" +core_cannot_read_entry_from_directory = Cannot read entry from directory "{ $path }" +core_folder_contains_file_inside = Folder contains file "{ $entry }" inside "{ $folder }" +core_unknown_directory_entry = Unable to determine file type of directory entry "{ $entry }" inside "{ $path }" +core_video_width_exceeds_limit = Video width { $width } exceeds the limit of { $limit } +core_video_height_exceeds_limit = Video height { $height } exceeds the limit of { $limit } +core_failed_to_process_video = Failed to process video file { $file }: { $reason } +core_optimized_file_larger = Optimized file { $optimized } (size: { $new_size }) is not smaller than original { $original } (size: { $original_size }) +core_unknown_codec = Unknown codec: { $codec } +core_invalid_video_optimizer_mode = Invalid video optimizer mode: '{ $mode }'. Allowed values: transcode, crop +core_folder_does_not_exist = Folder does not exist: { $folder } +core_path_not_directory = Path is not a directory: { $folder } +core_test_error_for_folder = Test error for folder: { $folder } +core_unknown_exif_tag_group = Unknown EXIF tag group: { $tag } +core_error_comparing_fingerprints = Error while comparing fingerprints: { $reason } +core_failed_to_generate_thumbnail_frames_different_dimensions = Failed to generate thumbnail for "{ $file }": extracted frames have different dimensions +core_failed_to_generate_thumbnail = Failed to generate thumbnail for "{ $file }": { $reason } +core_failed_to_extract_frame_at_seek_time = Failed to extract frame at { $time } seconds from "{ $file }": { $reason } +core_video_file_does_not_exist = Video file does not exist (could be removed between scan/later steps): "{ $path }" +core_image_too_large = Image is too large ({ $width }x{ $height }) - more than supported { $max } pixels +core_failed_to_get_video_metadata = Failed to get video metadata for file "{ $file }": { $reason } +core_failed_to_get_video_codec = Failed to get video codec for file "{ $file }" +core_failed_to_get_video_duration = Failed to get video duration for file "{ $file }" +core_failed_to_get_video_dimensions = Failed to get video dimensions for file "{ $file }" +core_frame_dimensions_mismatch = Frame dimensions for timestamp { $timestamp } do not match the first frame dimensions ({ $first_w }x{ $first_h }) +core_failed_to_load_data_from_cache = Failed to load data from cache file { $file }, reason { $reason } +core_failed_to_load_data_from_json_cache = Failed to load data from json cache file { $file }, reason { $reason } +core_failed_to_replace_with_optimized = Failed to replace file "{ $file }" with optimized version: { $reason } +core_failed_to_write_data_to_cache = Cannot write data to cache file "{ $file }", reason { $reason } +core_properly_saved_cache_entries = Properly saved to file { $count } cache entries. +core_video_processing_stopped_by_user = Video processing was stopped by user +core_thumbnail_generation_stopped_by_user = Thumbnail generation was stopped by user +core_failed_to_optimize_video = Failed to optimize video "{ $file }": { $reason } +core_failed_to_crop_video = Failed to crop video "{ $file }": { $reason } +core_failed_to_get_metadata_of_optimized_file = Failed to get metadata of optimized file "{ $file }": { $reason } +core_cannot_create_config_folder = Cannot create config folder "{ $folder }", reason { $reason } +core_cannot_create_cache_folder = Cannot create cache folder "{ $folder }", reason { $reason } +core_cannot_create_or_open_cache_file = Cannot create or open cache file "{ $file }", reason { $reason } +core_cannot_set_config_cache_path = Cannot set config/cache path - config and cache will not be used. +core_invalid_extension_contains_space = { $extension } is not a valid extension because it contains empty space inside +core_invalid_extension_contains_dot = { $extension } is not a valid extension because it contains dot inside diff --git a/czkawka_core/src/common/basic_gui_cli.rs b/czkawka_core/src/common/basic_gui_cli.rs index 3c350a351..162535f4d 100644 --- a/czkawka_core/src/common/basic_gui_cli.rs +++ b/czkawka_core/src/common/basic_gui_cli.rs @@ -2,8 +2,8 @@ use std::process; use log::{error, warn}; -use crate::CZKAWKA_VERSION; use crate::common::config_cache_path::get_config_cache_path; +use crate::{CZKAWKA_VERSION, flc}; #[derive(Clone, Debug)] pub struct CliResult { @@ -146,10 +146,10 @@ fn deduplicate_folders(folder_list: &mut Vec) { fn check_if_folder_is_valid(folder: &str) -> Result { let path = std::path::Path::new(folder); if !path.exists() { - return Err(format!("Folder does not exist: {folder}")); + return Err(flc!("core_folder_does_not_exist", folder = folder)); } if !path.is_dir() { - return Err(format!("Path is not a directory: {folder}")); + return Err(flc!("core_path_not_directory", folder = folder)); } let canonical_path = dunce::canonicalize(path).map_err(|e| format!("Failed to canonicalize path: {folder}. Error: {e}"))?; @@ -159,7 +159,7 @@ fn check_if_folder_is_valid(folder: &str) -> Result { #[cfg(test)] fn check_if_folder_is_valid(folder: &str) -> Result { if folder.contains("test_error") { - return Err(format!("Test error for folder: {folder}")); + return Err(flc!("core_test_error_for_folder", folder = folder)); } Ok(folder.to_string()) } diff --git a/czkawka_core/src/common/cache.rs b/czkawka_core/src/common/cache.rs index 798b6785b..7ec81914e 100644 --- a/czkawka_core/src/common/cache.rs +++ b/czkawka_core/src/common/cache.rs @@ -21,6 +21,7 @@ use crate::common::cache::cleaning::{should_clean_cache, update_cleaning_timesta use crate::common::config_cache_path::open_cache_folder; use crate::common::tool_data::CommonData; use crate::common::traits::ResultEntry; +use crate::flc; use crate::helpers::messages::Messages; pub(crate) const CACHE_VERSION: u8 = 100; @@ -58,7 +59,7 @@ where if let Err(e) = options.serialize_into(writer, &hashmap_to_save) { text_messages .warnings - .push(format!("Cannot write data to cache file \"{}\", reason {e}", cache_file.to_string_lossy())); + .push(flc!("core_failed_to_write_data_to_cache", file = cache_file.to_string_lossy(), reason = e.to_string())); debug!("Failed to save cache to file \"{}\" - {e}", cache_file.to_string_lossy()); return text_messages; } @@ -69,7 +70,7 @@ where if let Err(e) = serde_json::to_writer(writer, &hashmap_to_save) { text_messages .warnings - .push(format!("Cannot write data to cache file \"{}\", reason {e}", cache_file_json.to_string_lossy())); + .push(flc!("core_failed_to_write_data_to_cache", file = cache_file_json.to_string_lossy(), reason = e.to_string())); debug!("Failed to save cache to file \"{}\" - {e}", cache_file_json.to_string_lossy()); return text_messages; } @@ -80,7 +81,7 @@ where ); } - text_messages.messages.push(format!("Properly saved to file {} cache entries.", hashmap.len())); + text_messages.messages.push(flc!("core_properly_saved_cache_entries", count = hashmap.len())); debug!("Properly saved to file {} cache entries.", hashmap.len()); } else { debug!("Failed to save cache to file {cache_file_name} because not exists"); @@ -215,7 +216,7 @@ where Err(e) => { text_messages .warnings - .push(format!("Failed to load data from cache file {}, reason {e}", cache_file.to_string_lossy())); + .push(flc!("core_failed_to_load_data_from_cache", file = cache_file.to_string_lossy(), reason = e.to_string())); error!("Failed to load cache from file {} - {e}", cache_file.to_string_lossy()); return (text_messages, None); } @@ -226,9 +227,11 @@ where vec_loaded_entries = match serde_json::from_reader(reader) { Ok(t) => t, Err(e) => { - text_messages - .warnings - .push(format!("Failed to load data from json cache file {}, reason {e}", cache_file_json.to_string_lossy())); + text_messages.warnings.push(flc!( + "core_failed_to_load_data_from_json_cache", + file = cache_file_json.to_string_lossy(), + reason = e.to_string() + )); debug!("Failed to load cache from file {} - {e}", cache_file_json.to_string_lossy()); return (text_messages, None); } diff --git a/czkawka_core/src/common/config_cache_path.rs b/czkawka_core/src/common/config_cache_path.rs index 6ff7808a3..a8d6a0854 100644 --- a/czkawka_core/src/common/config_cache_path.rs +++ b/czkawka_core/src/common/config_cache_path.rs @@ -6,6 +6,8 @@ use directories_next::ProjectDirs; use log::{info, warn}; use once_cell::sync::OnceCell; +use crate::flc; + static CONFIG_CACHE_PATH: OnceCell> = OnceCell::new(); #[derive(Debug, Clone)] @@ -108,16 +110,16 @@ pub fn set_config_cache_path(cache_name: &'static str, config_name: &'static str if !config_folder.exists() && let Err(e) = fs::create_dir_all(&config_folder) { - warnings.push(format!("Cannot create config folder \"{}\", reason {e}", config_folder.to_string_lossy())); + warnings.push(flc!("core_cannot_create_config_folder", folder = config_folder.to_string_lossy(), reason = e.to_string())); } if !cache_folder.exists() && let Err(e) = fs::create_dir_all(&cache_folder) { - warnings.push(format!("Cannot create cache folder \"{}\", reason {e}", cache_folder.to_string_lossy())); + warnings.push(flc!("core_cannot_create_cache_folder", folder = cache_folder.to_string_lossy(), reason = e.to_string())); } Some(ConfigCachePath { config_folder, cache_folder }) } else { - warnings.push("Cannot set config/cache path - config and cache will not be used.".to_string()); + warnings.push(flc!("core_cannot_set_config_cache_path")); None }; @@ -150,7 +152,7 @@ pub(crate) fn open_cache_folder( file_handler_default = Some(match OpenOptions::new().truncate(true).write(true).create(true).open(&cache_file) { Ok(t) => t, Err(e) => { - warnings.push(format!("Cannot create or open cache file \"{}\", reason {e}", cache_file.to_string_lossy())); + warnings.push(flc!("core_cannot_create_or_open_cache_file", file = cache_file.to_string_lossy(), reason = e.to_string())); return None; } }); @@ -158,7 +160,11 @@ pub(crate) fn open_cache_folder( file_handler_json = Some(match OpenOptions::new().truncate(true).write(true).create(true).open(&cache_file_json) { Ok(t) => t, Err(e) => { - warnings.push(format!("Cannot create or open cache file \"{}\", reason {e}", cache_file_json.to_string_lossy())); + warnings.push(flc!( + "core_cannot_create_or_open_cache_file", + file = cache_file_json.to_string_lossy(), + reason = e.to_string() + )); return None; } }); diff --git a/czkawka_core/src/common/dir_traversal.rs b/czkawka_core/src/common/dir_traversal.rs index f7e4f6a26..64a1cbe86 100644 --- a/czkawka_core/src/common/dir_traversal.rs +++ b/czkawka_core/src/common/dir_traversal.rs @@ -680,7 +680,7 @@ mod tests { fn get_cd_mut(&mut self) -> &mut CommonToolData { self } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { false } } diff --git a/czkawka_core/src/common/extensions.rs b/czkawka_core/src/common/extensions.rs index dbcc0fb57..083ca4361 100644 --- a/czkawka_core/src/common/extensions.rs +++ b/czkawka_core/src/common/extensions.rs @@ -36,13 +36,11 @@ impl Extensions { } if e.contains(' ') { - messages - .warnings - .push(format!("{extension} is not a valid extension because it contains empty space inside")); + messages.warnings.push(flc!("core_invalid_extension_contains_space", extension = extension)); return None; } if e.contains('.') { - messages.warnings.push(format!("{extension} is not a valid extension because it contains dot inside")); + messages.warnings.push(flc!("core_invalid_extension_contains_dot", extension = extension)); return None; } Some(e) diff --git a/czkawka_core/src/common/image.rs b/czkawka_core/src/common/image.rs index b1b827ee6..9f0544724 100644 --- a/czkawka_core/src/common/image.rs +++ b/czkawka_core/src/common/image.rs @@ -20,26 +20,28 @@ use rawler::rawsource::RawSource; use crate::common::consts::{HEIC_EXTENSIONS, IMAGE_RS_EXTENSIONS, JXL_IMAGE_EXTENSIONS, RAW_IMAGE_EXTENSIONS}; use crate::common::create_crash_message; +use crate::flc; use crate::helpers::debug_timer::Timer; // #[cfg(feature = "heif")] // use libheif_rs::LibHeif; const MAXIMUM_IMAGE_PIXELS: u32 = 2_000_000_000; -pub(crate) fn get_jxl_image(path: &str) -> anyhow::Result { - let file = File::open(path)?; - let decoder = JxlDecoder::new(file)?; +pub(crate) fn get_jxl_image(path: &str) -> Result { + let file = File::open(path).map_err(|e| e.to_string())?; + let decoder = JxlDecoder::new(file).map_err(|e| e.to_string())?; - let image = DynamicImage::from_decoder(decoder)?; + let image = DynamicImage::from_decoder(decoder).map_err(|e| e.to_string())?; Ok(image) } // Using this instead of image::open because image::open only reads content of files if extension matches content // This is not really helpful when trying to show preview of files with wrong extensions -pub(crate) fn decode_normal_image(path: &str) -> anyhow::Result { - let file = File::open(path)?; - let img = ImageReader::new(std::io::BufReader::new(file)).with_guessed_format()?.decode()?; +pub(crate) fn decode_normal_image(path: &str) -> Result { + let file = File::open(path).map_err(|e| e.to_string())?; + let reader = ImageReader::new(std::io::BufReader::new(file)).with_guessed_format().map_err(|e| e.to_string())?; + let img = reader.decode().map_err(|e| e.to_string())?; Ok(img) } @@ -52,30 +54,25 @@ pub fn get_dynamic_image_from_path(path: &str) -> Result { let img = if HEIC_EXTENSIONS.iter().any(|ext| path_lower.ends_with(ext)) { #[cfg(feature = "heif")] { - get_dynamic_image_from_heic(path).map_err(|e| format!("Cannot open heic file \"{path}\": {e}")) + get_dynamic_image_from_heic(path).map_err(|e| flc!("core_image_open_failed", path = path, reason = e)) } #[cfg(not(feature = "heif"))] { - decode_normal_image(path).map_err(|e| format!("Cannot open image file \"{path}\": {e}")) + decode_normal_image(path).map_err(|e| flc!("core_image_open_failed", path = path, reason = e)) } } else if JXL_IMAGE_EXTENSIONS.iter().any(|ext| path_lower.ends_with(ext)) { - get_jxl_image(path).map_err(|e| format!("Cannot open jxl image file \"{path}\": {e}")) + get_jxl_image(path).map_err(|e| flc!("core_image_open_failed", path = path, reason = e)) } else if RAW_IMAGE_EXTENSIONS.iter().any(|ext| path_lower.ends_with(ext)) { - get_raw_image(path).map_err(|e| format!("Cannot open raw image file \"{path}\": {e}")) + get_raw_image(path).map_err(|e| flc!("core_image_open_failed", path = path, reason = e)) } else { - decode_normal_image(path).map_err(|e| format!("Cannot open image file \"{path}\": {e}")) + decode_normal_image(path).map_err(|e| flc!("core_image_open_failed", path = path, reason = e)) }?; if img.width() == 0 || img.height() == 0 { - return Err(format!("Image has zero width or height \"{path}\"")); + return Err(flc!("core_image_zero_dimensions", path = path)); } if img.width() as u64 * img.height() as u64 > MAXIMUM_IMAGE_PIXELS as u64 { - return Err(format!( - "Image is too large ({}x{}) - more than supported {} pixels \"{path}\"", - img.width(), - img.height(), - MAXIMUM_IMAGE_PIXELS - )); + return Err(flc!("core_image_too_large", width = img.width(), height = img.height(), max = MAXIMUM_IMAGE_PIXELS)); } Ok(img) }); @@ -84,7 +81,7 @@ pub fn get_dynamic_image_from_path(path: &str) -> Result { match res { Ok(t) => { if t.width() == 0 || t.height() == 0 { - return Err(format!("Image has zero width or height \"{path}\"")); + return Err(flc!("core_image_zero_dimensions", path = path)); } let rotation = get_rotation_from_exif(path).unwrap_or(None); @@ -99,7 +96,7 @@ pub fn get_dynamic_image_from_path(path: &str) -> Result { Some(ExifOrientation::Rotate270CW) => Ok(t.rotate270()), } } - Err(e) => Err(format!("Cannot open image file \"{path}\": {e}")), + Err(e) => Err(flc!("core_image_open_failed", path = path, reason = e)), } } else { let message = create_crash_message("Image-rs or libraw-rs or jxl-oxide", path, "https://github.com/image-rs/image/issues"); @@ -109,27 +106,29 @@ pub fn get_dynamic_image_from_path(path: &str) -> Result { } #[cfg(feature = "heif")] -pub(crate) fn get_dynamic_image_from_heic(path: &str) -> anyhow::Result { +pub(crate) fn get_dynamic_image_from_heic(path: &str) -> Result { // let libheif = LibHeif::new(); - let im = HeifContext::read_from_file(path)?; - let handle = im.primary_image_handle()?; + let im = HeifContext::read_from_file(path).map_err(|e| format!("Error reading HEIC/HEIF file: {e}"))?; + let handle = im.primary_image_handle().map_err(|e| format!("Error getting primary image handle: {e}"))?; // let image = libheif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?; // Enable when using libheif 0.19 - let image = handle.decode(ColorSpace::Rgb(RgbChroma::Rgb), None)?; + let image = handle + .decode(ColorSpace::Rgb(RgbChroma::Rgb), None) + .map_err(|e| format!("Error decoding HEIC/HEIF image: {e}"))?; let width = image.width(); let height = image.height(); let planes = image.planes(); - let interleaved_plane = planes.interleaved.ok_or_else(|| anyhow::anyhow!("Failed to get interleaved plane"))?; + let interleaved_plane = planes.interleaved.ok_or_else(|| "No interleaved plane found in HEIC/HEIF image".to_string())?; ImageBuffer::from_raw(width, height, interleaved_plane.data.to_owned()) .map(DynamicImage::ImageRgb8) - .ok_or_else(|| anyhow::anyhow!("Failed to create image buffer")) + .ok_or_else(|| "Failed to create image buffer".to_string()) } #[cfg(feature = "libraw")] -pub(crate) fn get_raw_image>(path: P) -> anyhow::Result { - let buf = fs::read(path.as_ref())?; +pub(crate) fn get_raw_image>(path: P) -> Result { + let buf = fs::read(path.as_ref()).map_err(|e| format!("Error reading image: {e}"))?; let processor = Processor::new(); - let processed = processor.process_8bit(&buf)?; + let processed = processor.process_8bit(&buf).map_err(|e| format!("Error processing RAW image: {e}"))?; let width = processed.width(); let height = processed.height(); @@ -137,9 +136,9 @@ pub(crate) fn get_raw_image>(path: P) -> anyhow::Result + std::fmt::Debug>(path: P) -> Result timer.checkpoint("Developed raw image"); - let dynamic_image = developed_image.to_dynamic_image().ok_or("Failed to convert image to DynamicImage")?; + let dynamic_image = developed_image.to_dynamic_image().ok_or("Failed to convert image to DynamicImage".to_string())?; timer.checkpoint("Converted to DynamicImage"); diff --git a/czkawka_core/src/common/mod.rs b/czkawka_core/src/common/mod.rs index 911c61860..054b1ee25 100644 --- a/czkawka_core/src/common/mod.rs +++ b/czkawka_core/src/common/mod.rs @@ -36,6 +36,35 @@ static ALL_AVAILABLE_THREADS: std::sync::LazyLock>> = std::s const MAX_SYMLINK_HARDLINK_ATTEMPTS: u8 = 5; +#[cfg(all(feature = "xdg_portal_trash", target_os = "linux"))] +thread_local! { + static TOKIO_RT: std::cell::RefCell>> = const { std::cell::RefCell::new(None) }; +} + +#[cfg(all(feature = "xdg_portal_trash", target_os = "linux"))] +fn with_runtime(f: F) -> Result +where + F: FnOnce(&tokio::runtime::Runtime) -> Result, +{ + TOKIO_RT.with(|cell| { + let mut opt = cell.borrow_mut(); + + if opt.is_none() { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|e| format!("Failed to build Tokio runtime: {e}")); + + *opt = Some(rt); + } + + match opt.as_ref().expect("Tokio runtime is initialized before") { + Ok(rt) => f(rt), + Err(e) => Err(e.clone()), + } + }) +} + pub fn get_number_of_threads() -> usize { let data = NUMBER_OF_THREADS.lock().expect("Cannot fail").expect("Should be set before get"); if data >= 1 { data } else { get_all_available_threads() } @@ -76,18 +105,18 @@ pub fn set_number_of_threads(thread_number: usize) { pub fn check_if_folder_contains_only_empty_folders>(path: P) -> Result<(), String> { let path = path.as_ref(); if !path.is_dir() { - return Err(format!("Trying to remove folder \"{}\" which is not a directory", path.to_string_lossy())); + return Err(flc!("core_not_directory_remove", path = path.to_string_lossy())); } let mut entries_to_check = Vec::new(); let Ok(initial_entry) = path.read_dir() else { - return Err(format!("Cannot read directory \"{}\"", path.to_string_lossy())); + return Err(flc!("core_cannot_read_directory", path = path.to_string_lossy())); }; for entry in initial_entry { if let Ok(entry) = entry { entries_to_check.push(entry); } else { - return Err(format!("Cannot read entry from directory \"{}\"", path.to_string_lossy())); + return Err(flc!("core_cannot_read_entry_from_directory", path = path.to_string_lossy())); } } loop { @@ -95,32 +124,28 @@ pub fn check_if_folder_contains_only_empty_folders>(path: P) -> R break; }; let Some(file_type) = entry.file_type().ok() else { - return Err(format!( - "Folder contains file with unknown type \"{}\" inside \"{}\"", - entry.path().to_string_lossy(), - path.to_string_lossy() + return Err(flc!( + "core_unknown_directory_entry", + entry = entry.path().to_string_lossy().to_string(), + path = path.to_string_lossy() )); }; if !file_type.is_dir() { - return Err(format!("Folder contains file \"{}\" inside \"{}\"", entry.path().to_string_lossy(), path.to_string_lossy())); + return Err(flc!( + "core_folder_contains_file_inside", + entry = entry.path().to_string_lossy().to_string(), + folder = path.to_string_lossy() + )); } let Ok(internal_read_dir) = entry.path().read_dir() else { - return Err(format!( - "Cannot read directory \"{}\" inside \"{}\"", - entry.path().to_string_lossy(), - path.to_string_lossy() - )); + return Err(flc!("core_cannot_read_directory", path = path.to_string_lossy().to_string())); }; for internal_elements in internal_read_dir { if let Ok(internal_element) = internal_elements { entries_to_check.push(internal_element); } else { - return Err(format!( - "Cannot read entry from directory \"{}\" inside \"{}\"", - entry.path().to_string_lossy(), - path.to_string_lossy() - )); + return Err(flc!("core_cannot_read_entry_from_directory", path = path.to_string_lossy().to_string())); } } } @@ -129,17 +154,30 @@ pub fn check_if_folder_contains_only_empty_folders>(path: P) -> R } /// A wrapper around `trash::delete`. Note that for platforms that do not have native trash support -/// (Android, iOS), this function will always return an [`Error`]. +/// (Android, iOS), this function will always return an [`Error`]. When the `xdg_portal_trash` feature is +/// enabled, the portal-based implementation will only be used on Linux; on other desktop OSes the +/// regular `trash::delete` fallback will be used instead. fn trash_delete>(path: P) -> Result<(), String> { let path = path.as_ref(); - #[cfg(not(any(target_os = "android", target_os = "ios")))] + #[cfg(not(any(target_os = "android", target_os = "ios", all(feature = "xdg_portal_trash", target_os = "linux"))))] { trash::delete(path).map_err(|err| err.to_string()) } + #[cfg(all(feature = "xdg_portal_trash", target_os = "linux"))] + { + use std::os::fd::AsFd; + let file = std::fs::OpenOptions::new().write(true).read(true).open(path).map_err(|err| err.to_string())?; + + with_runtime(|rt| rt.block_on(async move { ashpd::desktop::trash::trash_file(&file.as_fd()).await.map_err(|e| e.to_string()) }))?; + + Ok(()) + } + #[cfg(any(target_os = "android", target_os = "ios"))] { + let _path = path; Err("trash is not supported on this platform".to_string()) } } diff --git a/czkawka_core/src/common/process_utils.rs b/czkawka_core/src/common/process_utils.rs index 8a7371464..f29c40f6e 100644 --- a/czkawka_core/src/common/process_utils.rs +++ b/czkawka_core/src/common/process_utils.rs @@ -6,6 +6,8 @@ use std::time::{Duration, Instant}; use log::{error, warn}; +use crate::flc; + #[expect(clippy::needless_pass_by_ref_mut)] pub fn disable_windows_console_window(command: &mut Command) { #[cfg(target_os = "windows")] @@ -37,7 +39,7 @@ pub fn run_command_interruptible(mut command: Command, stop_flag: &Arc c, - Err(e) => return Some(Err(format!("Failed to spawn command: {e}"))), + Err(e) => return Some(Err(flc!("core_failed_to_spawn_command", reason = e.to_string()))), }; let Some(mut stdout) = child.stdout.take() else { @@ -95,13 +97,13 @@ pub fn run_command_interruptible(mut command: Command, stop_flag: &Arc break, Ok(None) => thread::sleep(Duration::from_millis(100)), - Err(e) => return Some(Err(format!("Failed to check process status: {e}"))), + Err(e) => return Some(Err(flc!("core_failed_to_check_process_status", reason = e.to_string()))), } } let status = match child.wait() { Ok(s) => s, - Err(e) => return Some(Err(format!("Failed to wait for process: {e}"))), + Err(e) => return Some(Err(flc!("core_failed_to_wait_for_process", reason = e.to_string()))), }; let _ = out_handle.join(); diff --git a/czkawka_core/src/common/tool_data.rs b/czkawka_core/src/common/tool_data.rs index 6cb97b4e3..f4e00eb21 100644 --- a/czkawka_core/src/common/tool_data.rs +++ b/czkawka_core/src/common/tool_data.rs @@ -37,6 +37,7 @@ pub struct CommonToolData { pub(crate) use_reference_folders: bool, pub(crate) dry_run: bool, pub(crate) move_to_trash: bool, + pub(crate) hide_hard_links: bool, } #[derive(Debug, Clone, Default)] @@ -113,6 +114,7 @@ impl CommonToolData { use_reference_folders: false, dry_run: false, move_to_trash: false, + hide_hard_links: false, } } } @@ -132,12 +134,19 @@ pub trait CommonData { fn get_test_type(&self) -> (ToolType, CheckingMethod) { (self.get_cd().tool_type, self.get_check_method()) } - fn found_any_broken_files(&self) -> bool; + fn found_any_items(&self) -> bool; fn get_tool_type(&self) -> ToolType { self.get_cd().tool_type } + fn set_hide_hard_links(&mut self, hide_hard_links: bool) { + self.get_cd_mut().hide_hard_links = hide_hard_links; + } + fn get_hide_hard_links(&self) -> bool { + self.get_cd().hide_hard_links + } + fn set_dry_run(&mut self, dry_run: bool) { self.get_cd_mut().dry_run = dry_run; } @@ -563,7 +572,7 @@ mod tests { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { false } } diff --git a/czkawka_core/src/common/video_utils.rs b/czkawka_core/src/common/video_utils.rs index c0bf2311c..645b05cf9 100644 --- a/czkawka_core/src/common/video_utils.rs +++ b/czkawka_core/src/common/video_utils.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use crate::common::consts::VIDEO_RESOLUTION_LIMIT; use crate::common::process_utils::disable_windows_console_window; use crate::common::progress_stop_handler::check_if_stop_received; +use crate::flc; pub const VIDEO_THUMBNAILS_SUBFOLDER: &str = "video_thumbnails"; @@ -27,7 +28,7 @@ pub struct VideoMetadata { impl VideoMetadata { pub fn from_path(path: &Path) -> Result { - let info = ffprobe(path).map_err(|e| format!("Failed to read video properties: {e}"))?; + let info = ffprobe(path).map_err(|e| flc!("core_failed_to_read_video_properties", reason = e.to_string()))?; let mut metadata = Self::default(); @@ -50,7 +51,7 @@ impl VideoMetadata { && w >= 0 { if w > VIDEO_RESOLUTION_LIMIT as i64 { - return Err(format!("Video width {w} exceeds the limit of {VIDEO_RESOLUTION_LIMIT}")); + return Err(flc!("core_video_width_exceeds_limit", width = w, limit = VIDEO_RESOLUTION_LIMIT)); } metadata.width = Some(w as u32); } @@ -58,7 +59,7 @@ impl VideoMetadata { && h >= 0 { if h > VIDEO_RESOLUTION_LIMIT as i64 { - return Err(format!("Video height {h} exceeds the limit of {VIDEO_RESOLUTION_LIMIT}")); + return Err(flc!("core_video_height_exceeds_limit", height = h, limit = VIDEO_RESOLUTION_LIMIT)); } metadata.height = Some(h as u32); } @@ -100,10 +101,7 @@ impl VideoMetadata { pub(crate) fn extract_frame_ffmpeg(video_path: &Path, timestamp: f32, max_values: Option<(u32, u32)>) -> Result { // This function returns strange status 234, when path contains non default UTF-8 characters, not sure why if !video_path.exists() { - return Err(format!( - "Video file does not exist(could be removed between scan/later steps): \"{}\"", - video_path.to_string_lossy() - )); + return Err(flc!("core_video_file_does_not_exist", path = video_path.to_string_lossy())); } let mut command = Command::new("ffmpeg"); @@ -130,14 +128,19 @@ pub(crate) fn extract_frame_ffmpeg(video_path: &Path, timestamp: f32, max_values .stdout(Stdio::piped()) .stderr(Stdio::null()) .output() - .map_err(|e| format!("Failed to execute ffmpeg: {e}"))?; + .map_err(|e| flc!("core_failed_to_execute_ffmpeg", reason = e.to_string()))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).replace("\r\n", "\n").replace("\n", " "); - return Err(format!("ffmpeg failed with status: {} - {stderr} - command {command:?} ", output.status)); + return Err(flc!( + "core_ffmpeg_failed_with_status", + status = output.status.to_string(), + stderr = stderr, + command = format!("{:?}", command) + )); } - let img = image::load_from_memory(&output.stdout).map_err(|e| format!("Failed to image frame: {e}"))?; + let img = image::load_from_memory(&output.stdout).map_err(|e| flc!("core_failed_to_load_image_frame", reason = e.to_string()))?; Ok(img.into_rgb8()) } @@ -183,14 +186,14 @@ pub fn generate_thumbnail( let mut imgs = Vec::new(); for ft in frame_times { if check_if_stop_received(stop_flag) { - return Err(String::from("Thumbnail generation was stopped by user")); + return Err(flc!("core_thumbnail_generation_stopped_by_user")); } match extract_frame_ffmpeg(video_path, ft, Some((max_width, max_height))) { Ok(img) => imgs.push(img), Err(e) => { let _ = fs::write(&thumbnail_path, b""); - return Err(format!("Failed to extract frame at {ft} seconds from \"{}\": {e}", video_path.to_string_lossy())); + return Err(flc!("core_failed_to_extract_frame", time = ft, file = video_path.to_string_lossy(), reason = e)); } } } @@ -200,10 +203,7 @@ pub fn generate_thumbnail( if imgs.iter().any(|img| img.height() != first_img.height() || img.width() != first_img.width()) { let _ = fs::write(&thumbnail_path, b""); - return Err(format!( - "Failed to generate thumbnail for \"{}\": extracted frames have different dimensions", - video_path.to_string_lossy() - )); + return Err(flc!("core_failed_to_generate_thumbnail_frames_different_dimensions", file = video_path.to_string_lossy())); } let mut new_thumbnail = RgbImage::new(first_img.width() * tiles_size as u32, first_img.height() * tiles_size as u32); for (idx, img) in imgs.iter().enumerate() { @@ -211,24 +211,29 @@ pub fn generate_thumbnail( let y = (idx / tiles_size) as u32 * img.height(); new_thumbnail .copy_from(img, x, y) - .map_err(|e| format!("Failed to generate thumbnail for \"{}\": {e}", video_path.to_string_lossy()))?; + .map_err(|e| flc!("core_failed_to_generate_thumbnail", file = video_path.to_string_lossy(), reason = e.to_string()))?; } if let Err(e) = new_thumbnail.save(&thumbnail_path) { let _ = fs::write(&thumbnail_path, b""); - return Err(format!("Failed to save thumbnail for \"{}\": {e}", video_path.to_string_lossy())); + return Err(flc!("core_failed_to_save_thumbnail", file = video_path.to_string_lossy(), reason = e.to_string())); } } else { match extract_frame_ffmpeg(video_path, seek_time as f32, Some((max_width, max_height))) { Ok(img) => { if let Err(e) = img.save(&thumbnail_path) { let _ = fs::write(&thumbnail_path, b""); - return Err(format!("Failed to save thumbnail for \"{}\": {e}", video_path.to_string_lossy())); + return Err(flc!("core_failed_to_save_thumbnail", file = video_path.to_string_lossy(), reason = e.to_string())); } } Err(e) => { let _ = fs::write(&thumbnail_path, b""); - return Err(format!("Failed to extract frame at {seek_time} seconds from \"{}\" - {e}", video_path.to_string_lossy())); + return Err(flc!( + "core_failed_to_extract_frame_at_seek_time", + time = seek_time, + file = video_path.to_string_lossy(), + reason = e + )); } } } diff --git a/czkawka_core/src/tools/bad_extensions/traits.rs b/czkawka_core/src/tools/bad_extensions/traits.rs index 12bc7a9b3..24babbada 100644 --- a/czkawka_core/src/tools/bad_extensions/traits.rs +++ b/czkawka_core/src/tools/bad_extensions/traits.rs @@ -110,7 +110,7 @@ impl CommonData for BadExtensions { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.get_information().number_of_files_with_bad_extension > 0 } } diff --git a/czkawka_core/src/tools/bad_names/traits.rs b/czkawka_core/src/tools/bad_names/traits.rs index 8a75727be..59a07f75a 100644 --- a/czkawka_core/src/tools/bad_names/traits.rs +++ b/czkawka_core/src/tools/bad_names/traits.rs @@ -122,7 +122,7 @@ impl CommonData for BadNames { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_files_with_bad_names > 0 } } diff --git a/czkawka_core/src/tools/big_file/traits.rs b/czkawka_core/src/tools/big_file/traits.rs index 80b6a5b0f..b157ecdd3 100644 --- a/czkawka_core/src/tools/big_file/traits.rs +++ b/czkawka_core/src/tools/big_file/traits.rs @@ -120,7 +120,7 @@ impl CommonData for BigFile { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_real_files > 0 } } diff --git a/czkawka_core/src/tools/broken_files/core.rs b/czkawka_core/src/tools/broken_files/core.rs index e516b165d..7552788df 100644 --- a/czkawka_core/src/tools/broken_files/core.rs +++ b/czkawka_core/src/tools/broken_files/core.rs @@ -14,13 +14,13 @@ use rayon::prelude::*; use crate::common::cache::{CACHE_BROKEN_FILES_VERSION, load_and_split_cache_generalized_by_path, save_and_connect_cache_generalized_by_path}; use crate::common::consts::{AUDIO_FILES_EXTENSIONS, IMAGE_RS_BROKEN_FILES_EXTENSIONS, PDF_FILES_EXTENSIONS, VIDEO_FILES_EXTENSIONS, ZIP_FILES_EXTENSIONS}; +use crate::common::create_crash_message; use crate::common::dir_traversal::{DirTraversalBuilder, DirTraversalResult}; use crate::common::model::{ToolType, WorkContinueStatus}; use crate::common::process_utils::run_command_interruptible; use crate::common::progress_data::{CurrentStage, ProgressData}; use crate::common::progress_stop_handler::{check_if_stop_received, prepare_thread_handler_common}; use crate::common::tool_data::{CommonData, CommonToolData}; -use crate::common::{create_crash_message, debug_save_file}; use crate::helpers::audio_checker; use crate::tools::broken_files::{BrokenEntry, BrokenFiles, BrokenFilesParameters, Info, TypeOfFile}; @@ -170,7 +170,7 @@ impl BrokenFiles { file_entry.error_string = format!("{error_message}{}", additional_message.map(|e| format!(" ({e})")).unwrap_or_default()); return Some(file_entry); } else if !output.status.success() { - debug_save_file("ffprobe_failed_output.txt", &format!("{} --- \n{}", file_entry.path.to_string_lossy(), combined)); + // debug_save_file("ffprobe_failed_output.txt", &format!("{} --- \n{}", file_entry.path.to_string_lossy(), combined)); file_entry.error_string = format!("ffprobe exited with non-zero status: {}", output.status); return Some(file_entry); } @@ -230,7 +230,7 @@ impl BrokenFiles { } else if let Some((error_message, additional_message)) = ffmpeg_message.iter().find(|(err, _)| combined.contains(err)) { file_entry.error_string = format!("{error_message}{}", additional_message.map(|e| format!(" ({e})")).unwrap_or_default()); } else if !output.status.success() { - debug_save_file("ffmpeg_failed_output.txt", &format!("{} --- \n{}", file_entry.path.to_string_lossy(), combined)); + // debug_save_file("ffmpeg_failed_output.txt", &format!("{} --- \n{}", file_entry.path.to_string_lossy(), combined)); file_entry.error_string = format!("ffmpeg exited with non-zero status: {}", output.status); } } diff --git a/czkawka_core/src/tools/broken_files/traits.rs b/czkawka_core/src/tools/broken_files/traits.rs index 14739153f..46bd13bb3 100644 --- a/czkawka_core/src/tools/broken_files/traits.rs +++ b/czkawka_core/src/tools/broken_files/traits.rs @@ -134,7 +134,7 @@ impl CommonData for BrokenFiles { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_broken_files > 0 } } diff --git a/czkawka_core/src/tools/duplicate/core.rs b/czkawka_core/src/tools/duplicate/core.rs index 4acda904f..d78ad9464 100644 --- a/czkawka_core/src/tools/duplicate/core.rs +++ b/czkawka_core/src/tools/duplicate/core.rs @@ -239,7 +239,7 @@ impl DuplicateFinder { self.common_data.text_messages.warnings.extend(warnings); let grouped_file_entries: Vec<(u64, Vec)> = grouped_file_entries.into_iter().collect(); - let rayon_max_len = if self.get_params().ignore_hard_links { 3 } else { 100 }; + let rayon_max_len = if self.get_hide_hard_links() { 3 } else { 100 }; let start_time = Instant::now(); // We only gather files with more than 1 entry, because only this will be later used @@ -255,7 +255,7 @@ impl DuplicateFinder { return None; } - let vector = if self.get_params().ignore_hard_links { filter_hard_links(vec) } else { vec }; + let vector = if self.get_hide_hard_links() { filter_hard_links(vec) } else { vec }; if vector.len() > 1 { Some((size, vector.into_iter().map(FileEntry::into_duplicate_entry).collect())) diff --git a/czkawka_core/src/tools/duplicate/mod.rs b/czkawka_core/src/tools/duplicate/mod.rs index 064f12252..409e87a19 100644 --- a/czkawka_core/src/tools/duplicate/mod.rs +++ b/czkawka_core/src/tools/duplicate/mod.rs @@ -27,6 +27,7 @@ use crate::common::model::{CheckingMethod, FileEntry, HashType}; use crate::common::progress_stop_handler::check_if_stop_received; use crate::common::tool_data::CommonToolData; use crate::common::traits::ResultEntry; +use crate::flc; pub const PREHASHING_BUFFER_SIZE: u64 = 4 * 1024; pub const THREAD_BUFFER_SIZE: usize = 2 * 1024 * 1024; @@ -84,7 +85,6 @@ pub struct Info { pub struct DuplicateFinderParameters { pub check_method: CheckingMethod, pub hash_type: HashType, - pub ignore_hard_links: bool, pub use_prehash_cache: bool, pub minimal_cache_file_size: u64, pub minimal_prehash_cache_file_size: u64, @@ -95,7 +95,6 @@ impl DuplicateFinderParameters { pub fn new( check_method: CheckingMethod, hash_type: HashType, - ignore_hard_links: bool, use_prehash_cache: bool, minimal_cache_file_size: u64, minimal_prehash_cache_file_size: u64, @@ -104,7 +103,6 @@ impl DuplicateFinderParameters { Self { check_method, hash_type, - ignore_hard_links, use_prehash_cache, minimal_cache_file_size, minimal_prehash_cache_file_size, @@ -232,14 +230,18 @@ pub(crate) fn hash_calculation_limit(buffer: &mut [u8], file_entry: &DuplicateEn Ok(t) => t, Err(e) => { size_counter.fetch_add(limit, Ordering::Relaxed); - return Err(format!("Unable to check hash of file \"{}\", reason {e}", file_entry.path.to_string_lossy())); + return Err(flc!( + "core_unable_check_hash_of_file", + file = file_entry.path.to_string_lossy().to_string(), + reason = e.to_string() + )); } }; let hasher = &mut *hash_type.hasher(); #[expect(clippy::indexing_slicing)] // Safe, because limit is always <= buffer size let n = match file_handler.read(&mut buffer[..limit as usize]) { Ok(t) => t, - Err(e) => return Err(format!("Error happened when checking hash of file \"{}\", reason {}", file_entry.path.to_string_lossy(), e)), + Err(e) => return Err(flc!("core_error_checking_hash_of_file", file = file_entry.path.to_string_lossy(), reason = e.to_string())), }; #[expect(clippy::indexing_slicing)] // Safe, because we read only n bytes, which is always <= limit <= buffer size @@ -259,7 +261,7 @@ pub fn hash_calculation( Ok(t) => t, Err(e) => { size_counter.fetch_add(file_entry.size, Ordering::Relaxed); - return Err(format!("Unable to check hash of file \"{}\", reason {e}", file_entry.path.to_string_lossy())); + return Err(flc!("core_unable_check_hash_of_file", file = file_entry.path.to_string_lossy(), reason = e.to_string())); } }; let hasher = &mut *hash_type.hasher(); @@ -267,7 +269,7 @@ pub fn hash_calculation( let n = match file_handler.read(buffer) { Ok(0) => break, Ok(t) => t, - Err(e) => return Err(format!("Error happened when checking hash of file \"{}\", reason {}", file_entry.path.to_string_lossy(), e)), + Err(e) => return Err(flc!("core_error_checking_hash_of_file", file = file_entry.path.to_string_lossy(), reason = e.to_string())), }; #[expect(clippy::indexing_slicing)] // Safe, because we read only n bytes, which is always <= buffer size diff --git a/czkawka_core/src/tools/duplicate/tests.rs b/czkawka_core/src/tools/duplicate/tests.rs index 2f9910174..c50c2eff2 100644 --- a/czkawka_core/src/tools/duplicate/tests.rs +++ b/czkawka_core/src/tools/duplicate/tests.rs @@ -19,7 +19,7 @@ fn test_find_duplicates_by_hash() { fs::write(path.join("file2.txt"), b"duplicate content").unwrap(); fs::write(path.join("unique.txt"), b"unique content").unwrap(); - let params = DuplicateFinderParameters::new(CheckingMethod::Hash, HashType::Blake3, false, false, 0, 0, true); + let params = DuplicateFinderParameters::new(CheckingMethod::Hash, HashType::Blake3, false, 0, 0, true); let mut finder = DuplicateFinder::new(params); finder.set_included_paths(vec![path.to_path_buf()]); @@ -45,7 +45,7 @@ fn test_find_duplicates_by_size() { fs::write(path.join("file2.txt"), b"abcde").unwrap(); fs::write(path.join("unique.txt"), b"123").unwrap(); - let params = DuplicateFinderParameters::new(CheckingMethod::Size, HashType::Blake3, false, false, 0, 0, true); + let params = DuplicateFinderParameters::new(CheckingMethod::Size, HashType::Blake3, false, 0, 0, true); let mut finder = DuplicateFinder::new(params); finder.set_included_paths(vec![path.to_path_buf()]); @@ -76,7 +76,7 @@ fn test_find_duplicates_by_name() { fs::write(dir2.join("duplicate.txt"), b"content2").unwrap(); fs::write(dir1.join("unique.txt"), b"unique").unwrap(); - let params = DuplicateFinderParameters::new(CheckingMethod::Name, HashType::Blake3, false, false, 0, 0, true); + let params = DuplicateFinderParameters::new(CheckingMethod::Name, HashType::Blake3, false, 0, 0, true); let mut finder = DuplicateFinder::new(params); finder.set_recursive_search(true); @@ -104,7 +104,6 @@ fn test_case_insensitive_name_comparison() { CheckingMethod::Name, HashType::Blake3, false, - false, 0, 0, false, // case insensitive @@ -131,7 +130,7 @@ fn test_no_duplicates_found() { fs::write(path.join("file1.txt"), b"content1").unwrap(); fs::write(path.join("file2.txt"), b"content2").unwrap(); - let params = DuplicateFinderParameters::new(CheckingMethod::Hash, HashType::Blake3, false, false, 0, 0, true); + let params = DuplicateFinderParameters::new(CheckingMethod::Hash, HashType::Blake3, false, 0, 0, true); let mut finder = DuplicateFinder::new(params); finder.set_included_paths(vec![path.to_path_buf()]); @@ -158,7 +157,7 @@ fn test_lost_space_calculation() { fs::write(path.join("file2.txt"), &content).unwrap(); fs::write(path.join("file3.txt"), &content).unwrap(); - let params = DuplicateFinderParameters::new(CheckingMethod::Hash, HashType::Blake3, false, false, 0, 0, true); + let params = DuplicateFinderParameters::new(CheckingMethod::Hash, HashType::Blake3, false, 0, 0, true); let mut finder = DuplicateFinder::new(params); finder.set_minimal_file_size(0); diff --git a/czkawka_core/src/tools/duplicate/traits.rs b/czkawka_core/src/tools/duplicate/traits.rs index 9dfe8ed7f..871a97e20 100644 --- a/czkawka_core/src/tools/duplicate/traits.rs +++ b/czkawka_core/src/tools/duplicate/traits.rs @@ -362,7 +362,7 @@ impl CommonData for DuplicateFinder { fn get_check_method(&self) -> CheckingMethod { self.get_params().check_method } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.get_information().number_of_duplicated_files_by_hash > 0 || self.get_information().number_of_duplicated_files_by_name > 0 || self.get_information().number_of_duplicated_files_by_size > 0 diff --git a/czkawka_core/src/tools/empty_files/traits.rs b/czkawka_core/src/tools/empty_files/traits.rs index 6d56fdd83..328cee445 100644 --- a/czkawka_core/src/tools/empty_files/traits.rs +++ b/czkawka_core/src/tools/empty_files/traits.rs @@ -93,7 +93,7 @@ impl CommonData for EmptyFiles { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_empty_files > 0 } } diff --git a/czkawka_core/src/tools/empty_folder/traits.rs b/czkawka_core/src/tools/empty_folder/traits.rs index b1c3c505c..f332a9b94 100644 --- a/czkawka_core/src/tools/empty_folder/traits.rs +++ b/czkawka_core/src/tools/empty_folder/traits.rs @@ -93,7 +93,7 @@ impl CommonData for EmptyFolder { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_empty_folders > 0 } } diff --git a/czkawka_core/src/tools/exif_remover/core.rs b/czkawka_core/src/tools/exif_remover/core.rs index b5f7fd3bb..ba69960c9 100644 --- a/czkawka_core/src/tools/exif_remover/core.rs +++ b/czkawka_core/src/tools/exif_remover/core.rs @@ -18,6 +18,7 @@ use crate::common::model::{ToolType, WorkContinueStatus}; use crate::common::progress_data::{CurrentStage, ProgressData}; use crate::common::progress_stop_handler::{check_if_stop_received, prepare_thread_handler_common}; use crate::common::tool_data::{CommonData, CommonToolData}; +use crate::flc; use crate::tools::exif_remover::{ExifEntry, ExifRemover, ExifRemoverParameters, ExifTagInfo, ExifTagsFixerParams, Info}; impl ExifRemover { @@ -338,7 +339,7 @@ pub fn string_to_exif_tag_group(tag: &str) -> Result { "INTEROP" => Ok(ExifTagGroup::INTEROP), "GPS" => Ok(ExifTagGroup::GPS), "GENERIC" => Ok(ExifTagGroup::GENERIC), - _ => Err(format!("Unknown EXIF tag group: {tag}")), + _ => Err(flc!("core_unknown_exif_tag_group", tag = tag)), } } diff --git a/czkawka_core/src/tools/exif_remover/traits.rs b/czkawka_core/src/tools/exif_remover/traits.rs index 7750240f1..100fb5a93 100644 --- a/czkawka_core/src/tools/exif_remover/traits.rs +++ b/czkawka_core/src/tools/exif_remover/traits.rs @@ -134,7 +134,7 @@ impl CommonData for ExifRemover { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_files_with_exif > 0 } } diff --git a/czkawka_core/src/tools/invalid_symlinks/traits.rs b/czkawka_core/src/tools/invalid_symlinks/traits.rs index 3710b6604..ed6809e6d 100644 --- a/czkawka_core/src/tools/invalid_symlinks/traits.rs +++ b/czkawka_core/src/tools/invalid_symlinks/traits.rs @@ -95,7 +95,7 @@ impl CommonData for InvalidSymlinks { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_invalid_symlinks > 0 } } diff --git a/czkawka_core/src/tools/same_music/core.rs b/czkawka_core/src/tools/same_music/core.rs index 10ca7090f..9aef6122e 100644 --- a/czkawka_core/src/tools/same_music/core.rs +++ b/czkawka_core/src/tools/same_music/core.rs @@ -5,7 +5,6 @@ use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::{mem, panic}; -use anyhow::Context; use crossbeam_channel::Sender; use fun_time::fun_time; use indexmap::IndexSet; @@ -30,6 +29,7 @@ use crate::common::progress_data::{CurrentStage, ProgressData}; use crate::common::progress_stop_handler::{check_if_stop_received, prepare_thread_handler_common}; use crate::common::tool_data::{CommonData, CommonToolData}; use crate::common::traits::ResultEntry; +use crate::flc; use crate::tools::same_music::{GroupedFilesToCheck, Info, MusicEntry, MusicSimilarity, SameMusic, SameMusicParameters}; impl SameMusic { @@ -423,7 +423,7 @@ impl SameMusic { } let mut segments = match match_fingerprints(&f_entry.fingerprint, &e_entry.fingerprint, configuration) { Ok(segments) => segments, - Err(e) => return Some(Err(format!("Error while comparing fingerprints: {e}"))), + Err(e) => return Some(Err(flc!("core_error_comparing_fingerprints", reason = e.to_string()))), }; segments.retain(|s| s.duration(configuration) > minimum_segment_duration && s.score < maximum_difference); if segments.is_empty() { None } else { Some(Ok((e_string, e_entry))) } @@ -532,12 +532,12 @@ impl SameMusic { } // TODO this should be taken from rusty-chromaprint repo, not reimplemented here -fn calc_fingerprint_helper>(path: P, config: &Configuration) -> anyhow::Result> { +fn calc_fingerprint_helper>(path: P, config: &Configuration) -> Result, String> { let path = path.as_ref().to_path_buf(); panic::catch_unwind(|| { let path = &path; - let src = File::open(path).context("failed to open file")?; + let src = File::open(path).map_err(|_| "failed to open file".to_string())?; let mss = MediaSourceStream::new(Box::new(src), Default::default()); let mut hint = Hint::new(); @@ -548,7 +548,9 @@ fn calc_fingerprint_helper>(path: P, config: &Configuration) -> a let meta_opts: MetadataOptions = Default::default(); let fmt_opts: FormatOptions = Default::default(); - let probed = symphonia::default::get_probe().format(&hint, mss, &fmt_opts, &meta_opts).context("unsupported format")?; + let probed = symphonia::default::get_probe() + .format(&hint, mss, &fmt_opts, &meta_opts) + .map_err(|_| "unsupported format".to_string())?; let mut format = probed.format; @@ -556,18 +558,20 @@ fn calc_fingerprint_helper>(path: P, config: &Configuration) -> a .tracks() .iter() .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) - .context("no supported audio tracks")?; + .ok_or_else(|| "no supported audio tracks".to_string())?; let dec_opts: DecoderOptions = Default::default(); - let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts).context("unsupported codec")?; + let mut decoder = symphonia::default::get_codecs() + .make(&track.codec_params, &dec_opts) + .map_err(|_| "unsupported codec".to_string())?; let track_id = track.id; let mut printer = Fingerprinter::new(config); - let sample_rate = track.codec_params.sample_rate.context("missing sample rate")?; - let channels = track.codec_params.channels.context("missing audio channels")?.count() as u32; - printer.start(sample_rate, channels).context("initializing fingerprinter")?; + let sample_rate = track.codec_params.sample_rate.ok_or_else(|| "missing sample rate".to_string())?; + let channels = track.codec_params.channels.ok_or_else(|| "missing audio channels".to_string())?.count() as u32; + printer.start(sample_rate, channels).map_err(|_| "initializing fingerprinter".to_string())?; let mut sample_buf = None; @@ -604,7 +608,7 @@ fn calc_fingerprint_helper>(path: P, config: &Configuration) -> a .unwrap_or_else(|_| { let message = create_crash_message("Symphonia", &path.to_string_lossy(), "https://github.com/pdeljanov/Symphonia"); error!("{message}"); - Err(anyhow::anyhow!("{message}")) + Err(message) }) } diff --git a/czkawka_core/src/tools/same_music/traits.rs b/czkawka_core/src/tools/same_music/traits.rs index 9cdce0a8e..2e9bf1941 100644 --- a/czkawka_core/src/tools/same_music/traits.rs +++ b/czkawka_core/src/tools/same_music/traits.rs @@ -162,7 +162,7 @@ impl CommonData for SameMusic { fn get_check_method(&self) -> CheckingMethod { self.get_params().check_type } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_duplicates > 0 } } diff --git a/czkawka_core/src/tools/similar_images/core.rs b/czkawka_core/src/tools/similar_images/core.rs index f265f2a76..c7a3e2151 100644 --- a/czkawka_core/src/tools/similar_images/core.rs +++ b/czkawka_core/src/tools/similar_images/core.rs @@ -52,7 +52,7 @@ impl SimilarImages { DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { self.images_to_check = grouped_file_entries .into_par_iter() - .flat_map(if self.get_params().ignore_hard_links { |(_, fes)| fes } else { take_1_per_inode }) + .flat_map(if self.get_hide_hard_links() { |(_, fes)| fes } else { take_1_per_inode }) .map(|fe| { let fe_str = fe.path.to_string_lossy().to_string(); let image_entry = fe.into_images_entry(); @@ -701,7 +701,6 @@ mod tests { max_difference: 0, image_filter: FilterType::Lanczos3, exclude_images_with_same_size: false, - ignore_hard_links: false, } } @@ -1147,7 +1146,7 @@ mod connect_results_tests { #[test] fn test_connect_results_real_case() { - let params = SimilarImagesParameters::new(10, 8, HashAlg::Gradient, FilterType::Lanczos3, false, true); + let params = SimilarImagesParameters::new(10, 8, HashAlg::Gradient, FilterType::Lanczos3, false); let _finder = SimilarImages::new(params); let hash1: ImHash = vec![59, 41, 53, 27, 19, 143, 228, 228]; diff --git a/czkawka_core/src/tools/similar_images/mod.rs b/czkawka_core/src/tools/similar_images/mod.rs index 3a842c39a..86e0c9c88 100644 --- a/czkawka_core/src/tools/similar_images/mod.rs +++ b/czkawka_core/src/tools/similar_images/mod.rs @@ -96,11 +96,10 @@ pub struct SimilarImagesParameters { pub hash_alg: HashAlg, pub image_filter: FilterType, pub exclude_images_with_same_size: bool, - pub ignore_hard_links: bool, } impl SimilarImagesParameters { - pub fn new(max_difference: u32, hash_size: u8, hash_alg: HashAlg, image_filter: FilterType, exclude_images_with_same_size: bool, ignore_hard_links: bool) -> Self { + pub fn new(max_difference: u32, hash_size: u8, hash_alg: HashAlg, image_filter: FilterType, exclude_images_with_same_size: bool) -> Self { assert!([8, 16, 32, 64].contains(&hash_size)); Self { max_difference, @@ -108,7 +107,6 @@ impl SimilarImagesParameters { hash_alg, image_filter, exclude_images_with_same_size, - ignore_hard_links, } } } diff --git a/czkawka_core/src/tools/similar_images/tests.rs b/czkawka_core/src/tools/similar_images/tests.rs index 9263cb797..5dfac0a95 100644 --- a/czkawka_core/src/tools/similar_images/tests.rs +++ b/czkawka_core/src/tools/similar_images/tests.rs @@ -42,7 +42,7 @@ fn test_similar_images() { ]; for (idx, (hash_alg, filter_type, hash_size, similarity, duplicates, groups, all_in_similar)) in algo_filter_hash_sim_found.into_iter().enumerate() { - let params = SimilarImagesParameters::new(similarity, hash_size, hash_alg, filter_type, false, true); + let params = SimilarImagesParameters::new(similarity, hash_size, hash_alg, filter_type, false); let mut finder = SimilarImages::new(params); finder.set_included_paths(vec![test_path.clone()]); @@ -69,7 +69,7 @@ fn test_similar_images() { fn test_similar_images_exclude_same_size() { let test_path = get_test_resources_path(); - let params = SimilarImagesParameters::new(10, 8, HashAlg::Gradient, FilterType::Lanczos3, true, true); + let params = SimilarImagesParameters::new(10, 8, HashAlg::Gradient, FilterType::Lanczos3, true); let mut finder = SimilarImages::new(params); finder.set_included_paths(vec![test_path]); @@ -99,7 +99,7 @@ fn test_similar_images_empty_directory() { let temp_dir = TempDir::new().unwrap(); let path = temp_dir.path(); - let params = SimilarImagesParameters::new(10, 8, HashAlg::Gradient, FilterType::Lanczos3, false, true); + let params = SimilarImagesParameters::new(10, 8, HashAlg::Gradient, FilterType::Lanczos3, false); let mut finder = SimilarImages::new(params); finder.set_included_paths(vec![path.to_path_buf()]); diff --git a/czkawka_core/src/tools/similar_images/traits.rs b/czkawka_core/src/tools/similar_images/traits.rs index 1a6f1ef3b..c33f34168 100644 --- a/czkawka_core/src/tools/similar_images/traits.rs +++ b/czkawka_core/src/tools/similar_images/traits.rs @@ -150,7 +150,7 @@ impl CommonData for SimilarImages { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_duplicates > 0 } } diff --git a/czkawka_core/src/tools/similar_videos/core.rs b/czkawka_core/src/tools/similar_videos/core.rs index f73bc698f..6ef2b2526 100644 --- a/czkawka_core/src/tools/similar_videos/core.rs +++ b/czkawka_core/src/tools/similar_videos/core.rs @@ -48,7 +48,7 @@ impl SimilarVideos { DirTraversalResult::SuccessFiles { grouped_file_entries, warnings } => { self.videos_to_check = grouped_file_entries .into_par_iter() - .flat_map(if self.get_params().ignore_hard_links { |(_, fes)| fes } else { take_1_per_inode }) + .flat_map(if self.get_hide_hard_links() { |(_, fes)| fes } else { take_1_per_inode }) .map(|fe| (fe.path.to_string_lossy().to_string(), fe.into_videos_entry())) .collect(); self.common_data.text_messages.warnings.extend(warnings); diff --git a/czkawka_core/src/tools/similar_videos/mod.rs b/czkawka_core/src/tools/similar_videos/mod.rs index 44055868e..407ec1cac 100644 --- a/czkawka_core/src/tools/similar_videos/mod.rs +++ b/czkawka_core/src/tools/similar_videos/mod.rs @@ -84,7 +84,6 @@ impl FileEntry { pub struct SimilarVideosParameters { pub tolerance: i32, pub exclude_videos_with_same_size: bool, - pub ignore_hard_links: bool, pub skip_forward_amount: u32, pub duration: u32, pub crop_detect: Cropdetect, @@ -106,7 +105,6 @@ impl SimilarVideosParameters { pub fn new( tolerance: i32, exclude_videos_with_same_size: bool, - ignore_hard_links: bool, skip_forward_amount: u32, duration: u32, crop_detect: Cropdetect, @@ -120,7 +118,6 @@ impl SimilarVideosParameters { Self { tolerance, exclude_videos_with_same_size, - ignore_hard_links, skip_forward_amount, duration, crop_detect, diff --git a/czkawka_core/src/tools/similar_videos/tests.rs b/czkawka_core/src/tools/similar_videos/tests.rs index fb4385aa8..aa6c97966 100644 --- a/czkawka_core/src/tools/similar_videos/tests.rs +++ b/czkawka_core/src/tools/similar_videos/tests.rs @@ -16,7 +16,7 @@ fn test_similar_videos_empty_directory() { let temp_dir = TempDir::new().unwrap(); let path = temp_dir.path(); - let params = SimilarVideosParameters::new(10, false, false, 15, 10, Cropdetect::Letterbox, false, 0, false); + let params = SimilarVideosParameters::new(10, false, 15, 10, Cropdetect::Letterbox, false, 0, false); let mut finder = SimilarVideos::new(params); finder.set_included_paths(vec![path.to_path_buf()]); diff --git a/czkawka_core/src/tools/similar_videos/traits.rs b/czkawka_core/src/tools/similar_videos/traits.rs index 7f892be83..422e5721d 100644 --- a/czkawka_core/src/tools/similar_videos/traits.rs +++ b/czkawka_core/src/tools/similar_videos/traits.rs @@ -169,7 +169,7 @@ impl CommonData for SimilarVideos { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_duplicates > 0 } } diff --git a/czkawka_core/src/tools/temporary/traits.rs b/czkawka_core/src/tools/temporary/traits.rs index e7de0efa5..64d4698dc 100644 --- a/czkawka_core/src/tools/temporary/traits.rs +++ b/czkawka_core/src/tools/temporary/traits.rs @@ -88,7 +88,7 @@ impl CommonData for Temporary { fn get_cd_mut(&mut self) -> &mut CommonToolData { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { self.information.number_of_temporary_files > 0 } } diff --git a/czkawka_core/src/tools/video_optimizer/core.rs b/czkawka_core/src/tools/video_optimizer/core.rs index aa6f041fa..155923ec6 100644 --- a/czkawka_core/src/tools/video_optimizer/core.rs +++ b/czkawka_core/src/tools/video_optimizer/core.rs @@ -28,6 +28,7 @@ pub use video_cropper::fix_video_crop; use crate::common::cache::CACHE_VIDEO_OPTIMIZE_VERSION; use crate::common::traits::ResultEntry; +use crate::flc; impl VideoOptimizer { pub fn new(params: VideoOptimizerParameters) -> Self { @@ -207,7 +208,7 @@ impl VideoOptimizer { CurrentStage::VideoOptimizerCreatingThumbnails, self.video_transcode_result_entries.len(), self.get_test_type(), - self.video_transcode_result_entries.iter().map(|e| e.size).sum(), + 0, ); let Some(config_cache_path) = get_config_cache_path() else { @@ -381,7 +382,7 @@ impl VideoOptimizer { match process_video(stop_flag, &entry.path.to_string_lossy(), entry.size, video_transcode_params) { Ok(_new_size) => Some(None), - Err(e) => Some(Some(format!("Failed to optimize video \"{}\": {}", entry.path.to_string_lossy(), e))), + Err(e) => Some(Some(flc!("core_failed_to_optimize_video", file = entry.path.to_string_lossy(), reason = e))), } }) .while_some() @@ -413,7 +414,7 @@ impl VideoOptimizer { match fix_video_crop(&entry.path, &entry_crop_params, stop_flag) { Ok(()) => Some(None), - Err(e) => Some(Some(format!("Failed to crop video \"{}\": {}", entry.path.to_string_lossy(), e))), + Err(e) => Some(Some(flc!("core_failed_to_crop_video", file = entry.path.to_string_lossy(), reason = e))), } }) .while_some() diff --git a/czkawka_core/src/tools/video_optimizer/core/video_converter.rs b/czkawka_core/src/tools/video_optimizer/core/video_converter.rs index 5a7f4c16d..744e70b45 100644 --- a/czkawka_core/src/tools/video_optimizer/core/video_converter.rs +++ b/czkawka_core/src/tools/video_optimizer/core/video_converter.rs @@ -6,24 +6,25 @@ use std::sync::atomic::AtomicBool; use crate::common::process_utils::run_command_interruptible; use crate::common::video_utils::VideoMetadata; +use crate::flc; use crate::tools::video_optimizer::{VideoTranscodeEntry, VideoTranscodeFixParams}; pub fn check_video(mut entry: VideoTranscodeEntry) -> VideoTranscodeEntry { let metadata = match VideoMetadata::from_path(&entry.path) { Ok(metadata) => metadata, Err(e) => { - entry.error = Some(format!("Failed to get video metadata for file \"{}\": {}", entry.path.to_string_lossy(), e)); + entry.error = Some(flc!("core_failed_to_get_video_metadata", file = entry.path.to_string_lossy(), reason = e)); return entry; } }; let Some(current_codec) = metadata.codec.clone() else { - entry.error = Some(format!("Failed to get video codec for file \"{}\"", entry.path.to_string_lossy())); + entry.error = Some(flc!("core_failed_to_get_video_codec", file = entry.path.to_string_lossy())); return entry; }; let Some(duration) = metadata.duration else { - entry.error = Some(format!("Failed to get video duration for file \"{}\"", entry.path.to_string_lossy())); + entry.error = Some(flc!("core_failed_to_get_video_duration", file = entry.path.to_string_lossy())); return entry; }; @@ -35,7 +36,7 @@ pub fn check_video(mut entry: VideoTranscodeEntry) -> VideoTranscodeEntry { entry.height = height; } _ => { - entry.error = Some(format!("Failed to get video dimensions for file \"{}\"", entry.path.to_string_lossy())); + entry.error = Some(flc!("core_failed_to_get_video_dimensions", file = entry.path.to_string_lossy())); return entry; } } @@ -69,11 +70,11 @@ pub fn process_video(stop_flag: &Arc, video_path: &str, original_siz match run_command_interruptible(command, stop_flag) { None => { let _ = fs::remove_file(&temp_output); - return Err(String::from("Video processing was stopped by user")); + return Err(flc!("core_video_processing_stopped_by_user")); } Some(Err(e)) => { let _ = fs::remove_file(&temp_output); - return Err(format!("Failed to process video file {video_path}: {e}")); + return Err(flc!("core_failed_to_process_video", file = video_path, reason = e)); } Some(Ok(_)) => { // Command succeeded, continue with validation @@ -82,24 +83,30 @@ pub fn process_video(stop_flag: &Arc, video_path: &str, original_siz let metadata = fs::metadata(&temp_output).map_err(|e| { let _ = fs::remove_file(&temp_output); - format!("Failed to get metadata of optimized file \"{}\": {}", temp_output.to_string_lossy(), e) + flc!( + "core_failed_to_get_metadata_of_optimized_file", + file = temp_output.to_string_lossy(), + reason = e.to_string() + ) })?; let new_size = metadata.len(); if video_transcode_params.fail_if_not_smaller && new_size >= original_size { let _ = fs::remove_file(&temp_output); - return Err(format!( - "Optimized file({}) ({new_size} bytes) is larger than original({}) ({original_size} bytes)", - temp_output.to_string_lossy(), - video_path + return Err(flc!( + "core_optimized_file_larger", + optimized = temp_output.to_string_lossy(), + new_size = new_size, + original = video_path, + original_size = original_size )); } if video_transcode_params.overwrite_original { fs::rename(&temp_output, video_path).map_err(|e| { let _ = fs::remove_file(&temp_output); - format!("Failed to replace file \"{video_path}\" with optimized version: {e}") + flc!("core_failed_to_replace_with_optimized", file = video_path, reason = e.to_string()) })?; return Ok(()); } diff --git a/czkawka_core/src/tools/video_optimizer/core/video_cropper.rs b/czkawka_core/src/tools/video_optimizer/core/video_cropper.rs index bf3ffa511..bbe7e6f7e 100644 --- a/czkawka_core/src/tools/video_optimizer/core/video_cropper.rs +++ b/czkawka_core/src/tools/video_optimizer/core/video_cropper.rs @@ -9,6 +9,7 @@ use log::error; use crate::common::consts::VIDEO_RESOLUTION_LIMIT; use crate::common::process_utils::run_command_interruptible; use crate::common::video_utils::{VideoMetadata, extract_frame_ffmpeg}; +use crate::flc; use crate::tools::video_optimizer::{VideoCropEntry, VideoCropParams, VideoCropSingleFixParams, VideoCroppingMechanism}; const MIN_SAMPLES: usize = 3; @@ -171,15 +172,20 @@ where let tmp_frame = match get_frame(timestamp) { Ok(frame) => frame, Err(e) => { - return Some(Err(format!("Failed to get frame at timestamp \"{}\" {timestamp}: {e}", path.to_string_lossy()))); + return Some(Err(flc!( + "core_failed_get_frame_at_timestamp", + file = path.to_string_lossy().to_string(), + timestamp = timestamp, + reason = e + ))); } }; if tmp_frame.dimensions() != first_frame.dimensions() { - return Some(Err(format!( - "Frame dimensions for timestamp {} do not match the first frame dimensions ({}x{})", - timestamp, - first_frame.width(), - first_frame.height() + return Some(Err(flc!( + "core_frame_dimensions_mismatch", + timestamp = timestamp, + first_w = first_frame.width(), + first_h = first_frame.height() ))); } @@ -257,15 +263,20 @@ where let tmp_frame = match get_frame(timestamp) { Ok(frame) => frame, Err(e) => { - return Some(Err(format!("Failed to get frame from \"{}\" at timestamp {timestamp}: {e}", path.to_string_lossy()))); + return Some(Err(flc!( + "core_failed_get_frame_from_file", + file = path.to_string_lossy().to_string(), + timestamp = timestamp, + reason = e + ))); } }; if tmp_frame.dimensions() != first_frame.dimensions() { - return Some(Err(format!( - "Frame dimensions for timestamp {} do not match the first frame dimensions ({}x{})", - timestamp, - first_frame.width(), - first_frame.height() + return Some(Err(flc!( + "core_frame_dimensions_mismatch", + timestamp = timestamp, + first_w = first_frame.width(), + first_h = first_frame.height() ))); } let dynamic_image_diff: RgbImage = diff_between_dynamic_images(first_frame, tmp_frame); @@ -411,7 +422,7 @@ pub fn fix_video_crop(video_path: &Path, params: &VideoCropSingleFixParams, stop let (left, top, right, bottom) = params.crop_rectangle; if left >= right || top >= bottom { - return Err(format!("Invalid crop rectangle: left={left}, top={top}, right={right}, bottom={bottom}")); + return Err(flc!("core_invalid_crop_rectangle", left = left, top = top, right = right, bottom = bottom)); } let crop_width = right - left; @@ -450,12 +461,12 @@ pub fn fix_video_crop(video_path: &Path, params: &VideoCropSingleFixParams, stop } Some(Err(e)) => { let _ = std::fs::remove_file(&temp_output); - return Err(format!("Failed to crop video file \"{}\": {e}", video_path.to_string_lossy())); + return Err(flc!("core_failed_to_crop_video_file", file = video_path.to_string_lossy(), reason = e)); } Some(Ok(_)) => { if !temp_output.exists() { error!("Cropped video file was not created: {temp_output:?}"); - return Err(format!("Cropped video file was not created: {temp_output:?}")); + return Err(flc!("core_cropped_video_not_created", temp = format!("{:?}", temp_output))); } } } diff --git a/czkawka_core/src/tools/video_optimizer/mod.rs b/czkawka_core/src/tools/video_optimizer/mod.rs index 32a07fd42..3fa54b2ca 100644 --- a/czkawka_core/src/tools/video_optimizer/mod.rs +++ b/czkawka_core/src/tools/video_optimizer/mod.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use crate::common::model::FileEntry; use crate::common::tool_data::CommonToolData; use crate::common::traits::ResultEntry; +use crate::flc; #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum VideoCodec { @@ -50,7 +51,7 @@ impl std::str::FromStr for VideoCodec { "h265" | "libx265" => Ok(Self::H265), "av1" | "libaom-av1" => Ok(Self::Av1), "vp9" | "libvpx-vp9" => Ok(Self::Vp9), - _ => Err(format!("Unknown codec: {codec}")), + _ => Err(flc!("core_unknown_codec", codec = codec)), } } } @@ -73,7 +74,7 @@ impl std::str::FromStr for VideoOptimizerMode { match s.to_lowercase().as_str() { "transcode" | "videotranscode" => Ok(Self::VideoTranscode), "crop" | "videocrop" => Ok(Self::VideoCrop), - _ => Err(format!("Invalid video optimizer mode: '{s}'. Allowed values: transcode, crop")), + _ => Err(flc!("core_invalid_video_optimizer_mode", mode = s)), } } } diff --git a/czkawka_core/src/tools/video_optimizer/traits.rs b/czkawka_core/src/tools/video_optimizer/traits.rs index 81751bccc..cdcdc374f 100644 --- a/czkawka_core/src/tools/video_optimizer/traits.rs +++ b/czkawka_core/src/tools/video_optimizer/traits.rs @@ -177,7 +177,7 @@ impl CommonData for VideoOptimizer { &mut self.common_data } - fn found_any_broken_files(&self) -> bool { + fn found_any_items(&self) -> bool { match &self.params { VideoOptimizerParameters::VideoTranscode(_) => self.information.number_of_videos_to_transcode > 0, VideoOptimizerParameters::VideoCrop(_) => self.information.number_of_videos_to_crop > 0, diff --git a/czkawka_gui/Cargo.toml b/czkawka_gui/Cargo.toml index 5e2a881ed..9ef395fff 100644 --- a/czkawka_gui/Cargo.toml +++ b/czkawka_gui/Cargo.toml @@ -51,6 +51,7 @@ default = [] heif = ["czkawka_core/heif"] libraw = ["czkawka_core/libraw"] libavif = ["czkawka_core/libavif"] +xdg_portal_trash = ["czkawka_core/xdg_portal_trash"] [lints] workspace = true diff --git a/czkawka_gui/src/connect_things/connect_button_search.rs b/czkawka_gui/src/connect_things/connect_button_search.rs index 8c17ded16..478127093 100644 --- a/czkawka_gui/src/connect_things/connect_button_search.rs +++ b/czkawka_gui/src/connect_things/connect_button_search.rs @@ -262,7 +262,6 @@ fn duplicate_search( let params = DuplicateFinderParameters::new( check_method, hash_type, - loaded_commons.hide_hard_links, use_prehash_cache, loaded_commons.minimal_cache_file_size, minimal_prehash_cache_file_size, @@ -597,7 +596,7 @@ fn similar_image_search( thread::Builder::new() .stack_size(DEFAULT_THREAD_SIZE) .spawn(move || { - let params = SimilarImagesParameters::new(similarity, hash_size, hash_alg, image_filter, ignore_same_size, loaded_commons.hide_hard_links); + let params = SimilarImagesParameters::new(similarity, hash_size, hash_alg, image_filter, ignore_same_size); let mut tool = SimilarImages::new(params); set_common_settings(&mut tool, &loaded_commons); @@ -635,7 +634,6 @@ fn similar_video_search( let params = SimilarVideosParameters::new( tolerance, ignore_same_size, - loaded_commons.hide_hard_links, DEFAULT_SKIP_FORWARD_AMOUNT, DEFAULT_VID_HASH_DURATION, DEFAULT_CROP_DETECT, @@ -718,6 +716,7 @@ where component.set_save_also_as_json(loaded_commons.save_also_as_json); component.set_minimal_file_size(loaded_commons.minimal_file_size); component.set_maximal_file_size(loaded_commons.maximal_file_size); + component.set_hide_hard_links(loaded_commons.hide_hard_links); } #[fun_time(message = "clean_tree_view", level = "debug")] diff --git a/data/io.github.qarmin.czkawka.krokiet.metainfo.xml b/data/io.github.qarmin.czkawka.krokiet.metainfo.xml deleted file mode 100644 index d619c86ce..000000000 --- a/data/io.github.qarmin.czkawka.krokiet.metainfo.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - com.github.qarmin.krokiet - Krokiet - Krokiet — find duplicate files, similar images and many more - CC0-1.0 - MIT - -

- Krokiet is a multi functional app to find duplicates, empty folders, similar images, broken files etc. -

-
- io.github.qarmin.krokiet.desktop - - - https://github.com/user-attachments/assets/f5e4b290-d001-4cf4-9f52-dab65a30e441 - - - - - - - - Rafał Mikrut - - https://github.com/qarmin/czkawka - https://github.com/qarmin/czkawka/issues - https://github.com/sponsors/qarmin - https://crowdin.com/project/czkawka -
diff --git a/data/io.github.qarmin.czkawka.krokiet.desktop b/data/io.github.qarmin.krokiet.desktop similarity index 70% rename from data/io.github.qarmin.czkawka.krokiet.desktop rename to data/io.github.qarmin.krokiet.desktop index 47d2497ee..87e519c67 100644 --- a/data/io.github.qarmin.czkawka.krokiet.desktop +++ b/data/io.github.qarmin.krokiet.desktop @@ -9,6 +9,6 @@ Type=Application Name=Krokiet -Comment=Krokiet — multi-functional app to find duplicates, empty folders, similar files and many more. +Comment=Krokiet - multi-functional app to find duplicates, empty folders, similar files and many more. Keywords=Krokiet;duplicate;same;similar;cleaner;copy;copies;compare;files;krokiet diff --git a/data/io.github.qarmin.krokiet.metainfo.xml b/data/io.github.qarmin.krokiet.metainfo.xml new file mode 100644 index 000000000..ab00718f4 --- /dev/null +++ b/data/io.github.qarmin.krokiet.metainfo.xml @@ -0,0 +1,49 @@ + + + io.github.qarmin.krokiet + Krokiet + Multi functional app to find duplicates, similar images and many more + CC0-1.0 + GPL-3.0-only + +

Krokiet is a multi functional app that finds:

+
    +
  • Duplicates
  • +
  • Similar images
  • +
  • Similar audio files
  • +
  • Similar videos
  • +
  • Empty folders
  • +
  • Empty files
  • +
  • Broken files
  • +
  • Temporary files
  • +
  • Big files
  • +
  • Invalid symlinks
  • +
  • Bad names
  • +
  • Videos that can be optimized/cropped
  • +
  • Exif tags
  • +
+
+ io.github.qarmin.krokiet.desktop + + + https://github.com/user-attachments/assets/720e98c3-598a-41aa-a04b-0c0c1d8a28e6 + + + https://github.com/user-attachments/assets/c95e51bf-1ae0-49ec-af92-0195efc98e5d + + + https://github.com/user-attachments/assets/4fe7bec3-4d67-48bb-91bc-91e7d3b82bdc + + + + + + + + Rafał Mikrut + + https://github.com/qarmin/czkawka + https://github.com/qarmin/czkawka/issues + https://github.com/sponsors/qarmin + https://crowdin.com/project/czkawka +
diff --git a/krokiet/Cargo.toml b/krokiet/Cargo.toml index 6e9d7d572..10a62c17e 100644 --- a/krokiet/Cargo.toml +++ b/krokiet/Cargo.toml @@ -35,7 +35,7 @@ rust-embed = { version = "8.5", features = ["debug-embed"] } # Try to use only needed features from https://github.com/slint-ui/slint/blob/master/api/rs/slint/Cargo.toml#L23-L31 #slint = { path = "/home/rafal/test/slint/api/rs/slint/", default-features = false, features = ["std", #slint = { git = "https://github.com/slint-ui/slint.git", default-features = false, features = [ -slint = { version = "1.13", default-features = false, features = [ +slint = { version = "=1.14.1", default-features = false, features = [ "std", "backend-winit", "compat-1-2" @@ -46,7 +46,7 @@ rodio = { version = "0.21.1", default-features = false, features = ["playback", [build-dependencies] #slint-build = { path = "/home/rafal/test/slint/api/rs/build/"} #slint-build = { git = "https://github.com/slint-ui/slint.git" } -slint-build = "1.13" +slint-build = "=1.14.1" [features] default = ["winit_femtovg", "winit_software"] @@ -63,6 +63,7 @@ winit_software = ["slint/renderer-winit-software"] heif = ["czkawka_core/heif"] libraw = ["czkawka_core/libraw"] libavif = ["czkawka_core/libavif"] +xdg_portal_trash = ["czkawka_core/xdg_portal_trash"] [lints] workspace = true diff --git a/krokiet/i18n/en/krokiet.ftl b/krokiet/i18n/en/krokiet.ftl index e31147aee..1c7c471dc 100644 --- a/krokiet/i18n/en/krokiet.ftl +++ b/krokiet/i18n/en/krokiet.ftl @@ -270,23 +270,22 @@ settings_use_cache = Use cache settings_save_as_json = Also save cache as JSON file settings_move_to_trash = Move deleted files to trash settings_ignore_other_filesystems = Ignore other filesystems (only Linux) +settings_delete_outdated_cache_entries = Delete automatically outdated cache entries +settings_delete_outdated_cache_entries_hint = When enabled, the app will verify during cache loading (at most once per week) whether the cached records still point to existing and unmodified files/data +settings_hide_hard_links = Hide hard links +settings_hide_hard_links_hint = Hide hard links to same files in results settings_thread_number = Thread number settings_restart_required = ---You need to restart app to apply changes in thread number--- settings_duplicate_image_preview = Image preview -settings_duplicate_hide_hard_links = Hide hard links settings_duplicate_minimal_hash_cache_size = Minimal size of cached files - Hash (KB) settings_duplicate_use_prehash = Use prehash settings_duplicate_minimal_prehash_cache_size = Minimal size of cached files - Prehash (KB) -settings_duplicate_delete_outdated_entries = Delete automatically outdated entries settings_similar_images_show_image_preview = Image preview settings_application_scale_text = Application scale settings_application_scale_hint_text = When manual scale is enabled, this allows you to choose a custom scale factor, but completely disables automatic scaling based on the monitor’s DPI. settings_restart_required_scale_text = ---You need to restart app to apply changes in scale--- settings_use_manual_application_scale_text = Use manual application scale -settings_similar_images_hide_hard_links = Hide hard links -settings_delete_outdated_entries = Delete automatically outdated entries -settings_similar_videos_hide_hard_links = Hide hard links settings_video_thumbnails_preview = Image preview settings_open_config_folder = Open config folder settings_open_cache_folder = Open cache folder @@ -305,7 +304,6 @@ settings_video_thumbnails_position = Thumbnail position in video (%) settings_video_thumbnails_generate_grid = Generate thumbnail grid instead of single image settings_video_thumbnails_generate_grid_hint = Generating multiple images in grid is a lot slower than generating single thumbnail settings_similar_images_tool = Similar Images tool -settings_similar_music_tool = Similar Music tool settings_general_settings = General Settings settings_cache_header_text = Cache Settings settings_clean_cache_button_text = Clean outdated cache diff --git a/krokiet/src/connect_scan.rs b/krokiet/src/connect_scan.rs index 1d401c6a0..ea97ef0d3 100644 --- a/krokiet/src/connect_scan.rs +++ b/krokiet/src/connect_scan.rs @@ -192,4 +192,6 @@ where component.set_exclude_other_filesystems(custom_settings.ignore_other_file_systems); component.set_use_cache(custom_settings.use_cache); component.set_save_also_as_json(custom_settings.save_also_as_json); + component.set_delete_outdated_cache(custom_settings.delete_outdated_cache_entries); + component.set_hide_hard_links(custom_settings.hide_hard_links); } diff --git a/krokiet/src/connect_scan/duplicate.rs b/krokiet/src/connect_scan/duplicate.rs index 9272d83fe..28f7988d0 100644 --- a/krokiet/src/connect_scan/duplicate.rs +++ b/krokiet/src/connect_scan/duplicate.rs @@ -26,7 +26,6 @@ pub(crate) fn scan_duplicates(a: Weak, sd: ScanData) { let params = DuplicateFinderParameters::new( check_method, hash_type, - sd.custom_settings.duplicate_hide_hard_links, sd.custom_settings.duplicate_use_prehash, sd.custom_settings.duplicate_minimal_hash_cache_size as u64, sd.custom_settings.duplicate_minimal_prehash_cache_size as u64, @@ -35,7 +34,6 @@ pub(crate) fn scan_duplicates(a: Weak, sd: ScanData) { let mut tool = DuplicateFinder::new(params); set_common_settings(&mut tool, &sd.custom_settings, &sd.stop_flag); - tool.set_delete_outdated_cache(sd.custom_settings.duplicate_delete_outdated_entries); tool.search(&sd.stop_flag, Some(&sd.progress_sender)); let (critical, messages) = get_text_messages(&tool, &sd.basic_settings); diff --git a/krokiet/src/connect_scan/similar_images.rs b/krokiet/src/connect_scan/similar_images.rs index 0e3d9eb5b..6dee5e44f 100644 --- a/krokiet/src/connect_scan/similar_images.rs +++ b/krokiet/src/connect_scan/similar_images.rs @@ -34,14 +34,11 @@ pub(crate) fn scan_similar_images(a: Weak, sd: ScanData) { hash_alg, resize_algorithm, sd.custom_settings.similar_images_sub_ignore_same_size, - sd.custom_settings.similar_images_hide_hard_links, ); let mut tool = SimilarImages::new(params); set_common_settings(&mut tool, &sd.custom_settings, &sd.stop_flag); - tool.set_delete_outdated_cache(sd.custom_settings.similar_images_delete_outdated_entries); - tool.search(&sd.stop_flag, Some(&sd.progress_sender)); let (critical, messages) = get_text_messages(&tool, &sd.basic_settings); diff --git a/krokiet/src/connect_scan/similar_videos.rs b/krokiet/src/connect_scan/similar_videos.rs index ecb650932..98f6d4ec2 100644 --- a/krokiet/src/connect_scan/similar_videos.rs +++ b/krokiet/src/connect_scan/similar_videos.rs @@ -23,7 +23,6 @@ pub(crate) fn scan_similar_videos(a: Weak, sd: ScanData) { let params = SimilarVideosParameters::new( sd.custom_settings.similar_videos_sub_similarity, sd.custom_settings.similar_videos_sub_ignore_same_size, - sd.custom_settings.similar_videos_hide_hard_links, sd.custom_settings.similar_videos_skip_forward_amount, sd.custom_settings.similar_videos_vid_hash_duration, sd.combo_box_items.videos_crop_detect.value, @@ -34,8 +33,6 @@ pub(crate) fn scan_similar_videos(a: Weak, sd: ScanData) { let mut tool = SimilarVideos::new(params); set_common_settings(&mut tool, &sd.custom_settings, &sd.stop_flag); - tool.set_delete_outdated_cache(sd.custom_settings.similar_videos_delete_outdated_entries); - tool.search(&sd.stop_flag, Some(&sd.progress_sender)); let (critical, messages) = get_text_messages(&tool, &sd.basic_settings); diff --git a/krokiet/src/connect_translation.rs b/krokiet/src/connect_translation.rs index eb7578640..aff40abd8 100644 --- a/krokiet/src/connect_translation.rs +++ b/krokiet/src/connect_translation.rs @@ -334,15 +334,14 @@ fn translate_items(app: &MainWindow) { translation.set_settings_application_scale_hint_text(flk!("settings_application_scale_hint_text").into()); translation.set_settings_restart_required_scale_text(flk!("settings_restart_required_scale_text").into()); translation.set_settings_use_manual_application_scale_text(flk!("settings_use_manual_application_scale_text").into()); - translation.set_settings_duplicate_hide_hard_links_text(flk!("settings_duplicate_hide_hard_links").into()); translation.set_settings_duplicate_minimal_hash_cache_size_text(flk!("settings_duplicate_minimal_hash_cache_size").into()); translation.set_settings_duplicate_use_prehash_text(flk!("settings_duplicate_use_prehash").into()); translation.set_settings_duplicate_minimal_prehash_cache_size_text(flk!("settings_duplicate_minimal_prehash_cache_size").into()); - translation.set_settings_duplicate_delete_outdated_entries_text(flk!("settings_duplicate_delete_outdated_entries").into()); + translation.set_settings_delete_outdated_cache_entries_text(flk!("settings_delete_outdated_cache_entries").into()); + translation.set_settings_delete_outdated_cache_entries_hint_text(flk!("settings_delete_outdated_cache_entries_hint").into()); + translation.set_settings_hide_hard_links_text(flk!("settings_hide_hard_links").into()); + translation.set_settings_hide_hard_links_hint_text(flk!("settings_hide_hard_links_hint").into()); translation.set_settings_similar_images_show_image_preview_text(flk!("settings_similar_images_show_image_preview").into()); - translation.set_settings_similar_images_hide_hard_links_text(flk!("settings_similar_images_hide_hard_links").into()); - translation.set_settings_delete_outdated_entries_text(flk!("settings_delete_outdated_entries").into()); - translation.set_settings_similar_videos_hide_hard_links_text(flk!("settings_similar_videos_hide_hard_links").into()); translation.set_settings_open_config_folder_text(flk!("settings_open_config_folder").into()); translation.set_settings_open_cache_folder_text(flk!("settings_open_cache_folder").into()); translation.set_settings_language_text(flk!("settings_language").into()); @@ -360,7 +359,6 @@ fn translate_items(app: &MainWindow) { translation.set_settings_video_thumbnails_generate_grid_text(flk!("settings_video_thumbnails_generate_grid").into()); translation.set_settings_video_thumbnails_generate_grid_hint_text(flk!("settings_video_thumbnails_generate_grid_hint").into()); translation.set_settings_similar_images_tool_text(flk!("settings_similar_images_tool").into()); - translation.set_settings_similar_music_tool_text(flk!("settings_similar_music_tool").into()); translation.set_settings_general_settings_text(flk!("settings_general_settings").into()); translation.set_settings_settings_text(flk!("settings_settings").into()); translation.set_popup_save_title_text(flk!("popup_save_title").into()); diff --git a/krokiet/src/settings/mod.rs b/krokiet/src/settings/mod.rs index 1ae7001a1..2af2636a2 100644 --- a/krokiet/src/settings/mod.rs +++ b/krokiet/src/settings/mod.rs @@ -398,21 +398,16 @@ pub(crate) fn set_settings_to_gui(app: &MainWindow, custom_settings: &SettingsCu settings.set_recursive_search(custom_settings.recursive_search); settings.set_duplicate_image_preview(custom_settings.duplicate_image_preview); - settings.set_duplicate_hide_hard_links(custom_settings.duplicate_hide_hard_links); settings.set_duplicate_use_prehash(custom_settings.duplicate_use_prehash); settings.set_duplicate_minimal_hash_cache_size(custom_settings.duplicate_minimal_hash_cache_size.to_string().into()); settings.set_duplicate_minimal_prehash_cache_size(custom_settings.duplicate_minimal_prehash_cache_size.to_string().into()); - settings.set_duplicate_delete_outdated_entries(custom_settings.duplicate_delete_outdated_entries); + settings.set_delete_outdated_cache_entries(custom_settings.delete_outdated_cache_entries); + settings.set_hide_hard_links(custom_settings.hide_hard_links); settings.set_duplicates_sub_name_case_sensitive(custom_settings.duplicates_sub_name_case_sensitive); - settings.set_similar_images_hide_hard_links(custom_settings.similar_images_hide_hard_links); settings.set_similar_images_show_image_preview(custom_settings.similar_images_show_image_preview); - settings.set_similar_images_delete_outdated_entries(custom_settings.similar_images_delete_outdated_entries); - settings.set_similar_videos_hide_hard_links(custom_settings.similar_videos_hide_hard_links); - settings.set_similar_videos_delete_outdated_entries(custom_settings.similar_videos_delete_outdated_entries); settings.set_video_thumbnails_preview(custom_settings.video_thumbnails_preview); settings.set_video_thumbnails_unused_thumbnails(custom_settings.video_thumbnails_unused_thumbnails); settings.set_similar_music_compare_fingerprints_only_with_similar_titles(custom_settings.similar_music_compare_fingerprints_only_with_similar_titles); - settings.set_similar_music_delete_outdated_entries(custom_settings.similar_music_delete_outdated_entries); set_combobox_custom_settings_items(&settings, custom_settings); @@ -571,27 +566,22 @@ pub(crate) fn collect_settings(app: &MainWindow) -> SettingsCustom { let thread_number = settings.get_thread_number().round() as i32; let duplicate_image_preview = settings.get_duplicate_image_preview(); - let duplicate_hide_hard_links = settings.get_duplicate_hide_hard_links(); let duplicate_use_prehash = settings.get_duplicate_use_prehash(); let duplicate_minimal_hash_cache_size = settings.get_duplicate_minimal_hash_cache_size().parse::().unwrap_or(DEFAULT_MINIMUM_CACHE_SIZE); let duplicate_minimal_prehash_cache_size = settings .get_duplicate_minimal_prehash_cache_size() .parse::() .unwrap_or(DEFAULT_MINIMUM_PREHASH_CACHE_SIZE); - let duplicate_delete_outdated_entries = settings.get_duplicate_delete_outdated_entries(); + let delete_outdated_cache_entries = settings.get_delete_outdated_cache_entries(); + let hide_hard_links = settings.get_hide_hard_links(); let duplicates_sub_name_case_sensitive = settings.get_duplicates_sub_name_case_sensitive(); - let similar_images_hide_hard_links = settings.get_similar_images_hide_hard_links(); let similar_images_show_image_preview = settings.get_similar_images_show_image_preview(); - let similar_images_delete_outdated_entries = settings.get_similar_images_delete_outdated_entries(); - let similar_videos_hide_hard_links = settings.get_similar_videos_hide_hard_links(); - let similar_videos_delete_outdated_entries = settings.get_similar_videos_delete_outdated_entries(); let video_thumbnails_preview = settings.get_video_thumbnails_preview(); let video_thumbnails_unused_thumbnails = settings.get_video_thumbnails_unused_thumbnails(); let similar_music_compare_fingerprints_only_with_similar_titles = settings.get_similar_music_compare_fingerprints_only_with_similar_titles(); - let similar_music_delete_outdated_entries = settings.get_similar_music_delete_outdated_entries(); let similar_images_sub_hash_size = combo_box_items.hash_size.config_name.clone(); let similar_images_sub_hash_alg = combo_box_items.image_hash_alg.config_name.clone(); @@ -707,18 +697,14 @@ pub(crate) fn collect_settings(app: &MainWindow) -> SettingsCustom { ignore_other_file_systems, thread_number, duplicate_image_preview, - duplicate_hide_hard_links, duplicate_use_prehash, duplicate_minimal_hash_cache_size, duplicate_minimal_prehash_cache_size, - duplicate_delete_outdated_entries, - similar_images_hide_hard_links, + delete_outdated_cache_entries, + hide_hard_links, similar_images_show_image_preview, - similar_images_delete_outdated_entries, - similar_videos_delete_outdated_entries, video_thumbnails_preview, video_thumbnails_unused_thumbnails, - similar_music_delete_outdated_entries, similar_images_sub_hash_size, similar_images_sub_hash_alg, similar_images_sub_resize_algorithm, @@ -729,7 +715,6 @@ pub(crate) fn collect_settings(app: &MainWindow) -> SettingsCustom { duplicates_sub_name_case_sensitive, biggest_files_sub_method, biggest_files_sub_number_of_files, - similar_videos_hide_hard_links, similar_videos_sub_ignore_same_size, similar_videos_sub_similarity, similar_music_sub_audio_check_type, diff --git a/krokiet/src/settings/model.rs b/krokiet/src/settings/model.rs index bb2a18a43..5bb7abcf5 100644 --- a/krokiet/src/settings/model.rs +++ b/krokiet/src/settings/model.rs @@ -68,29 +68,21 @@ pub struct SettingsCustom { #[serde(default = "ttrue")] pub duplicate_image_preview: bool, #[serde(default = "ttrue")] - pub duplicate_hide_hard_links: bool, - #[serde(default = "ttrue")] pub duplicate_use_prehash: bool, #[serde(default = "minimal_hash_cache_size")] pub duplicate_minimal_hash_cache_size: i32, #[serde(default = "minimal_prehash_cache_size")] pub duplicate_minimal_prehash_cache_size: i32, #[serde(default = "ttrue")] - pub duplicate_delete_outdated_entries: bool, + pub delete_outdated_cache_entries: bool, #[serde(default = "ttrue")] - pub similar_images_hide_hard_links: bool, + pub hide_hard_links: bool, #[serde(default = "ttrue")] pub similar_images_show_image_preview: bool, #[serde(default = "ttrue")] - pub similar_images_delete_outdated_entries: bool, - #[serde(default = "ttrue")] - pub similar_videos_delete_outdated_entries: bool, - #[serde(default = "ttrue")] pub video_thumbnails_preview: bool, #[serde(default = "ttrue")] pub video_thumbnails_unused_thumbnails: bool, - #[serde(default = "ttrue")] - pub similar_music_delete_outdated_entries: bool, #[serde(default = "default_sub_hash_size")] pub similar_images_sub_hash_size: String, #[serde(default = "default_hash_type")] @@ -111,8 +103,6 @@ pub struct SettingsCustom { pub biggest_files_sub_method: String, #[serde(default = "default_biggest_files")] pub biggest_files_sub_number_of_files: i32, - #[serde(default = "ttrue")] - pub similar_videos_hide_hard_links: bool, #[serde(default)] pub similar_videos_sub_ignore_same_size: bool, #[serde(default = "default_video_similarity")] diff --git a/krokiet/ui/settings.slint b/krokiet/ui/settings.slint index 24ec63575..99f989cb9 100644 --- a/krokiet/ui/settings.slint +++ b/krokiet/ui/settings.slint @@ -31,24 +31,19 @@ export global Settings { in-out property save_as_json: false; in-out property move_to_trash: false; in-out property ignore_other_filesystems: false; + in-out property delete_outdated_cache_entries: false; + in-out property hide_hard_links: false; in-out property thread_number: 4; in-out property duplicate_image_preview; - in-out property duplicate_hide_hard_links; in-out property duplicate_use_prehash; in-out property duplicate_minimal_hash_cache_size; in-out property duplicate_minimal_prehash_cache_size; - in-out property duplicate_delete_outdated_entries; in-out property similar_images_show_image_preview; - in-out property similar_images_delete_outdated_entries; - in-out property similar_images_hide_hard_links; in-out property video_thumbnails_preview; - in-out property similar_videos_delete_outdated_entries; - in-out property similar_videos_hide_hard_links; in-out property video_thumbnails_unused_thumbnails; - in-out property similar_music_delete_outdated_entries; // Video Thumbnails Global Settings in-out property video_thumbnails_generate: false; diff --git a/krokiet/ui/settings_list.slint b/krokiet/ui/settings_list.slint index c71e56e58..f82a1c116 100644 --- a/krokiet/ui/settings_list.slint +++ b/krokiet/ui/settings_list.slint @@ -387,6 +387,24 @@ export component SettingsList inherits VerticalLayout { model <=> Settings.ignore_other_filesystems; } + CheckBoxComponent { + name <=> Translations.settings_delete_outdated_cache_entries_text; + model <=> Settings.delete_outdated_cache_entries; + } + + HintText { + hint_text <=> Translations.settings_delete_outdated_cache_entries_hint_text; + } + + CheckBoxComponent { + name <=> Translations.settings_hide_hard_links_text; + model <=> Settings.hide_hard_links; + } + + HintText { + hint_text <=> Translations.settings_hide_hard_links_hint_text; + } + ThreadSliderComponent { name <=> Translations.settings_thread_number_text; maximum_number <=> GuiState.maximum_threads; @@ -409,10 +427,6 @@ export component SettingsList inherits VerticalLayout { model <=> Settings.duplicate_image_preview; } - CheckBoxComponent { - name <=> Translations.settings_duplicate_hide_hard_links_text; - model <=> Settings.duplicate_hide_hard_links; - } TextComponent { name <=> Translations.settings_duplicate_minimal_hash_cache_size_text; @@ -429,10 +443,6 @@ export component SettingsList inherits VerticalLayout { model <=> Settings.duplicate_minimal_prehash_cache_size; } - CheckBoxComponent { - name <=> Translations.settings_duplicate_delete_outdated_entries_text; - model <=> Settings.duplicate_delete_outdated_entries; - } HeaderText { text <=> Translations.settings_similar_images_tool_text; @@ -443,15 +453,7 @@ export component SettingsList inherits VerticalLayout { model <=> Settings.similar_images_show_image_preview; } - CheckBoxComponent { - name <=> Translations.settings_similar_images_hide_hard_links_text; - model <=> Settings.similar_images_hide_hard_links; - } - CheckBoxComponent { - name <=> Translations.settings_delete_outdated_entries_text; - model <=> Settings.similar_images_delete_outdated_entries; - } HeaderText { text <=> Translations.settings_similar_videos_tool_text; @@ -462,24 +464,6 @@ export component SettingsList inherits VerticalLayout { model <=> Settings.video_thumbnails_preview; } - CheckBoxComponent { - name <=> Translations.settings_similar_videos_hide_hard_links_text; - model <=> Settings.similar_videos_hide_hard_links; - } - - CheckBoxComponent { - name <=> Translations.settings_delete_outdated_entries_text; - model <=> Settings.similar_videos_delete_outdated_entries; - } - - HeaderText { - text <=> Translations.settings_similar_music_tool_text; - } - - CheckBoxComponent { - name <=> Translations.settings_delete_outdated_entries_text; - model <=> Settings.similar_music_delete_outdated_entries; - } HeaderText { text <=> Translations.settings_video_thumbnails_header_text; diff --git a/krokiet/ui/translations.slint b/krokiet/ui/translations.slint index c95e0c8fb..bdc6b45f2 100644 --- a/krokiet/ui/translations.slint +++ b/krokiet/ui/translations.slint @@ -152,6 +152,10 @@ export global Translations { in-out property settings_save_as_json_text: "Also save cache as JSON file"; in-out property settings_move_to_trash_text: "Move deleted files to trash"; in-out property settings_ignore_other_filesystems_text: "Ignore other filesystems (only Linux)"; + in-out property settings_delete_outdated_cache_entries_text: "Delete automatically outdated cache entries"; + in-out property settings_delete_outdated_cache_entries_hint_text: "When enabled, the app will verify during cache loading (at most once per week) whether the cached records still point to existing and unmodified files/data"; + in-out property settings_hide_hard_links_text: "Hide hard links"; + in-out property settings_hide_hard_links_hint_text: "Hide hard links to same files in results"; in-out property settings_thread_number_text: "Thread number"; in-out property settings_restart_required_text: "---You need to restart app to apply changes in thread number---"; in-out property settings_restart_required_scale_text: "---You need to restart app to apply changes in app scale---"; @@ -159,15 +163,10 @@ export global Translations { in-out property settings_use_manual_application_scale_text: "Apply manual application scale"; in-out property settings_application_scale_hint_text: "This may break HiDPI automatic detection and cause multi monitor related problems, so use it wisely"; in-out property settings_duplicate_image_preview_text: "Image preview"; - in-out property settings_duplicate_hide_hard_links_text: "Hide hard links"; in-out property settings_duplicate_minimal_hash_cache_size_text: "Minimal size of cached files - Hash (KB)"; in-out property settings_duplicate_use_prehash_text: "Use prehash"; in-out property settings_duplicate_minimal_prehash_cache_size_text: "Minimal size of cached files - Prehash (KB)"; - in-out property settings_duplicate_delete_outdated_entries_text: "Delete automatically outdated entries"; in-out property settings_similar_images_show_image_preview_text: "Image preview"; - in-out property settings_similar_images_hide_hard_links_text: "Hide hard links"; - in-out property settings_delete_outdated_entries_text: "Delete automatically outdated entries"; - in-out property settings_similar_videos_hide_hard_links_text: "Hide hard links"; in-out property settings_similar_videos_preview_text: "Image preview"; in-out property settings_open_config_folder_text: "Open config folder"; in-out property settings_open_cache_folder_text: "Open cache folder"; @@ -186,7 +185,6 @@ export global Translations { in-out property settings_video_thumbnails_generate_grid_text: "Generate thumbnail grid instead of single image"; in-out property settings_video_thumbnails_generate_grid_hint_text: "Generating multiple images in grid is a lot slower than generating single thumbnail"; in-out property settings_similar_images_tool_text: "Similar Images tool"; - in-out property settings_similar_music_tool_text: "Similar Music tool"; in-out property settings_general_settings_text: "General Settings"; in-out property settings_global_settings_text: "Global Settings"; in-out property settings_settings_text: "Settings"; diff --git a/misc/find_unused_fluent_translations.py b/misc/find_unused_fluent_translations.py index c7baa9a2e..cc0107e8f 100644 --- a/misc/find_unused_fluent_translations.py +++ b/misc/find_unused_fluent_translations.py @@ -71,7 +71,9 @@ def format_green(text: str) -> str: if f'"{key}"' not in rust_content: unused.append(key) if unused: - print(f"Unused keys in {ftl_file}(needs to bind to slint in connect_translations.rs file):") + print( + f"Unused keys in {ftl_file}(needs to bind to slint in connect_translations.rs file, if using krokiet, otherwise it needs to be removed from ftl file or added to code):" + ) for key in unused: print(f" {format_green(key)}") found = True