diff --git a/noita-proxy/Cargo.lock b/noita-proxy/Cargo.lock index 066397b7..0651ac27 100644 --- a/noita-proxy/Cargo.lock +++ b/noita-proxy/Cargo.lock @@ -1056,6 +1056,16 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1651,6 +1661,12 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.4.0" @@ -1991,6 +2007,7 @@ dependencies = [ "egui-file-dialog", "egui_extras", "egui_plot", + "eyre", "fluent-bundle", "fluent-templates", "image", diff --git a/noita-proxy/Cargo.toml b/noita-proxy/Cargo.toml index 3bfd27f1..7c46381b 100644 --- a/noita-proxy/Cargo.toml +++ b/noita-proxy/Cargo.toml @@ -46,6 +46,7 @@ argh = "0.1.12" shlex = "1.3.0" quick-xml = { version = "0.36.0", features = ["serialize"] } dashmap = "6.0.1" +eyre = "0.6.12" [build-dependencies] winresource = "0.1.17" diff --git a/noita-proxy/src/bookkeeping/mod_manager.rs b/noita-proxy/src/bookkeeping/mod_manager.rs index 10f3517c..ab4b06e8 100644 --- a/noita-proxy/src/bookkeeping/mod_manager.rs +++ b/noita-proxy/src/bookkeeping/mod_manager.rs @@ -3,6 +3,7 @@ use std::{ error::Error, fs::{self, File}, io::{self, BufReader}, + mem, path::{Path, PathBuf}, }; @@ -248,41 +249,57 @@ impl Modmanager { match promise.ready() { Some(Ok(downloader)) => { downloader.show_progress(ui); - match downloader.ready() { - Some(Ok(_)) => { - let path = downloader.path().to_path_buf(); - let directory = settings.mod_path(); - let promise: Promise> = - Promise::spawn_thread("unpack", move || { - extract_and_remove_zip(path, directory) - }); - self.state = State::UnpackMod(promise); - } - Some(Err(err)) => self.state = State::ReleasesError(err.clone()), - None => {} - } } - Some(Err(err)) => self.state = State::ReleasesError(err.clone()), + Some(Err(_)) => {} None => { ui.label(tr("modman_receiving_rel_info")); ui.spinner(); } } - } - State::UnpackMod(promise) => match promise.ready() { - Some(Ok(_)) => { - ui.label(tr("modman_installed")); - if ui.button(tr("button_continue")).clicked() { - self.state = State::Done; + if promise.ready().is_some() { + let State::DownloadMod(promise) = mem::take(&mut self.state) else { + unreachable!(); }; + match promise.block_and_take() { + Ok(downloader) => { + let path = downloader.path().to_path_buf(); + let directory = settings.mod_path(); + let promise: Promise> = + Promise::spawn_thread("unpack", move || { + extract_and_remove_zip(path, directory) + }); + self.state = State::UnpackMod(promise); + } + Err(err) => self.state = State::ReleasesError(err), + } } - Some(Err(err)) => { - self.state = State::ReleasesError(err.clone()); + } + State::UnpackMod(promise) => { + match promise.ready() { + Some(Ok(_)) => { + ui.label(tr("modman_installed")); + if ui.button(tr("button_continue")).clicked() { + self.state = State::Done; + return; + }; + } + Some(Err(_)) => {} + None => { + ui.label(tr("modman_unpacking")); + } } - None => { - ui.label(tr("modman_unpacking")); + if promise.ready().is_some() { + let State::UnpackMod(promise) = mem::take(&mut self.state) else { + unreachable!(); + }; + match promise.block_and_take() { + Ok(_) => {} + Err(err) => { + self.state = State::ReleasesError(err); + } + } } - }, + } State::Error(err) => { ui.label(format!("Encountered an error: {}", err)); if ui.button(tr("button_retry")).clicked() { @@ -290,7 +307,7 @@ impl Modmanager { } } State::ReleasesError(err) => { - ui.label(format!("Encountered an error: {}", err)); + ui.label(format!("Encountered an error: \n {:?}", err)); if ui.button(tr("button_retry")).clicked() { self.state = State::JustStarted; } diff --git a/noita-proxy/src/bookkeeping/releases.rs b/noita-proxy/src/bookkeeping/releases.rs index 1e4804e8..4a6fe480 100644 --- a/noita-proxy/src/bookkeeping/releases.rs +++ b/noita-proxy/src/bookkeeping/releases.rs @@ -1,48 +1,19 @@ use std::{ fmt::Display, fs::File, - io::{self, Read, Write}, + io::{Read, Write}, path::{Path, PathBuf}, sync::{atomic::AtomicU64, Arc}, time::Duration, }; use eframe::egui::{self, Ui}; +use eyre::{eyre, Context}; use poll_promise::Promise; use reqwest::blocking::Client; use serde::Deserialize; -use thiserror::Error; -use zip::result::ZipError; -#[derive(Debug, Error, Clone)] -pub enum ReleasesError { - #[error("Could not complete request: {0}")] - Request(Arc), - #[error("Asset not found")] - AssetNotFound, - #[error("Io error: {0}")] - Io(Arc), - #[error("Zip error: {0}")] - Zip(Arc), -} - -impl From for ReleasesError { - fn from(value: reqwest::Error) -> Self { - Self::Request(value.into()) - } -} - -impl From for ReleasesError { - fn from(value: io::Error) -> Self { - Self::Io(value.into()) - } -} - -impl From for ReleasesError { - fn from(value: ZipError) -> Self { - Self::Zip(value.into()) - } -} +pub type ReleasesError = eyre::Report; #[derive(Debug, Deserialize)] pub struct Release { @@ -92,18 +63,25 @@ fn download_thread( .header("Accept", "application/octet-stream") .header("X-GitHub-Api-Version", "2022-11-28") .header("User-agent", "noita proxy") - .send()?; + .send() + .wrap_err_with(|| format!("Failed to download from {}", url))?; let mut buf = [0; 4096]; loop { - let len = response.read(&mut buf)?; + let len = response.read(&mut buf).wrap_err_with(|| { + format!( + "Failed to download from {}: couldn't read from response", + url + ) + })?; shared .progress .fetch_add(len as u64, std::sync::atomic::Ordering::Relaxed); if len == 0 { break; } - file.write_all(&buf[..len])?; + file.write_all(&buf[..len]) + .wrap_err_with(|| format!("Failed to download from {}: couldn't write to file", url))?; } Ok(()) @@ -153,7 +131,7 @@ impl AssetList { self.0 .iter() .find(|asset| asset.name == name) - .ok_or(ReleasesError::AssetNotFound) + .ok_or_else(|| eyre!("Asset not found: {}", name)) } } @@ -213,9 +191,15 @@ impl Release { .header("Accept", "application/vnd.github+json") .header("X-GitHub-Api-Version", "2022-11-28") .header("User-agent", "noita proxy") - .send()?; + .send() + .wrap_err_with(|| format!("Failed to request asset list from {}", self.assets_url))?; - Ok(response.json()?) + Ok(response.json().wrap_err_with(|| { + format!( + "Failed to request asset list from {}: couldn't parse json", + self.assets_url + ) + })?) } } @@ -225,21 +209,24 @@ pub fn get_latest_release(client: &Client) -> Result { .header("Accept", "application/vnd.github+json") .header("X-GitHub-Api-Version", "2022-11-28") .header("User-agent", "noita proxy") - .send()?; + .send() + .wrap_err("Failed to request latest release")?; Ok(response.json()?) } pub fn get_release_by_tag(client: &Client, tag: Tag) -> Result { + let url = format!( + "https://api.github.com/repos/IntQuant/noita_entangled_worlds/releases/tags/{}", + tag.0 + ); let response = client - .get(format!( - "https://api.github.com/repos/IntQuant/noita_entangled_worlds/releases/tags/{}", - tag.0 - )) + .get(&url) .header("Accept", "application/vnd.github+json") .header("X-GitHub-Api-Version", "2022-11-28") .header("User-agent", "noita proxy") - .send()?; + .send() + .wrap_err_with(|| format!("Failed to get release by tag from {}", url))?; Ok(response.json()?) } diff --git a/noita-proxy/src/bookkeeping/self_update.rs b/noita-proxy/src/bookkeeping/self_update.rs index 4edaded2..5567381a 100644 --- a/noita-proxy/src/bookkeeping/self_update.rs +++ b/noita-proxy/src/bookkeeping/self_update.rs @@ -1,11 +1,12 @@ use std::{ cmp::Ordering, fs::{self, File}, - io, + io, mem, path::{Path, PathBuf}, }; use eframe::egui::{Align, Layout, Ui}; +use eyre::Context; use poll_promise::Promise; use reqwest::blocking::Client; use tracing::info; @@ -20,10 +21,13 @@ struct VersionCheckResult { ord: Ordering, } +#[derive(Default)] enum State { + #[default] Initial, Download(Promise>), ReleasesError(ReleasesError), + ReleasesError2(String), Unpack(Promise>), } @@ -113,11 +117,17 @@ impl SelfUpdateManager { }); self.state = State::Unpack(promise); } - Some(Err(err)) => self.state = State::ReleasesError(err.clone()), + Some(Err(_)) => { + let State::Download(promise) = mem::take(&mut self.state) else { + unreachable!(); + }; + self.state = + State::ReleasesError(promise.block_and_take().err().unwrap()) + } None => {} } } - Some(Err(err)) => self.state = State::ReleasesError(err.clone()), + Some(Err(err)) => self.state = State::ReleasesError2(format!("{:?}", err)), None => { ui.label(tr("selfupdate_receiving_rel_info")); ui.spinner(); @@ -137,7 +147,10 @@ impl SelfUpdateManager { } }, State::ReleasesError(err) => { - ui.label(format!("Encountered an error: {}", err)); + ui.label(format!("Encountered an error: {:?}", err)); + } + State::ReleasesError2(err) => { + ui.label(format!("Encountered an error:\n{}", err)); } } }