fix: Add Windows compatibility for file operations
- Fix home directory detection using BaseDirs from directories crate - Add cross-platform file moving with copy+delete fallback - Replace Unix-specific HOME env var usage - Handle cross-filesystem moves on Windows automatically
This commit is contained in:
372
Cargo.lock
generated
372
Cargo.lock
generated
@@ -2,6 +2,56 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -57,6 +107,52 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "3.0.0"
|
||||
@@ -111,6 +207,27 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
@@ -122,18 +239,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
@@ -348,6 +453,12 @@ version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@@ -602,13 +713,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
@@ -632,6 +740,16 @@ version = "0.2.178"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
@@ -703,18 +821,18 @@ dependencies = [
|
||||
name = "noentropy"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"colored",
|
||||
"dotenv",
|
||||
"directories",
|
||||
"futures",
|
||||
"hex",
|
||||
"itertools",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"walkdir",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -723,6 +841,12 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.75"
|
||||
@@ -767,6 +891,12 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -856,6 +986,17 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.26"
|
||||
@@ -968,15 +1109,6 @@ version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.28"
|
||||
@@ -1058,6 +1190,15 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@@ -1124,6 +1265,12 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -1195,13 +1342,33 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1286,6 +1453,47 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
@@ -1392,6 +1600,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
@@ -1404,16 +1618,6 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
@@ -1506,15 +1710,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
@@ -1550,6 +1745,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -1577,6 +1781,21 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -1610,6 +1829,12 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -1622,6 +1847,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -1634,6 +1865,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -1658,6 +1895,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -1670,6 +1913,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -1682,6 +1931,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -1694,6 +1949,12 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -1706,6 +1967,15 @@ version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
|
||||
@@ -4,15 +4,15 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
colored = "3.0.0"
|
||||
dotenv = "0.15.0"
|
||||
directories = "5.0.1"
|
||||
futures = "0.3.31"
|
||||
hex = "0.4.3"
|
||||
itertools = "0.14.0"
|
||||
reqwest = { version = "0.12.26", features = ["json"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "2.0.11"
|
||||
tokio = { version = "1.48.0", features = ["full"] }
|
||||
walkdir = "2.5.0"
|
||||
toml = "0.8.19"
|
||||
|
||||
271
src/config.rs
Normal file
271
src/config.rs
Normal file
@@ -0,0 +1,271 @@
|
||||
use colored::*;
|
||||
use directories::{BaseDirs, ProjectDirs};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const MAX_RETRIES: u32 = 3;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub api_key: String,
|
||||
pub download_folder: PathBuf,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn get_config_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
if let Some(proj_dirs) = ProjectDirs::from("dev", "noentropy", "NoEntropy") {
|
||||
let config_dir = proj_dirs.config_dir().to_path_buf();
|
||||
fs::create_dir_all(&config_dir)?;
|
||||
Ok(config_dir)
|
||||
} else {
|
||||
Err("Failed to determine config directory".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
Ok(Self::get_config_dir()?.join("config.toml"))
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Config, Box<dyn std::error::Error>> {
|
||||
let config_path = Self::get_config_path()?;
|
||||
|
||||
if !config_path.exists() {
|
||||
return Err("Config file not found".into());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&config_path)?;
|
||||
let config: Config = toml::from_str(&content)?;
|
||||
|
||||
if config.api_key.is_empty() {
|
||||
return Err("API key not found in config file".into());
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config_path = Self::get_config_path()?;
|
||||
|
||||
let toml_string = toml::to_string_pretty(self)?;
|
||||
|
||||
fs::write(&config_path, toml_string)?;
|
||||
|
||||
println!(
|
||||
"{} Configuration saved to {}",
|
||||
"✓".green(),
|
||||
config_path.display().to_string().yellow()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_api_key() -> Result<String, Box<dyn std::error::Error>> {
|
||||
match Self::load() {
|
||||
Ok(config) => Ok(config.api_key),
|
||||
Err(_) => Err("API key not configured".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_download_folder() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
match Self::load() {
|
||||
Ok(config) => Ok(config.download_folder),
|
||||
Err(_) => Err("Download folder not configured".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
|
||||
if let Ok(config) = Config::load() {
|
||||
if !config.api_key.is_empty() {
|
||||
return Ok(config.api_key);
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "🔑 NoEntropy Configuration".bold().cyan());
|
||||
println!("{}", "─────────────────────────────".cyan());
|
||||
|
||||
let api_key = prompt_api_key()?;
|
||||
|
||||
let mut config = if let Ok(cfg) = Config::load() {
|
||||
cfg
|
||||
} else {
|
||||
Config {
|
||||
api_key: api_key.clone(),
|
||||
download_folder: PathBuf::new(),
|
||||
}
|
||||
};
|
||||
|
||||
config.api_key = api_key.clone();
|
||||
config.save()?;
|
||||
|
||||
println!();
|
||||
Ok(api_key)
|
||||
}
|
||||
|
||||
pub fn get_or_prompt_download_folder() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
if let Ok(config) = Config::load() {
|
||||
if !config.download_folder.as_os_str().is_empty() && config.download_folder.exists() {
|
||||
return Ok(config.download_folder);
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("{}", "📁 Download folder not configured.".yellow());
|
||||
|
||||
let folder_path = prompt_download_folder()?;
|
||||
|
||||
let mut config = if let Ok(cfg) = Config::load() {
|
||||
cfg
|
||||
} else {
|
||||
Config {
|
||||
api_key: String::new(),
|
||||
download_folder: folder_path.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
config.download_folder = folder_path.clone();
|
||||
config.save()?;
|
||||
|
||||
println!();
|
||||
Ok(folder_path)
|
||||
}
|
||||
|
||||
fn prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut attempts = 0;
|
||||
|
||||
println!();
|
||||
println!("Get your API key at: {}", "https://ai.google.dev/".cyan().underline());
|
||||
println!("Enter your API Key (starts with 'AIza'):");
|
||||
|
||||
while attempts < MAX_RETRIES {
|
||||
print!("API Key: ");
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
let key = input.trim();
|
||||
|
||||
if validate_api_key(key) {
|
||||
return Ok(key.to_string());
|
||||
}
|
||||
|
||||
attempts += 1;
|
||||
|
||||
let remaining = MAX_RETRIES - attempts;
|
||||
eprintln!(
|
||||
"{} Invalid API key format. Must start with 'AIza' and be around 39 characters.",
|
||||
"✗".red()
|
||||
);
|
||||
|
||||
if remaining > 0 {
|
||||
eprintln!("Try again ({} attempts remaining):", remaining);
|
||||
}
|
||||
}
|
||||
|
||||
Err("Max retries exceeded. Please run again with a valid API key.".into())
|
||||
}
|
||||
|
||||
fn prompt_download_folder() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
let default_path = get_default_downloads_folder();
|
||||
let default_display = default_path.to_string_lossy();
|
||||
|
||||
let mut attempts = 0;
|
||||
|
||||
println!(
|
||||
"Enter path to folder to organize (e.g., {}):",
|
||||
default_display.yellow()
|
||||
);
|
||||
println!("Or press Enter to use default: {}", default_display.green());
|
||||
println!("Folder path: ");
|
||||
|
||||
while attempts < MAX_RETRIES {
|
||||
print!("> ");
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
let input = input.trim();
|
||||
|
||||
let path = if input.is_empty() {
|
||||
default_path.clone()
|
||||
} else {
|
||||
let expanded = expand_home(input);
|
||||
PathBuf::from(expanded)
|
||||
};
|
||||
|
||||
if validate_folder_path(&path) {
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
attempts += 1;
|
||||
|
||||
let remaining = MAX_RETRIES - attempts;
|
||||
eprintln!("{} Invalid folder path.", "✗".red());
|
||||
|
||||
if !path.exists() {
|
||||
eprintln!(" Path does not exist: {}", path.display());
|
||||
} else if !path.is_dir() {
|
||||
eprintln!(" Path is not a directory: {}", path.display());
|
||||
}
|
||||
|
||||
if remaining > 0 {
|
||||
eprintln!("Try again ({} attempts remaining):", remaining);
|
||||
println!("Folder path: ");
|
||||
}
|
||||
}
|
||||
|
||||
Err("Max retries exceeded. Please run again with a valid folder path.".into())
|
||||
}
|
||||
|
||||
fn validate_api_key(key: &str) -> bool {
|
||||
if key.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !key.starts_with("AIza") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if key.len() < 35 || key.len() > 50 {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn validate_folder_path(path: &Path) -> bool {
|
||||
if !path.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !path.is_dir() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn get_default_downloads_folder() -> PathBuf {
|
||||
if let Some(base_dirs) = BaseDirs::new() {
|
||||
let home = base_dirs.home_dir();
|
||||
return home.join("Downloads");
|
||||
}
|
||||
|
||||
PathBuf::from("./Downloads")
|
||||
}
|
||||
|
||||
fn expand_home(path: &str) -> String {
|
||||
if path.starts_with("~/")
|
||||
&& let Some(base_dirs) = BaseDirs::new()
|
||||
{
|
||||
let home = base_dirs.home_dir();
|
||||
return path.replacen("~", &home.to_string_lossy(), 1);
|
||||
}
|
||||
|
||||
path.to_string()
|
||||
}
|
||||
151
src/files.rs
151
src/files.rs
@@ -2,7 +2,6 @@ use colored::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::{ffi::OsStr, fs, path::Path, path::PathBuf};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct FileCategory {
|
||||
@@ -25,28 +24,54 @@ impl FileBatch {
|
||||
pub fn from_path(root_path: PathBuf) -> Self {
|
||||
let mut filenames = Vec::new();
|
||||
let mut paths = Vec::new();
|
||||
for entry in WalkDir::new(&root_path)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.path().is_file())
|
||||
{
|
||||
if let Ok(relative_path) = entry.path().strip_prefix(&root_path) {
|
||||
|
||||
let entries = match fs::read_dir(&root_path) {
|
||||
Ok(entries) => entries,
|
||||
Err(e) => {
|
||||
eprintln!("Error reading directory {:?}: {}", root_path, e);
|
||||
return FileBatch {
|
||||
filenames: Vec::new(),
|
||||
paths: Vec::new(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file()
|
||||
&& let Ok(relative_path) = path.strip_prefix(&root_path) {
|
||||
filenames.push(relative_path.to_string_lossy().into_owned());
|
||||
paths.push(entry.path().to_path_buf());
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
FileBatch { filenames, paths }
|
||||
}
|
||||
|
||||
/// Helper to get the number of files found
|
||||
pub fn count(&self) -> usize {
|
||||
self.filenames.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Move a file with cross-platform compatibility
|
||||
/// Tries rename first (fastest), falls back to copy+delete if needed (e.g., cross-filesystem on Windows)
|
||||
fn move_file_cross_platform(source: &Path, target: &Path) -> io::Result<()> {
|
||||
match fs::rename(source, target) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
if cfg!(windows) || e.kind() == io::ErrorKind::CrossesDevices {
|
||||
fs::copy(source, target)?;
|
||||
fs::remove_file(source)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
// ---------------------------------------------------------
|
||||
// PHASE 1: PREVIEW (Show the plan)
|
||||
// ---------------------------------------------------------
|
||||
println!("\n{}", "--- EXECUTION PLAN ---".bold().underline());
|
||||
|
||||
if plan.files.is_empty() {
|
||||
@@ -54,7 +79,6 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate by reference (&) so we don't consume the data yet
|
||||
for item in &plan.files {
|
||||
let mut target_display = format!("{}", item.category.green());
|
||||
if !item.sub_category.is_empty() {
|
||||
@@ -64,9 +88,6 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
println!("Plan: {} -> {}/", item.filename, target_display);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// PHASE 2: PROMPT (Ask for permission)
|
||||
// ---------------------------------------------------------
|
||||
eprint!("\nDo you want to apply these changes? [y/N]: ");
|
||||
|
||||
let mut input = String::new();
|
||||
@@ -74,27 +95,25 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
.read_line(&mut input)
|
||||
.is_err()
|
||||
{
|
||||
println!("\n{}", "Failed to read input. Operation cancelled.".red());
|
||||
eprintln!("\n{}", "Failed to read input. Operation cancelled.".red());
|
||||
return;
|
||||
}
|
||||
|
||||
let input = input.trim().to_lowercase();
|
||||
|
||||
// If input is not "y" or "yes", abort.
|
||||
if input != "y" && input != "yes" {
|
||||
println!("\n{}", "Operation cancelled.".red());
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// PHASE 3: EXECUTION (Actually move files)
|
||||
// ---------------------------------------------------------
|
||||
println!("\n{}", "--- MOVING FILES ---".bold().underline());
|
||||
|
||||
let mut moved_count = 0;
|
||||
let mut error_count = 0;
|
||||
|
||||
for item in plan.files {
|
||||
let source = base_path.join(&item.filename);
|
||||
|
||||
// Logic: Destination / Parent Category / Sub Category
|
||||
let mut final_path = base_path.join(&item.category);
|
||||
|
||||
if !item.sub_category.is_empty() {
|
||||
@@ -109,25 +128,27 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
|
||||
let target = final_path.join(&file_name);
|
||||
|
||||
// 1. Create the category/sub-category folder
|
||||
// (Only need to call this once per file path)
|
||||
if let Err(e) = fs::create_dir_all(&final_path) {
|
||||
println!(
|
||||
eprintln!(
|
||||
"{} Failed to create dir {:?}: {}",
|
||||
"ERROR:".red(),
|
||||
final_path,
|
||||
e
|
||||
);
|
||||
continue; // Skip moving this file if we can't make the folder
|
||||
error_count += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. Move the file
|
||||
if source.exists() {
|
||||
match fs::rename(&source, &target) {
|
||||
if let Ok(metadata) = fs::metadata(&source) {
|
||||
if metadata.is_file() {
|
||||
match move_file_cross_platform(&source, &target) {
|
||||
Ok(_) => {
|
||||
// Formatting the success message
|
||||
if item.sub_category.is_empty() {
|
||||
println!("Moved: {} -> {}/", item.filename, item.category.green());
|
||||
println!(
|
||||
"Moved: {} -> {}/",
|
||||
item.filename,
|
||||
item.category.green()
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Moved: {} -> {}/{}",
|
||||
@@ -136,30 +157,82 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
item.sub_category.blue()
|
||||
);
|
||||
}
|
||||
moved_count += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{} Failed to move {}: {}", "ERROR:".red(), item.filename, e);
|
||||
error_count += 1;
|
||||
}
|
||||
Err(e) => println!("{} Failed to move {}: {}", "ERROR:".red(), item.filename, e),
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"{} Skipping {}: File not found",
|
||||
eprintln!(
|
||||
"{} Skipping {}: Not a file",
|
||||
"WARN:".yellow(),
|
||||
item.filename
|
||||
);
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"{} Skipping {}: File not found",
|
||||
"WARN:".yellow(),
|
||||
item.filename
|
||||
);
|
||||
error_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n{}", "Organization Complete!".bold().green());
|
||||
} // --- 1. Helper to check if file is likely text ---
|
||||
pub fn is_text_file(path: &Path) -> bool {
|
||||
println!(
|
||||
"Files moved: {}, Errors: {}",
|
||||
moved_count.to_string().green(),
|
||||
error_count.to_string().red()
|
||||
);
|
||||
} pub fn is_text_file(path: &Path) -> bool {
|
||||
let text_extensions = [
|
||||
"txt", "md", "rs", "py", "js", "html", "css", "json", "xml", "csv",
|
||||
"txt",
|
||||
"md",
|
||||
"rs",
|
||||
"py",
|
||||
"js",
|
||||
"ts",
|
||||
"jsx",
|
||||
"tsx",
|
||||
"html",
|
||||
"css",
|
||||
"json",
|
||||
"xml",
|
||||
"csv",
|
||||
"yaml",
|
||||
"yml",
|
||||
"toml",
|
||||
"ini",
|
||||
"cfg",
|
||||
"conf",
|
||||
"log",
|
||||
"sh",
|
||||
"bat",
|
||||
"ps1",
|
||||
"sql",
|
||||
"c",
|
||||
"cpp",
|
||||
"h",
|
||||
"hpp",
|
||||
"java",
|
||||
"go",
|
||||
"rb",
|
||||
"php",
|
||||
"swift",
|
||||
"kt",
|
||||
"scala",
|
||||
"lua",
|
||||
"r",
|
||||
"m",
|
||||
];
|
||||
|
||||
if let Some(ext) = path.extension() {
|
||||
if let Some(ext_str) = ext.to_str() {
|
||||
if let Some(ext) = path.extension()
|
||||
&& let Some(ext_str) = ext.to_str() {
|
||||
return text_extensions.contains(&ext_str.to_lowercase().as_str());
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user