From 1093f6d2f3abcc4831c188d45e5c3cb9902d8aad Mon Sep 17 00:00:00 2001 From: unsecretised Date: Sat, 25 Apr 2026 02:26:25 +0800 Subject: [PATCH] add auto updating --- Cargo.lock | 547 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 + src/app.rs | 1 + src/app/pages/settings.rs | 14 + src/app/tile.rs | 43 +-- src/app/tile/update.rs | 12 + src/autoupdate.rs | 222 ++++++++++++++++ src/config.rs | 2 + src/main.rs | 1 + 9 files changed, 805 insertions(+), 41 deletions(-) create mode 100644 src/autoupdate.rs diff --git a/Cargo.lock b/Cargo.lock index 59d8cc8..63fbcaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "ahash" version = "0.8.12" @@ -454,6 +465,24 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -535,6 +564,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -651,6 +689,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -726,6 +774,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "core-foundation" version = "0.9.4" @@ -829,6 +889,24 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -891,6 +969,25 @@ dependencies = [ "wgpu", ] +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctor-lite" version = "0.1.2" @@ -913,6 +1010,43 @@ dependencies = [ "byteorder", ] +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.1", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1191,6 +1325,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide 0.8.9", + "zlib-rs", ] [[package]] @@ -1432,6 +1567,16 @@ dependencies = [ "system-deps", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "1.1.0" @@ -1461,10 +1606,25 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", + "wasm-bindgen", +] + [[package]] name = "gif" version = "0.14.1" @@ -1806,6 +1966,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +dependencies = [ + "typenum", +] + [[package]] name = "iced" version = "0.14.0" @@ -2085,6 +2263,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -2154,6 +2338,17 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", ] [[package]] @@ -2277,6 +2472,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lebe" version = "0.5.3" @@ -2307,6 +2508,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" + [[package]] name = "libc" version = "0.2.182" @@ -2448,6 +2655,15 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +[[package]] +name = "lzma-rust2" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" +dependencies = [ + "sha2 0.10.9", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2714,6 +2930,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + [[package]] name = "num-derive" version = "0.4.2" @@ -3287,6 +3509,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3442,6 +3674,18 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -3457,6 +3701,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -3586,6 +3840,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.9.2" @@ -3858,6 +4118,7 @@ dependencies = [ "crossbeam-channel", "emojis", "global-hotkey", + "hex", "iced", "icns", "image", @@ -3876,11 +4137,14 @@ dependencies = [ "rfd", "serde", "serde_json", + "sha2 0.11.0", + "tempfile", "tokio", "toml 0.9.12+spec-1.1.0", "tracing-subscriber", "tray-icon", "url", + "zip", ] [[package]] @@ -4065,6 +4329,39 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -4282,6 +4579,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "svg_fmt" version = "0.4.5" @@ -4444,6 +4747,26 @@ dependencies = [ "zune-jpeg 0.4.21", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "js-sys", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + [[package]] name = "tiny-skia" version = "0.11.4" @@ -4723,6 +5046,18 @@ dependencies = [ "core_maths", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + [[package]] name = "uds_windows" version = "1.2.1" @@ -4770,6 +5105,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -4858,6 +5199,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.113" @@ -4917,6 +5267,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "wasmtimer" version = "0.4.3" @@ -5774,6 +6158,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -5997,6 +6463,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" @@ -6030,12 +6502,85 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "8.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcab981e19633ebcf0b001ddd37dd802996098bc1864f90b7c5d970ce76c1d59" +dependencies = [ + "aes", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.2", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/Cargo.toml b/Cargo.toml index 7cb8af6..367e842 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ block2 = "0.6.2" crossbeam-channel = "0.5.15" emojis = "0.8.0" global-hotkey = "0.7.0" +hex = "0.4.3" iced = { version = "0.14.0", features = ["image", "tokio"] } icns = "0.3.1" image = { version = "0.25.9", features = ["tiff"] } @@ -31,8 +32,11 @@ rayon = "1.11.0" rfd = "0.17.2" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" +sha2 = "0.11.0" +tempfile = "3.27.0" tokio = { version = "1.48.0", features = ["full"] } toml = "0.9.8" tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } tray-icon = "0.21.3" url = { version = "2.5.8", default-features = false } +zip = "8.5.1" diff --git a/src/app.rs b/src/app.rs index 6892bdb..9f683f0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -128,6 +128,7 @@ pub enum SetConfigFields { PlaceHolder(String), SearchUrl(String), ClipboardHistory(bool), + SetAutoUpdate(bool), HapticFeedback(bool), ShowMenubarIcon(bool), SetPage(MainPage), diff --git a/src/app/pages/settings.rs b/src/app/pages/settings.rs index 2c59db1..01d8149 100644 --- a/src/app/pages/settings.rs +++ b/src/app/pages/settings.rs @@ -129,6 +129,19 @@ pub fn settings_page(config: Config) -> Element<'static, Message> { notice_item(theme.clone(), "If you want rustcast to start on login"), ]); + let theme_clone = theme.clone(); + let auto_update = settings_item_row([ + settings_hint_text(theme.clone(), "Auto update"), + checkbox(config.clone().auto_update) + .style(move |_, _| settings_checkbox_style(&theme_clone)) + .on_toggle(move |input| Message::SetConfig(SetConfigFields::SetAutoUpdate(input))) + .into(), + notice_item( + theme.clone(), + "If rustcast should automatically update itself", + ), + ]); + let theme_clone = theme.clone(); let haptic = Row::from_iter([ settings_hint_text(theme.clone(), "Haptic feedback"), @@ -422,6 +435,7 @@ pub fn settings_page(config: Config) -> Element<'static, Message> { search.into(), debounce.into(), start_at_login.into(), + auto_update.into(), haptic.into(), tray_icon.into(), clipboard_history.into(), diff --git a/src/app/tile.rs b/src/app/tile.rs index 7b131be..ce248e3 100644 --- a/src/app/tile.rs +++ b/src/app/tile.rs @@ -4,6 +4,7 @@ pub mod update; use crate::app::apps::App; use crate::app::{ArrowKey, Message, Move, Page}; +use crate::autoupdate::new_version_available; use crate::clipboard::ClipBoardContentType; use crate::config::{Config, Shelly}; use crate::debounce::Debouncer; @@ -32,7 +33,6 @@ use tray_icon::TrayIcon; use std::collections::HashMap; use std::fmt::Debug; -use std::str::FromStr; use std::time::Duration; /// This is a wrapper around the sender to disable dropping @@ -597,46 +597,9 @@ fn handle_recipient() -> impl futures::Stream { fn handle_version_and_rankings() -> impl futures::Stream { stream::channel(100, async |mut output| { - let current_version = format!("\"{}\"", option_env!("APP_VERSION").unwrap_or("")); - - if current_version.is_empty() { - println!("empty version"); - return; - } - - let req = minreq::Request::new( - minreq::Method::Get, - "https://api.github.com/repos/RustCastLabs/rustcast/releases/latest", - ) - .with_header("User-Agent", "rustcast-update-checker") - .with_header("Accept", "application/vnd.github+json") - .with_header("X-GitHub-Api-Version", "2022-11-28"); - loop { - let resp = req - .clone() - .send() - .and_then(|x| x.as_str().map(serde_json::Value::from_str)); - - info!("Made a req for latest version"); - - if let Ok(Ok(val)) = resp { - let new_ver = val - .get("name") - .map(|x| x.to_string()) - .unwrap_or("".to_string()); - - // new_ver is in the format "\"v0.0.0\"" - // note that it is encapsulated in double quotes - if new_ver.trim() != current_version - && !new_ver.is_empty() - && new_ver.starts_with("\"v") - { - info!("new version available: {new_ver}"); - output.send(Message::UpdateAvailable).await.ok(); - } - } else { - warn!("Error getting resp"); + if new_version_available().is_some() { + output.send(Message::UpdateAvailable).await.ok(); } tokio::time::sleep(Duration::from_secs(30)).await; output.send(Message::SaveRanking).await.ok(); diff --git a/src/app/tile/update.rs b/src/app/tile/update.rs index f9dd999..7b3407b 100644 --- a/src/app/tile/update.rs +++ b/src/app/tile/update.rs @@ -31,6 +31,8 @@ use crate::app::menubar::menu_builder; use crate::app::menubar::menu_icon; use crate::app::tile::AppIndex; use crate::app::{Message, Page, tile::Tile}; +use crate::autoupdate::download_latest_app; +use crate::autoupdate::relaunch_app; use crate::calculator::Expr; use crate::commands::Function; use crate::config::Config; @@ -96,6 +98,13 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { Message::UpdateAvailable => { tile.update_available = true; + + if tile.config.auto_update { + thread::spawn(|| { + download_latest_app().ok(); + relaunch_app(); + }); + } Task::done(Message::ReloadConfig) } @@ -796,6 +805,9 @@ pub fn handle_update(tile: &mut Tile, message: Message) -> Task { SetConfigFields::HapticFeedback(haptic_feedback) => { final_config.haptic_feedback = haptic_feedback } + SetConfigFields::SetAutoUpdate(au) => { + final_config.auto_update = au; + } SetConfigFields::ShowMenubarIcon(show) => final_config.show_trayicon = show, SetConfigFields::SetThemeFields(SetConfigThemeFields::Font(fnt)) => { final_config.theme.font = Some(fnt) diff --git a/src/autoupdate.rs b/src/autoupdate.rs new file mode 100644 index 0000000..de34d7b --- /dev/null +++ b/src/autoupdate.rs @@ -0,0 +1,222 @@ +use std::str::FromStr; + +use log::{error, info}; +use sha2::{Digest, Sha256}; + +pub struct ReleaseInfo { + pub version: String, + pub zip_url: String, + pub sha256: String, +} + +pub fn get_latest_release() -> Option { + let req = minreq::Request::new( + minreq::Method::Get, + "https://api.github.com/repos/RustCastLabs/rustcast/releases/latest", + ) + .with_header("User-Agent", "rustcast-update-checker") + .with_header("Accept", "application/vnd.github+json") + .with_header("X-GitHub-Api-Version", "2022-11-28"); + + let resp = req + .send() + .and_then(|x| x.as_str().map(serde_json::Value::from_str)); + + if let Ok(Ok(val)) = resp { + let version = val.get("name")?.as_str()?.to_string(); + + let assets = val.get("assets")?.as_array()?; + + let mut zip_url = None; + let mut sha256 = None; + + for asset in assets { + let name = asset.get("name")?.as_str()?; + let url = asset.get("browser_download_url")?.as_str()?.to_string(); + + if name == "Rustcast-universal-macos.app.zip" { + zip_url = Some(url); + + sha256 = asset + .get("digest") + .and_then(|d| d.as_str()) + .and_then(|d| d.strip_prefix("sha256:")) + .map(|d| d.to_string()); + } + } + + Some(ReleaseInfo { + version, + zip_url: zip_url?, + sha256: sha256?, + }) + } else { + None + } +} + +pub fn new_version_available() -> Option { + info!("Checking for new version"); + let info = get_latest_release()?; + info!("Got latest info"); + let current = option_env!("APP_VERSION").unwrap_or(""); + + if info.version != current { + Some(info) + } else { + None + } +} + +pub fn verify_sha256(file_path: &std::path::Path, expected_hex: &str) -> std::io::Result { + let bytes = std::fs::read(file_path)?; + let digest = Sha256::digest(&bytes); + let actual_hex = hex::encode(digest); + Ok(actual_hex == expected_hex) +} + +pub fn download_latest_app() -> Result { + let info = get_latest_release().ok_or_else(|| { + error!("Could not get latest release info"); + })?; + + info!("got latest release"); + + let tmp = tempfile::tempdir().map_err(|e| { + error!("Could not create temporary directory: {e}"); + })?; + + info!("created temp dir"); + + let zip_path = tmp.path().join("Rustcast-universal-macos.app.zip"); + + info!("zip path: {:?}", zip_path); + let resp = minreq::get(&info.zip_url) + .with_header("User-Agent", "rustcast-update-checker") + .send() + .map_err(|e| { + error!("Could not download update: {e}"); + })?; + + info!("downloaded zip"); + + std::fs::write(&zip_path, resp.as_bytes()).map_err(|e| { + error!("Could not write zip to disk: {e}"); + })?; + + info!("wrote zip to disk"); + + let ok = verify_sha256(&zip_path, &info.sha256).map_err(|e| { + error!("Could not verify sha256: {e}"); + })?; + + info!("verified sha256"); + + if !ok { + error!("SHA256 mismatch — aborting update"); + return Err(()); + } + + let zip_file = std::fs::File::open(&zip_path).map_err(|e| { + error!("Could not open zip: {e}"); + })?; + + info!("opened zip"); + + let mut archive = zip::ZipArchive::new(zip_file).map_err(|e| { + error!("Could not read zip archive: {e}"); + })?; + + info!("read zip archive. contents:"); + + archive.extract(tmp.path()).map_err(|e| { + error!("Could not extract zip: {e}"); + })?; + + if let Ok(entries) = std::fs::read_dir(tmp.path()) { + for entry in entries.flatten() { + info!(" extracted entry: {:?}", entry.file_name()); + } + } + + let extracted_app = tmp.path().join("target/release/macos/Rustcast.app"); + + info!("found extracted app at: {:?}", extracted_app); + + let dest = get_app_path().ok_or_else(|| { + error!("Could not determine current app path"); + })?; + + info!("Installing update over {:?}", dest); + + if dest.exists() { + std::fs::remove_dir_all(&dest).map_err(|e| { + error!("Could not remove existing app: {e}"); + })?; + } + + move_or_copy(&extracted_app, &dest).map_err(|e| { + error!("Could not move app into place: {e}"); + })?; + + info!("Successful update"); + + Ok(dest) +} + +fn move_or_copy(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> { + match std::fs::rename(src, dst) { + Ok(()) => Ok(()), + Err(_) => { + copy_dir_recursive(src, dst)?; + std::fs::remove_dir_all(src) + } + } +} + +fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> { + std::fs::create_dir_all(dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let dst_path = dst.join(entry.file_name()); + if entry.file_type()?.is_dir() { + copy_dir_recursive(&entry.path(), &dst_path)?; + } else { + std::fs::copy(entry.path(), dst_path)?; + } + } + Ok(()) +} + +pub fn relaunch_app() { + let app_path = match get_app_path() { + Some(p) => p, + None => { + error!("Could not determine current app path for relaunch"); + return; + } + }; + + match std::process::Command::new("open").arg(&app_path).spawn() { + Ok(_) => { + info!("Relaunching app at {:?}", app_path); + std::thread::sleep(std::time::Duration::from_millis(500)); + std::process::exit(0); + } + Err(e) => { + error!("Could not relaunch app: {e}"); + } + } +} + +pub fn get_app_path() -> Option { + let exe = std::env::current_exe().ok()?; + + let mut path = exe.as_path(); + loop { + if path.extension().and_then(|e| e.to_str()) == Some("app") { + return Some(path.to_path_buf()); + } + path = path.parent()?; + } +} diff --git a/src/config.rs b/src/config.rs index 41de249..d038e09 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,6 +34,7 @@ pub struct Config { pub search_dirs: Vec, pub log_path: String, pub debounce_delay: u64, + pub auto_update: bool, } impl Default for Config { @@ -49,6 +50,7 @@ impl Default for Config { search_url: "https://duckduckgo.com/search?q=%s".to_string(), cbhist: true, haptic_feedback: false, + auto_update: true, show_trayicon: true, main_page: MainPage::default(), search_dirs: vec!["~".to_string()], diff --git a/src/main.rs b/src/main.rs index 82c8863..c10192c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![deny(clippy::dbg_macro)] mod app; +mod autoupdate; mod calculator; mod clipboard; mod commands;