diff --git a/Cargo.lock b/Cargo.lock index 25c5e4e..0ab730a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.6.0" @@ -832,6 +838,15 @@ dependencies = [ "wio", ] +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + [[package]] name = "endian-type" version = "0.1.2" @@ -1022,6 +1037,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -1374,6 +1398,25 @@ dependencies = [ "syn", ] +[[package]] +name = "h2" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1410,6 +1453,87 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "im" version = "15.1.0" @@ -1517,6 +1641,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "ipnet" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" + [[package]] name = "is-terminal" version = "0.4.4" @@ -1723,6 +1853,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1756,6 +1892,41 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nexusmods" +version = "0.1.0" +dependencies = [ + "futures", + "lazy_static", + "regex", + "reqwest", + "serde", + "serde_json", + "thiserror", + "time", + "tokio", + "tracing", + "url", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1987,6 +2158,51 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -2093,6 +2309,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "pest" version = "2.5.6" @@ -2398,6 +2620,43 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "reqwest" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "base64 0.21.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "resvg" version = "0.25.0" @@ -2533,6 +2792,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2566,6 +2834,29 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "0.10.2" @@ -2598,6 +2889,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_sjson" version = "1.0.0" @@ -2607,6 +2909,18 @@ dependencies = [ "serde", ] +[[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 = "sha1" version = "0.10.5" @@ -2687,6 +3001,16 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "steamid-ng" version = "1.0.0" @@ -2926,6 +3250,21 @@ dependencies = [ "displaydoc", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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.26.0" @@ -2940,6 +3279,7 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "tracing", "windows-sys 0.45.0", @@ -2956,6 +3296,16 @@ dependencies = [ "syn", ] +[[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-stream" version = "0.1.12" @@ -2967,6 +3317,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.11" @@ -2993,6 +3357,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.37" @@ -3076,6 +3446,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "ttf-parser" version = "0.17.1" @@ -3211,6 +3587,15 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-script" version = "0.5.5" @@ -3235,13 +3620,25 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "usvg" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585bb2d87c8fd6041a479dea01479dcf9094e61b5f9af221606927e61a2bd939" dependencies = [ - "base64", + "base64 0.13.1", "data-url", "flate2", "fontdb", @@ -3279,6 +3676,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[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.1.1" @@ -3323,6 +3726,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3354,6 +3767,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" diff --git a/crates/dtmm/src/controller/app.rs b/crates/dtmm/src/controller/app.rs index 64c4184..9e46305 100644 --- a/crates/dtmm/src/controller/app.rs +++ b/crates/dtmm/src/controller/app.rs @@ -34,7 +34,7 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result Result<()> { Ok(()) } + +#[tracing::instrument(skip(info, api), fields(id = info.id, name = info.name, version = info.version))] +async fn check_mod_update(info: Arc, api: Arc) -> Result> { + let Some(nexus) = &info.nexus else { + return Ok(None); + }; + + let updated_info = api + .mods_id(nexus.id) + .await + .wrap_err_with(|| format!("Failed to query mod {} from Nexus", nexus.id))?; + + let updated_nexus = NexusInfo { + id: nexus.id, + version: updated_info.version, + updated: updated_info.updated_timestamp, + }; + + let mut info = Arc::unwrap_or_clone(info); + info.nexus = Some(updated_nexus); + + Ok(Some(info)) +} + +#[tracing::instrument(skip(state))] +pub(crate) async fn check_updates(state: ActionState) -> Result> { + if state.nexus_api_key.is_empty() { + eyre::bail!("Nexus API key not set. Cannot check for updates."); + } + + let api = NexusApi::new(state.nexus_api_key.to_string()) + .wrap_err("Failed to initialize Nexus API")?; + let api = Arc::new(api); + + let tasks = state + .mods + .iter() + .map(|info| check_mod_update(info.clone(), api.clone())); + + let results = futures::future::join_all(tasks).await; + let updates = results + .into_iter() + .filter_map(|res| match res { + Ok(info) => info, + Err(err) => { + tracing::error!("{:?}", err); + None + } + }) + .collect(); + Ok(updates) +} diff --git a/crates/dtmm/src/controller/worker.rs b/crates/dtmm/src/controller/worker.rs index 48e9c1e..fafeebe 100644 --- a/crates/dtmm/src/controller/worker.rs +++ b/crates/dtmm/src/controller/worker.rs @@ -12,6 +12,7 @@ use tokio::sync::RwLock; use crate::controller::app::*; use crate::controller::game::*; use crate::state::AsyncAction; +use crate::state::ACTION_FINISH_CHECK_UPDATE; use crate::state::ACTION_FINISH_SAVE_SETTINGS; use crate::state::ACTION_SHOW_ERROR_DIALOG; use crate::state::{ @@ -120,6 +121,29 @@ async fn handle_action( .submit_command(ACTION_FINISH_SAVE_SETTINGS, (), Target::Auto) .expect("failed to send command"); }), + AsyncAction::CheckUpdates(state) => tokio::spawn(async move { + let updates = match check_updates(state) + .await + .wrap_err("Failed to check for updates") + { + Ok(updates) => updates, + Err(err) => { + tracing::error!("{:?}", err); + send_error(event_sink.clone(), err).await; + vec![] + } + }; + + event_sink + .write() + .await + .submit_command( + ACTION_FINISH_CHECK_UPDATE, + SingleUse::new(updates), + Target::Auto, + ) + .expect("failed to send command"); + }), }; } } diff --git a/crates/dtmm/src/main.rs b/crates/dtmm/src/main.rs index de8ca8d..ea7bacb 100644 --- a/crates/dtmm/src/main.rs +++ b/crates/dtmm/src/main.rs @@ -1,5 +1,6 @@ #![recursion_limit = "256"] #![feature(let_chains)] +#![feature(arc_unwrap_or_clone)] #![windows_subsystem = "windows"] use std::path::PathBuf; diff --git a/crates/dtmm/src/state/data.rs b/crates/dtmm/src/state/data.rs index 848c6bb..a250660 100644 --- a/crates/dtmm/src/state/data.rs +++ b/crates/dtmm/src/state/data.rs @@ -73,7 +73,7 @@ impl From for ModDependency { #[derive(Clone, Data, Debug, Lens, serde::Serialize, serde::Deserialize)] pub(crate) struct NexusInfo { pub id: u64, - pub version: Option, + pub version: String, #[data(ignore)] #[serde(with = "time::serde::timestamp")] pub updated: OffsetDateTime, @@ -97,6 +97,7 @@ pub(crate) struct ModInfo { #[data(ignore)] pub resources: ModResourceInfo, pub depends: Vector, + #[data(ignore)] pub nexus: Option, } @@ -139,6 +140,7 @@ pub(crate) struct State { pub is_reset_in_progress: bool, pub is_save_in_progress: bool, pub is_next_save_pending: bool, + pub is_update_in_progress: bool, pub game_dir: Arc, pub data_dir: Arc, pub nexus_api_key: Arc, @@ -177,6 +179,7 @@ impl State { is_reset_in_progress: false, is_save_in_progress: false, is_next_save_pending: false, + is_update_in_progress: false, config_path: Arc::new(config_path), game_dir: Arc::new(game_dir), data_dir: Arc::new(data_dir), diff --git a/crates/dtmm/src/state/delegate.rs b/crates/dtmm/src/state/delegate.rs index 1dbe2a2..4a0fc17 100644 --- a/crates/dtmm/src/state/delegate.rs +++ b/crates/dtmm/src/state/delegate.rs @@ -39,6 +39,11 @@ pub(crate) const ACTION_START_SAVE_SETTINGS: Selector = pub(crate) const ACTION_FINISH_SAVE_SETTINGS: Selector = Selector::new("dtmm.action.finish-save-settings"); +pub(crate) const ACTION_START_CHECK_UPDATE: Selector = + Selector::new("dtmm.action.start-check-update"); +pub(crate) const ACTION_FINISH_CHECK_UPDATE: Selector>> = + Selector::new("dtmm.action.finish-check-update"); + pub(crate) const ACTION_SET_DIRTY: Selector = Selector::new("dtmm.action.set-dirty"); pub(crate) const ACTION_SHOW_ERROR_DIALOG: Selector> = @@ -79,6 +84,7 @@ pub(crate) enum AsyncAction { AddMod(ActionState, FileInfo), DeleteMod(ActionState, Arc), SaveSettings(ActionState), + CheckUpdates(ActionState), } pub(crate) struct Delegate { @@ -304,6 +310,50 @@ impl AppDelegate for Delegate { state.windows.insert(id, handle); Handled::Yes } + cmd if cmd.is(ACTION_START_CHECK_UPDATE) => { + if self + .sender + .send(AsyncAction::CheckUpdates(state.clone().into())) + .is_ok() + { + state.is_update_in_progress = true; + } else { + tracing::error!("Failed to queue action to check updates"); + } + Handled::Yes + } + cmd if cmd.is(ACTION_FINISH_CHECK_UPDATE) => { + let mut updates = cmd + .get(ACTION_FINISH_CHECK_UPDATE) + .and_then(SingleUse::take) + .expect("command type matched but didn't contain the expected value"); + + if tracing::enabled!(tracing::Level::DEBUG) { + let mods: Vec<_> = updates + .iter() + .map(|info| { + format!( + "{}: {} -> {:?}", + info.name, + info.version, + info.nexus.as_ref().map(|n| &n.version) + ) + }) + .collect(); + + tracing::info!("Mod updates:\n{}", mods.join("\n")); + } + + for mod_info in state.mods.iter_mut() { + if let Some(index) = updates.iter().position(|i2| i2.id == mod_info.id) { + let update = updates.swap_remove(index); + *mod_info = Arc::new(update); + } + } + + state.is_update_in_progress = false; + Handled::Yes + } cmd => { if cfg!(debug_assertions) { tracing::warn!("Unknown command: {:?}", cmd); diff --git a/crates/dtmm/src/ui/window/main.rs b/crates/dtmm/src/ui/window/main.rs index 67ab0e5..16be5f0 100644 --- a/crates/dtmm/src/ui/window/main.rs +++ b/crates/dtmm/src/ui/window/main.rs @@ -15,8 +15,8 @@ use lazy_static::lazy_static; use crate::state::{ ModInfo, State, View, ACTION_ADD_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP, - ACTION_SELECT_MOD, ACTION_SET_WINDOW_HANDLE, ACTION_START_DELETE_SELECTED_MOD, - ACTION_START_DEPLOY, ACTION_START_RESET_DEPLOYMENT, + ACTION_SELECT_MOD, ACTION_SET_WINDOW_HANDLE, ACTION_START_CHECK_UPDATE, + ACTION_START_DELETE_SELECTED_MOD, ACTION_START_DEPLOY, ACTION_START_RESET_DEPLOYMENT, }; use crate::ui::theme::{self, ColorExt}; use crate::ui::widget::border::Border; @@ -49,6 +49,12 @@ fn build_top_bar() -> impl Widget { state.current_view = View::Settings; }); + let check_update_button = Button::with_label("Check for updates") + .on_click(|ctx, _: &mut State, _| { + ctx.submit_command(ACTION_START_CHECK_UPDATE); + }) + .disabled_if(|data, _| data.is_update_in_progress); + let deploy_button = { let icon = Svg::new(SvgData::from_str(theme::icons::ALERT_CIRCLE).expect("invalid SVG")) .fix_height(druid::theme::TEXT_SIZE_NORMAL); @@ -85,6 +91,8 @@ fn build_top_bar() -> impl Widget { ) .with_child( Flex::row() + .with_child(check_update_button) + .with_default_spacer() .with_child(deploy_button) .with_default_spacer() .with_child(reset_button), @@ -118,10 +126,30 @@ fn build_mod_list() -> impl Widget { let name = Label::raw().lens(lens!((usize, Arc, bool), 1).then(ModInfo::name.in_arc())); + let version = Label::dynamic(|info: &Arc, _| { + let has_update = info + .nexus + .as_ref() + .map(|n| info.version != n.version) + .unwrap_or(false); + if has_update { + format!("! {}", info.version) + } else { + info.version.to_string() + } + }) + .lens(lens!((usize, Arc, bool), 1)); + + let fields = Flex::row() + .must_fill_main_axis(true) + .main_axis_alignment(MainAxisAlignment::SpaceBetween) + .with_child(name) + .with_child(version); + Flex::row() .must_fill_main_axis(true) .with_child(checkbox) - .with_child(name) + .with_flex_child(fields, 1.) .padding((5.0, 4.0)) .background(theme::keys::KEY_MOD_LIST_ITEM_BG_COLOR) .on_click(|ctx, (i, _, _), _env| ctx.submit_command(ACTION_SELECT_MOD.with(*i)))