From 84287088a945b0d1dd553168f7e83cfad4558f16 Mon Sep 17 00:00:00 2001 From: Caleb Smith Date: Sat, 6 Jul 2024 23:16:41 -0500 Subject: [PATCH] Busy window during 1-click downloads --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/gui.rs | 7 ++++ src/gui/tasks.rs | 87 +++++++++++++++++++++++++++++++++++++++++++----- src/main.rs | 3 ++ 5 files changed, 97 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 068a006f..a98c774e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4397,6 +4397,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f2b15926089e5526bb2dd738a2eb0e59034356e06eb71e1cd912358c0e62c4d" +[[package]] +name = "ssilide" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2afc62cecb31ec7151e0fd5649540d9fd25221588857677e673b581ff0aa2b82" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -5075,6 +5081,7 @@ dependencies = [ "serde_json", "serde_yaml 0.9.34+deprecated", "smartstring", + "ssilide", "uk-content", "uk-manager", "uk-mod", diff --git a/Cargo.toml b/Cargo.toml index 8d7a141f..b1268331 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ serde = { workspace = true, features = ["derive", "rc"] } serde_json = { workspace = true } serde_yaml = { workspace = true } smartstring = { workspace = true, features = ["serde"] } +ssilide = "0.2.0" zip = { workspace = true, default-features = false, features = ["deflate"] } astrolabe = "0.5.1" diff --git a/src/gui.rs b/src/gui.rs index 98e610aa..8db3f709 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -188,6 +188,7 @@ pub enum Message { SelectThrough(usize), SelectProfileManage(smartstring::alias::String), SetChangelog(String), + SetDownloading(String), SetFocus(FocusedPane), SetTheme(uk_ui::visuals::Theme), ShowAbout, @@ -640,6 +641,11 @@ impl App { Message::SelectProfileManage(name) => { self.profiles_state.borrow_mut().selected = Some(name); } + Message::SetDownloading(mod_name) => { + ctx.request_repaint(); + self.busy.set(true); + log::info!("Downloading {mod_name} from GameBanana…"); + } Message::SetFocus(pane) => { self.focused = pane; } @@ -668,6 +674,7 @@ impl App { Message::OpenMod(path) => { let core = self.core.clone(); let meta = self.meta_input.take(); + ctx.request_repaint(); self.do_task(move |_| tasks::open_mod(&core, &path, meta)); } Message::HandleMod(mod_) => { diff --git a/src/gui/tasks.rs b/src/gui/tasks.rs index ef95abe6..c71acbee 100644 --- a/src/gui/tasks.rs +++ b/src/gui/tasks.rs @@ -27,6 +27,7 @@ use uk_reader::ResourceReader; use uk_util::{OptionExt, PathExt}; use super::{package::ModPackerBuilder, util::response, Message}; +use crate::INTERFACE; mod handlers; @@ -627,12 +628,35 @@ pub fn do_update(version: VersionResponse) -> Result { pub static ONECLICK_SENDER: uk_util::OnceLock> = uk_util::OnceLock::new(); +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +enum IpcMessage { + OpenMod(PathBuf), + Error(String), + Starting(String), +} + +impl From for Message { + fn from(value: IpcMessage) -> Self { + match value { + IpcMessage::OpenMod(path) => Message::OpenMod(path), + IpcMessage::Error(e) => Message::Error(anyhow::anyhow!(e)), + IpcMessage::Starting(mod_name) => Message::SetDownloading(mod_name), + } + } +} + +impl IpcMessage { + fn into_bytes(self) -> Vec { + serde_json::to_vec(&self).unwrap() + } +} + pub fn oneclick(url: &str) { - let mut parts = url.split(','); - let url = parts.next().unwrap_or_default().to_owned(); - let cat = parts.next().unwrap_or_default().to_owned(); - let id = parts.next().unwrap_or_default().to_owned(); - std::thread::spawn(move || { + fn process(url: &str) -> IpcMessage { + let mut parts = url.split(','); + let url = parts.next().unwrap_or_default().to_owned(); + let cat = parts.next().unwrap_or_default().to_owned(); + let id = parts.next().unwrap_or_default().to_owned(); log::debug!("Processing GameBanana 1-click URL: {url}"); log::debug!("Checking mod name from API"); let mod_name = response(&format!( @@ -642,6 +666,10 @@ pub fn oneclick(url: &str) { .map(|mut res| sanitise(&res.remove(0))) .unwrap_or_else(|_| "oneclick_mod".into()); log::info!("Downloading {mod_name} from GameBanana 1-click…"); + if let Ok(client) = INTERFACE.connect() { + let buf = IpcMessage::Starting(mod_name.clone()).into_bytes(); + let _ = client.send(&buf); + } let mut data = vec![]; let msg = http_req::request::Request::new(&url.as_str().try_into().unwrap()) .method(http_req::request::Method::GET) @@ -666,12 +694,55 @@ pub fn oneclick(url: &str) { log::debug!("Saving mod to temp file at {}", tmp.display()); fs_err::write(tmp.as_path(), data).context("Failed to save mod to temp file")?; log::info!("Finished downloading {mod_name}"); - Ok(Message::OpenMod(tmp.to_path_buf())) + Ok(IpcMessage::OpenMod(tmp.to_path_buf())) }) - .map_err(Message::Error) + .map_err(|e| IpcMessage::Error(e.to_string())) .unwrap_or_else(|e| e); log::debug!("1-click mod downloaded, sending to UI for install"); - ONECLICK_SENDER.wait().send(msg).expect("Broken channel") + msg + } + + match INTERFACE.connect() { + Ok(client) => { + let msg = process(url); + let buf = msg.into_bytes(); + client + .send(&buf) + .expect("Failed to send mod to existing UKMM instance"); + std::process::exit(0); + } + Err(_) => { + let url = url.to_owned(); + std::thread::spawn(move || { + let msg = process(&url); + ONECLICK_SENDER + .wait() + .send(msg.into()) + .expect("Broken channel") + }); + } + } +} + +pub fn wait_ipc() { + std::thread::spawn(|| { + let sock = INTERFACE + .claim() + .expect("Failed to claim single instance interface. Is UKMM already open?"); + let mut buf = vec![0; 2048]; + loop { + if let Ok(len) = sock.recv(&mut buf) { + log::debug!("Received 1-click install message"); + unsafe { buf.set_len(len) } + let msg: IpcMessage = serde_json::from_slice(&buf) + .with_context(|| String::from_utf8(buf.clone()).unwrap_or_default()) + .expect("Broken IPC message"); + ONECLICK_SENDER + .wait() + .send(msg.into()) + .expect("Broken channel"); + } + } }); } diff --git a/src/main.rs b/src/main.rs index 52c48be6..859e7564 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,8 @@ extern "system" { fn MessageBoxW(hwnd: i32, message: *const i8, title: *const i8, utype: usize) -> i32; } +const INTERFACE: ssilide::Interface = ssilide::Interface::new(6666); + fn main() -> Result<()> { #[cfg(target_os = "windows")] unsafe { @@ -43,6 +45,7 @@ fn main() -> Result<()> { } } } + gui::tasks::wait_ipc(); if let Err(e) = std::panic::catch_unwind(gui::main) { let error_msg = format!( "An unrecoverable error occured. Error details: {}",