From 9df757c1699ae04ad29be6d706980992dd40bc73 Mon Sep 17 00:00:00 2001 From: futile Date: Thu, 17 Apr 2025 20:28:25 +0100 Subject: [PATCH] add button to download latest gta-tools exe --- Cargo.lock | 239 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/gui/mod.rs | 19 +++- src/util/codeberg.rs | 48 +++++++++ src/util/meta.rs | 25 +++++ src/util/mod.rs | 2 + 6 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 src/util/codeberg.rs create mode 100644 src/util/meta.rs diff --git a/Cargo.lock b/Cargo.lock index 43e61c5..b35eed2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,6 +424,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit-set" version = "0.8.0" @@ -654,6 +660,35 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -744,6 +779,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -1106,6 +1150,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -1395,11 +1445,13 @@ dependencies = [ "egui_extras", "image", "open", + "semver", "serde", "serde_json", "static_vcruntime", "strum 0.27.1", "sysinfo", + "ureq", "windows 0.61.1", "winreg", "winresource", @@ -1447,6 +1499,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -1964,6 +2033,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -2499,6 +2574,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2637,6 +2718,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[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.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -2669,6 +2764,47 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.23.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -2715,6 +2851,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -2951,6 +3093,12 @@ dependencies = [ "syn", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.100" @@ -3059,6 +3207,37 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-skia" version = "0.11.4" @@ -3221,6 +3400,45 @@ 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 = "ureq" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3e9af6113ecd57b8c63d3cd76a385b2e3881365f1f489e54f49801d0c83ea" +dependencies = [ + "base64", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "ureq-proto", + "utf-8", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -3232,6 +3450,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -3492,6 +3716,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.8" @@ -4414,6 +4647,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/Cargo.toml b/Cargo.toml index 9817afc..e8a3485 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,12 @@ eframe = "0.31.1" egui_extras = { version = "0.31.1", features = ["image"] } image = { version = "0.25.6", default-features = false, features = ["png"] } open = "5.3.2" +semver = "1.0.26" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" strum = { version = "0.27.1", features = ["derive"] } sysinfo = "0.34.2" +ureq = { version = "3.0.11", features = ["json"] } windows = { version = "0.61.1", features = [ "Win32_UI_Input_KeyboardAndMouse", "Win32_System_Threading", diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 1190ced..12f1b97 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -10,6 +10,7 @@ use crate::{ util::{ self, consts::{APP_STORAGE_PATH, ENHANCED, GTA_WINDOW_TITLE, LEGACY}, + meta::Meta, }, }; use eframe::egui; @@ -40,6 +41,7 @@ pub struct Flags { #[derive(Debug)] pub struct App { + meta: Meta, settings: Settings, stage: Stage, flags: Flags, @@ -54,6 +56,7 @@ pub struct App { impl Default for App { fn default() -> Self { Self { + meta: Meta::default(), settings: Settings::default(), stage: Stage::default(), flags: Flags::default(), @@ -243,21 +246,31 @@ impl App { }); } - #[allow(clippy::unused_self)] fn show_about(&self, _ctx: &egui::Context, ui: &mut egui::Ui) { ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { ui.horizontal(|ui| { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; - ui.label("with love from "); + ui.label("with "); + ui.hyperlink_to("❤", "https://codeberg.org/futile/gta-tools"); + ui.label(" from "); ui.hyperlink_to("futile", "http://futile.eu"); }); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.label(format!( "v{} {}", - env!("CARGO_PKG_VERSION"), + self.meta.current_version, if cfg!(debug_assertions) { "(dev)" } else { "" } )); + let button = ui.add_enabled_ui(self.meta.newer_version_available, |ui| { + ui.style_mut().spacing.button_padding = egui::Vec2::new(3.0, 0.0); + ui.button("⬇") + .on_disabled_hover_text("Already up to date.") + .on_hover_text("New version available!") + }); + if button.inner.clicked() { + open::that(&self.meta.latest_release.download_url).unwrap(); + } }); }); ui.add(egui::Image::new(egui::include_image!( diff --git a/src/util/codeberg.rs b/src/util/codeberg.rs new file mode 100644 index 0000000..f500612 --- /dev/null +++ b/src/util/codeberg.rs @@ -0,0 +1,48 @@ +use semver::Version; + +const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); + +#[derive(Debug)] +pub struct Release { + pub version: Version, + pub download_url: String, +} + +impl Default for Release { + fn default() -> Self { + Self { + version: Version::parse(env!("CARGO_PKG_VERSION")).unwrap(), + download_url: String::new(), + } + } +} + +pub fn get_latest_release() -> Option { + let request_url = "https://codeberg.org/api/v1/repos/futile/gta-tools/releases/latest"; + let ureq_config = ureq::Agent::config_builder() + .user_agent(APP_USER_AGENT) + .build(); + let ureq = ureq::Agent::new_with_config(ureq_config); + let Ok(mut response) = ureq.get(request_url).call() else { + return None; + }; + let Ok(json) = response.body_mut().read_json::() else { + return None; + }; + let version: Version; + if let Some(tag_name) = &json["tag_name"].as_str() { + version = Version::parse(tag_name).unwrap(); + } else { + return None; + } + let download_url: String; + if let Some(browser_download_url) = &json["assets"][0]["browser_download_url"].as_str() { + download_url = String::from(*browser_download_url); + } else { + return None; + } + Some(Release { + version, + download_url, + }) +} diff --git a/src/util/meta.rs b/src/util/meta.rs new file mode 100644 index 0000000..fe52c55 --- /dev/null +++ b/src/util/meta.rs @@ -0,0 +1,25 @@ +use crate::util::codeberg; +use semver::Version; + +#[derive(Debug)] +pub struct Meta { + pub current_version: Version, + pub latest_release: codeberg::Release, + pub newer_version_available: bool, +} + +impl Default for Meta { + fn default() -> Self { + let current_version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + let latest_release = codeberg::get_latest_release().unwrap_or_default(); + let newer_version_available = matches!( + ¤t_version.cmp_precedence(&latest_release.version), + std::cmp::Ordering::Less + ); + Self { + current_version, + latest_release, + newer_version_available, + } + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index b5f99db..bad9134 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,5 @@ +pub mod codeberg; pub mod consts; pub mod countdown; +pub mod meta; pub mod win;