fetch essentials from url

This commit is contained in:
Julian Freeman
2026-03-30 17:45:09 -04:00
parent 8f55da1940
commit 626a9c21b0
7 changed files with 980 additions and 55 deletions

525
src-tauri/Cargo.lock generated
View File

@@ -444,6 +444,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.44"
@@ -493,6 +499,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
@@ -516,9 +532,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
dependencies = [
"bitflags 2.11.0",
"core-foundation",
"core-foundation 0.10.1",
"core-graphics-types",
"foreign-types",
"foreign-types 0.5.0",
"libc",
]
@@ -529,7 +545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [
"bitflags 2.11.0",
"core-foundation",
"core-foundation 0.10.1",
"libc",
]
@@ -852,6 +868,15 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "endi"
version = "1.1.1"
@@ -986,6 +1011,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@@ -993,7 +1027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared",
"foreign-types-shared 0.3.1",
]
[[package]]
@@ -1007,6 +1041,12 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
@@ -1252,8 +1292,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@@ -1263,9 +1305,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi 5.3.0",
"wasip2",
"wasm-bindgen",
]
[[package]]
@@ -1429,6 +1473,25 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "h2"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap 2.13.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1545,6 +1608,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"httparse",
@@ -1556,6 +1620,39 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
@@ -1574,9 +1671,11 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
@@ -1982,6 +2081,12 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "mac"
version = "0.1.1"
@@ -2093,6 +2198,23 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "native-tls"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ndk"
version = "0.9.0"
@@ -2313,6 +2435,50 @@ dependencies = [
"pathdiff",
]
[[package]]
name = "openssl"
version = "0.10.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
dependencies = [
"bitflags 2.11.0",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
version = "0.9.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "option-ext"
version = "0.2.0"
@@ -2768,6 +2934,61 @@ dependencies = [
"memchr",
]
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand 0.9.2",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
]
[[package]]
name = "quote"
version = "1.0.45"
@@ -2814,6 +3035,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -2834,6 +3065,16 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -2852,6 +3093,15 @@ dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -2945,6 +3195,50 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
]
[[package]]
name = "reqwest"
version = "0.13.2"
@@ -2979,6 +3273,20 @@ dependencies = [
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -3007,12 +3315,53 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"web-time",
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "same-file"
version = "1.0.6"
@@ -3022,6 +3371,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "schemars"
version = "0.8.22"
@@ -3079,6 +3437,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags 2.11.0",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "selectors"
version = "0.24.0"
@@ -3221,6 +3602,18 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_with"
version = "3.18.0"
@@ -3469,6 +3862,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "swift-rs"
version = "1.0.7"
@@ -3522,6 +3921,27 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags 2.11.0",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "system-deps"
version = "6.2.2"
@@ -3543,7 +3963,7 @@ checksum = "6e06d52c379e63da659a483a958110bbde891695a0ecb53e48cc7786d5eda7bb"
dependencies = [
"bitflags 2.11.0",
"block2",
"core-foundation",
"core-foundation 0.10.1",
"core-graphics",
"crossbeam-channel",
"dispatch2",
@@ -3620,7 +4040,7 @@ dependencies = [
"percent-encoding",
"plist",
"raw-window-handle",
"reqwest",
"reqwest 0.13.2",
"serde",
"serde_json",
"serde_repr",
@@ -3948,6 +4368,21 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.50.0"
@@ -3976,6 +4411,26 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
@@ -4280,6 +4735,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
@@ -4329,6 +4790,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.2.1"
@@ -4526,6 +4993,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "web_atoms"
version = "0.2.3"
@@ -4582,6 +5059,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-roots"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webview2-com"
version = "0.38.2"
@@ -4624,6 +5110,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"regex",
"reqwest 0.12.28",
"serde",
"serde_json",
"tauri",
@@ -4781,6 +5268,17 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-result"
version = "0.3.4"
@@ -4826,6 +5324,15 @@ dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -5369,6 +5876,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.3"

View File

@@ -25,4 +25,5 @@ serde_json = "1"
tokio = { version = "1.50.0", features = ["full"] }
chrono = "0.4.44"
regex = "1.12.3"
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }

View File

@@ -4,12 +4,26 @@ use std::fs;
use std::process::{Command, Stdio};
use std::os::windows::process::CommandExt;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use tokio::sync::mpsc;
use tauri::{AppHandle, Manager, State, Emitter};
use serde::Serialize;
use serde::{Serialize, Deserialize};
use winget::{Software, list_all_software, list_updates, ensure_winget_dependencies};
use regex::Regex;
#[derive(Clone, Serialize, Deserialize)]
pub struct AppSettings {
pub repo_url: String,
}
impl Default for AppSettings {
fn default() -> Self {
Self {
repo_url: "https://karlblue.github.io/winget-repo".to_string(),
}
}
}
struct AppState {
install_tx: mpsc::Sender<String>,
}
@@ -34,46 +48,94 @@ pub fn emit_log(handle: &AppHandle, id: &str, command: &str, output: &str, statu
});
}
fn get_settings_path(app: &AppHandle) -> PathBuf {
let app_data_dir = app.path().app_data_dir().unwrap_or_default();
if !app_data_dir.exists() {
let _ = fs::create_dir_all(&app_data_dir);
}
app_data_dir.join("settings.json")
}
fn get_essentials_path(app: &AppHandle) -> PathBuf {
let app_data_dir = app.path().app_data_dir().unwrap_or_default();
if !app_data_dir.exists() {
let _ = fs::create_dir_all(&app_data_dir);
}
app_data_dir.join("setup-essentials.json")
}
#[tauri::command]
fn get_settings(app: AppHandle) -> AppSettings {
let path = get_settings_path(&app);
if !path.exists() {
let default_settings = AppSettings::default();
let _ = fs::write(&path, serde_json::to_string_pretty(&default_settings).unwrap());
return default_settings;
}
let content = fs::read_to_string(path).unwrap_or_default();
serde_json::from_str(&content).unwrap_or_default()
}
#[tauri::command]
fn save_settings(app: AppHandle, settings: AppSettings) -> Result<(), String> {
let path = get_settings_path(&app);
let content = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?;
fs::write(path, content).map_err(|e| e.to_string())
}
#[tauri::command]
async fn sync_essentials(app: AppHandle) -> Result<bool, String> {
let settings = get_settings(app.clone());
let url = format!("{}/setup-essentials.json", settings.repo_url.trim_end_matches('/'));
emit_log(&app, "sync-essentials", "Syncing Essentials", &format!("Downloading from {}...", url), "info");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| e.to_string())?;
match client.get(&url).send().await {
Ok(response) => {
if response.status().is_success() {
let content = response.text().await.map_err(|e| e.to_string())?;
// 验证 JSON 格式
let validation: Result<Vec<Software>, _> = serde_json::from_str(&content);
if validation.is_ok() {
let path = get_essentials_path(&app);
fs::write(path, content).map_err(|e| e.to_string())?;
emit_log(&app, "sync-essentials", "Result", "Essentials list updated successfully.", "success");
Ok(true)
} else {
emit_log(&app, "sync-essentials", "Error", "Invalid JSON format from repository.", "error");
Err("Invalid JSON format".to_string())
}
} else {
let err_msg = format!("HTTP Error: {}", response.status());
emit_log(&app, "sync-essentials", "Error", &err_msg, "error");
Err(err_msg)
}
}
Err(e) => {
emit_log(&app, "sync-essentials", "Skipped", &format!("Network issue: {}. Using local cache.", e), "info");
Ok(false) // 静默失败,返回 false 表示未更新但可继续
}
}
}
#[tauri::command]
async fn initialize_app(app: AppHandle) -> Result<bool, String> {
let app_clone = app.clone();
tokio::task::spawn_blocking(move || {
ensure_winget_dependencies(&app).map(|_| true)
ensure_winget_dependencies(&app_clone).map(|_| true)
}).await.unwrap_or(Err("Initialization Task Panicked".to_string()))
}
#[tauri::command]
fn get_essentials(app: AppHandle) -> Vec<Software> {
let app_data_dir = app.path().app_data_dir().unwrap_or_default();
if !app_data_dir.exists() {
let _ = fs::create_dir_all(&app_data_dir);
}
let file_path = app_data_dir.join("setup-essentials.json");
let file_path = get_essentials_path(&app);
if !file_path.exists() {
let default_essentials = vec![
Software {
id: "Microsoft.PowerToys".to_string(),
name: "PowerToys".to_string(),
description: Some("Microsoft PowerToys 是一组实用程序,供高级用户调整和简化其 Windows 10 和 11 体验。".to_string()),
version: None,
available_version: None,
icon_url: Some("https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/logo.png".to_string()),
status: "idle".to_string(),
progress: 0.0,
},
Software {
id: "Google.Chrome".to_string(),
name: "Google Chrome".to_string(),
description: Some("Google Chrome 是一款快速、安全且免费的浏览器。".to_string()),
version: None,
available_version: None,
icon_url: Some("https://www.google.com/chrome/static/images/chrome-logo.svg".to_string()),
status: "idle".to_string(),
progress: 0.0,
}
];
let _ = fs::write(&file_path, serde_json::to_string_pretty(&default_essentials).unwrap());
return default_essentials;
return vec![];
}
let content = fs::read_to_string(file_path).unwrap_or_else(|_| "[]".to_string());
@@ -210,6 +272,9 @@ pub fn run() {
})
.invoke_handler(tauri::generate_handler![
initialize_app,
get_settings,
save_settings,
sync_essentials,
get_essentials,
get_all_software,
get_updates,

View File

@@ -34,19 +34,30 @@
全部软件
</router-link>
<!-- 底部日志选项 -->
<router-link to="/logs" class="nav-item nav-logs">
<span class="nav-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
</span>
运行日志
</router-link>
<!-- 底部选项 -->
<div class="sidebar-footer">
<router-link to="/logs" class="nav-item nav-logs">
<span class="nav-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
</span>
运行日志
</router-link>
<router-link to="/settings" class="nav-item nav-settings">
<span class="nav-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1Z"></path>
</svg>
</span>
软件设置
</router-link>
</div>
</nav>
</div>
</template>
@@ -106,8 +117,15 @@
justify-content: center;
}
.nav-logs {
margin-top: auto; /* 将日志推到底部 */
margin-bottom: 20px;
.sidebar-footer {
margin-top: auto;
padding-bottom: 20px;
display: flex;
flex-direction: column;
gap: 8px;
}
.nav-logs, .nav-settings {
margin-top: 0;
}
</style>

View File

@@ -22,6 +22,10 @@ const router = createRouter({
{
path: '/logs',
component: () => import('../views/Logs.vue')
},
{
path: '/settings',
component: () => import('../views/Settings.vue')
}
]
})

View File

@@ -20,12 +20,16 @@ export const useSoftwareStore = defineStore('software', {
selectedEssentialIds: [] as string[],
selectedUpdateIds: [] as string[],
logs: [] as LogEntry[],
settings: {
repo_url: 'https://karlblue.github.io/winget-repo'
},
loading: false,
isInitialized: false,
initStatus: '正在检查系统环境...',
lastFetched: 0
}),
getters: {
// ... (mergedEssentials, sortedUpdates, sortedAllSoftware, isBusy getters stay the same)
mergedEssentials: (state) => {
return state.essentials.map(item => {
const isInstalled = state.allSoftware.some(s => s.id.toLowerCase() === item.id.toLowerCase());
@@ -55,8 +59,10 @@ export const useSoftwareStore = defineStore('software', {
actions: {
async initializeApp() {
if (this.isInitialized) return;
this.initStatus = '正在同步 Winget 模块...';
this.initStatus = '正在加载应用配置...';
try {
this.settings = await invoke('get_settings');
this.initStatus = '正在同步 Winget 模块...';
await invoke('initialize_app');
this.isInitialized = true;
} catch (err) {
@@ -64,6 +70,23 @@ export const useSoftwareStore = defineStore('software', {
setTimeout(() => { this.isInitialized = true; }, 2000);
}
},
async saveSettings(newSettings: any) {
await invoke('save_settings', { settings: newSettings });
this.settings = newSettings;
},
async syncEssentials() {
this.loading = true;
try {
await invoke('sync_essentials');
await this.fetchEssentials();
} finally {
this.loading = false;
}
},
// ... (Selection methods stay the same)
toggleSelection(id: string, type: 'essential' | 'update') {
if (this.isBusy) return;
const list = type === 'essential' ? this.selectedEssentialIds : this.selectedUpdateIds;
@@ -130,6 +153,9 @@ export const useSoftwareStore = defineStore('software', {
async fetchAllData() {
this.loading = true;
try {
// 在获取全量数据之前,先同步云端清单
await invoke('sync_essentials').catch(() => {});
const [essentials, all, updates] = await Promise.all([
invoke('get_essentials'),
invoke('get_all_software'),

298
src/views/Settings.vue Normal file
View File

@@ -0,0 +1,298 @@
<template>
<div class="settings-view">
<header class="view-header">
<h1>软件设置</h1>
<p class="subtitle">管理仓库地址与应用配置</p>
</header>
<div class="settings-content">
<section class="settings-section">
<h3 class="section-title">仓库配置</h3>
<div class="settings-card">
<div class="setting-item">
<div class="setting-info">
<label>仓库地址</label>
<p>应用将从该地址同步装机必备软件清单</p>
</div>
<div class="setting-action">
<input
type="text"
v-model="tempRepoUrl"
placeholder="https://..."
class="settings-input"
/>
</div>
</div>
<div class="setting-footer">
<button
@click="handleSave"
class="btn-primary"
:disabled="isSaving || !isValidUrl"
>
{{ isSaving ? '正在保存...' : '保存配置' }}
</button>
<button
@click="handleSync"
class="btn-secondary"
:disabled="store.loading"
>
<span v-if="store.loading" class="spinner"></span>
{{ store.loading ? '正在同步...' : '立即同步清单' }}
</button>
</div>
</div>
</section>
<section class="settings-section">
<h3 class="section-title">关于</h3>
<div class="settings-card about-card">
<p>Windows 软件管理器 v0.1.0</p>
<p class="hint">基于 Tauri WinGet 构建的现代化软件管理工具</p>
</div>
</section>
</div>
<!-- 通知提示 -->
<transition name="toast">
<div v-if="toast" class="toast" :class="toast.type">
{{ toast.message }}
</div>
</transition>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useSoftwareStore } from '../store/software'
const store = useSoftwareStore()
const tempRepoUrl = ref('')
const isSaving = ref(false)
const toast = ref<{ message: string, type: 'success' | 'error' } | null>(null)
const isValidUrl = computed(() => {
try {
new URL(tempRepoUrl.value)
return true
} catch {
return false
}
})
onMounted(() => {
tempRepoUrl.value = store.settings.repo_url
})
const showToast = (message: string, type: 'success' | 'error' = 'success') => {
toast.value = { message, type }
setTimeout(() => {
toast.value = null
}, 3000)
}
const handleSave = async () => {
if (!isValidUrl.value) return
isSaving.value = true
try {
await store.saveSettings({ repo_url: tempRepoUrl.value })
showToast('配置已保存')
} catch (err) {
showToast('保存失败: ' + err, 'error')
} finally {
isSaving.value = false
}
}
const handleSync = async () => {
try {
await store.syncEssentials()
showToast('清单同步成功')
} catch (err) {
showToast('同步失败,请检查网络或地址', 'error')
}
}
</script>
<style scoped>
.settings-view {
padding: 60px;
height: 100%;
overflow-y: auto;
background-color: var(--bg-light);
}
.view-header {
margin-bottom: 40px;
}
.view-header h1 {
font-size: 34px;
font-weight: 700;
letter-spacing: -0.5px;
margin-bottom: 8px;
}
.subtitle {
color: var(--text-sec);
font-size: 17px;
}
.settings-content {
max-width: 800px;
}
.settings-section {
margin-bottom: 40px;
}
.section-title {
font-size: 13px;
font-weight: 600;
text-transform: uppercase;
color: var(--text-sec);
margin-bottom: 12px;
padding-left: 4px;
}
.settings-card {
background: white;
border-radius: var(--radius-card);
padding: 24px;
box-shadow: var(--card-shadow);
border: 1px solid var(--border-color);
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.setting-info label {
display: block;
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.setting-info p {
font-size: 14px;
color: var(--text-sec);
}
.settings-input {
width: 320px;
padding: 12px 16px;
border-radius: 10px;
border: 1px solid var(--border-color);
font-size: 14px;
transition: all 0.2s ease;
background-color: #f5f5f7;
}
.settings-input:focus {
outline: none;
border-color: var(--primary-color);
background-color: white;
box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.1);
}
.setting-footer {
display: flex;
gap: 12px;
border-top: 1px solid var(--border-color);
padding-top: 24px;
}
.btn-primary, .btn-secondary {
padding: 10px 20px;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
border: none;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: var(--primary-hover);
transform: translateY(-1px);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
background-color: #f5f5f7;
color: var(--text-main);
}
.btn-secondary:hover:not(:disabled) {
background-color: #e5e5e7;
}
.about-card p {
margin-bottom: 4px;
font-weight: 600;
}
.hint {
font-size: 14px;
color: var(--text-sec);
font-weight: 400 !important;
}
/* Toast */
.toast {
position: fixed;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
padding: 12px 24px;
border-radius: 12px;
background: #323232;
color: white;
font-size: 14px;
font-weight: 500;
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
z-index: 1000;
}
.toast.error {
background: #ff3b30;
}
.toast-enter-active, .toast-leave-active {
transition: all 0.3s ease;
}
.toast-enter-from, .toast-leave-to {
opacity: 0;
transform: translate(-50%, 20px);
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(0,0,0,0.1);
border-top-color: var(--text-main);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>