diff --git a/Cargo.lock b/Cargo.lock index 5f4b5ef3732..a60e661b65d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12431,6 +12431,7 @@ dependencies = [ name = "theseus" version = "1.0.0-local" dependencies = [ + "anyhow", "ariadne", "async-minecraft-ping", "async-recursion", @@ -12456,7 +12457,6 @@ dependencies = [ "either", "encoding_rs", "enumset", - "eyre", "flate2", "fs4", "futures", @@ -12524,6 +12524,7 @@ name = "theseus_gui" version = "1.0.0-local" dependencies = [ "aes-gcm", + "anyhow", "async_zip", "base64 0.22.1", "chrono", @@ -12559,7 +12560,6 @@ dependencies = [ "tauri-plugin-updater", "tauri-plugin-window-state", "theseus", - "thiserror 2.0.17", "tokio", "tokio-util", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 4ae0d16b5cd..74cf970c30c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ actix-web = "4.11.0" actix-web-prom = "0.10.0" actix-ws = "0.3.0" aes-gcm = "0.10.3" +anyhow = "1.0.100" arc-swap = "1.7.1" argon2 = { version = "0.5.3", features = ["std"] } ariadne = { path = "packages/ariadne" } diff --git a/apps/app/Cargo.toml b/apps/app/Cargo.toml index e5c59d1dfef..7da395a4ac3 100644 --- a/apps/app/Cargo.toml +++ b/apps/app/Cargo.toml @@ -9,6 +9,7 @@ license = "GPL-3.0-only" [dependencies] aes-gcm = { workspace = true } +anyhow = { workspace = true } async_zip = { workspace = true, features = ["deflate", "tokio-fs"] } base64 = { workspace = true } chrono = { workspace = true } @@ -39,7 +40,6 @@ tauri-plugin-single-instance = { workspace = true } tauri-plugin-updater = { workspace = true } tauri-plugin-window-state = { workspace = true } theseus = { workspace = true, features = ["tauri"] } -thiserror = { workspace = true } tokio = { workspace = true, features = ["time"] } tokio-util = { workspace = true, features = ["rt"] } tracing = { workspace = true } diff --git a/apps/app/src/api/mod.rs b/apps/app/src/api/mod.rs index 693bc861d2f..d353f6de505 100644 --- a/apps/app/src/api/mod.rs +++ b/apps/app/src/api/mod.rs @@ -1,6 +1,6 @@ use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; -use thiserror::Error; +use theseus::ErrorKind; pub mod auth; pub mod import; @@ -33,110 +33,48 @@ pub mod worlds; mod oauth_utils; -pub type Result = std::result::Result; +pub type Result = std::result::Result; -// // Main returnable Theseus GUI error -// // Needs to be Serializable to be returned to the JavaScript side -// #[derive(Error, Debug, Serialize)] -// pub enum TheseusGuiError { -// #[error(transparent)] -// Serializable(), -// } +#[derive(Debug)] +pub struct CommandError(anyhow::Error); -// Serializable error intermediary, so TheseusGuiError can be Serializable (eg: so that we can return theseus::Errors in Tauri directly) -#[derive(Error, Debug)] -pub enum TheseusSerializableError { - #[error("{0}")] - Theseus(#[from] theseus::Error), - - #[error("IO error: {0}")] - IO(#[from] std::io::Error), - - #[error("Tauri error: {0}")] - Tauri(#[from] tauri::Error), - - #[cfg(feature = "updater")] - #[error("Updater error: {0}")] - Updater(#[from] tauri_plugin_updater::Error), - - #[cfg(feature = "updater")] - #[error("HTTP error: {0}")] - Http(#[from] tauri_plugin_http::reqwest::Error), +impl> From for CommandError { + fn from(error: E) -> Self { + Self(error.into()) + } } -// Generic implementation of From for ErrorTypeA -// impl From for TheseusGuiError -// where -// TheseusSerializableError: From, -// { -// fn from(error: T) -> Self { -// TheseusGuiError::Serializable(TheseusSerializableError::from(error)) -// } -// } +impl Serialize for CommandError { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: Serializer, + { + tracing::warn!("{:?}", self.0); -// This is a very simple macro that implements a very basic Serializable for each variant of TheseusSerializableError, -// where the field is the string. (This allows easy extension to errors without many match arms) -macro_rules! impl_serialize { - ($($variant:ident),* $(,)?) => { - impl Serialize for TheseusSerializableError { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - match self { - TheseusSerializableError::Theseus(theseus_error) => { - let unavailable_reason = match theseus_error.raw.as_ref() { - theseus::ErrorKind::SharedInstanceUnavailable(reason) => Some(reason), - _ => None, - }; - let code = match theseus_error.raw.as_ref() { - theseus::ErrorKind::SharedInstanceUnavailable(_) => { - Some("shared_instance_unavailable") - } - theseus::ErrorKind::SharedInstancesApiError(_) => { - Some("shared_instances_api_error") - } - _ => None, - }; - let mut state = serializer.serialize_struct( - "Theseus", - 2 + usize::from(code.is_some()) + usize::from(unavailable_reason.is_some()), - )?; - state.serialize_field("field_name", "Theseus")?; - state.serialize_field("message", &theseus_error.to_string())?; - if let Some(code) = code { - state.serialize_field("code", code)?; - } - if let Some(reason) = unavailable_reason { - state.serialize_field("reason", reason)?; - } - state.end() - } - $( - TheseusSerializableError::$variant(message) => { - let mut state = serializer.serialize_struct(stringify!($variant), 2)?; - state.serialize_field("field_name", stringify!($variant))?; - state.serialize_field("message", &message.to_string())?; - state.end() - }, - )* - } + let (code, reason) = match self.0.downcast_ref::() { + Some(ErrorKind::SharedInstanceUnavailable(reason)) => { + (Some("shared_instance_unavailable"), Some(reason)) } - } - }; -} - -// Use the macro to implement Serialize for TheseusSerializableError -#[cfg(not(feature = "updater"))] -impl_serialize! { - IO, - Tauri, -} + Some(ErrorKind::SharedInstancesApiError(_)) => { + (Some("shared_instances_api_error"), None) + } + _ => (None, None), + }; -#[cfg(feature = "updater")] -impl_serialize! { - IO, - Tauri, - Updater, - Http, + let mut state = serializer.serialize_struct( + "CommandError", + 1 + usize::from(code.is_some()) + usize::from(reason.is_some()), + )?; + state.serialize_field("message", &self.0.to_string())?; + if let Some(code) = code { + state.serialize_field("code", code)?; + } + if let Some(reason) = reason { + state.serialize_field("reason", reason)?; + } + state.end() + } } diff --git a/apps/app/src/api/mr_auth.rs b/apps/app/src/api/mr_auth.rs index 1633fd761d0..0a320ce72d3 100644 --- a/apps/app/src/api/mr_auth.rs +++ b/apps/app/src/api/mr_auth.rs @@ -1,5 +1,4 @@ use crate::api::Result; -use crate::api::TheseusSerializableError; use crate::api::oauth_utils; use crate::api::oauth_utils::auth_code_reply::AuthReplySession; use crate::api::oauth_utils::auth_seal::AuthSeal; @@ -46,18 +45,16 @@ pub async fn modrinth_login( app.opener() .open_url(auth_request_uri, None::<&str>) .map_err(|e| { - TheseusSerializableError::Theseus( - theseus::ErrorKind::OtherError(format!( - "Failed to open auth request URI: {e}" - )) - .into(), - ) + theseus::ErrorKind::OtherError(format!( + "Failed to open auth request URI: {e}" + )) + .as_error() })?; let Some(auth_code) = reply.wait().await? else { - return Err(TheseusSerializableError::Theseus( - theseus::ErrorKind::OtherError("Login canceled".into()).into(), - )); + return Err(theseus::ErrorKind::OtherError("Login canceled".into()) + .as_error() + .into()); }; let session_token = seal.open(&auth_code)?; let credentials = mr_auth::authenticate_finish_flow(&session_token).await?; diff --git a/apps/app/src/api/utils.rs b/apps/app/src/api/utils.rs index 071f98c614e..7d474cddab6 100644 --- a/apps/app/src/api/utils.rs +++ b/apps/app/src/api/utils.rs @@ -6,10 +6,10 @@ use theseus::{ prelude::{CommandPayload, DirectoryInfo, app_db_backup_dir}, }; +use crate::api::Result; use crate::api::oauth_utils::auth_code_reply::{ normalize_deep_link_command, parse_auth_deeplink, submit_deeplink, }; -use crate::api::{Result, TheseusSerializableError}; use dashmap::DashMap; use std::path::{Path, PathBuf}; use theseus::prelude::canonicalize; @@ -281,20 +281,9 @@ pub(crate) fn tauri_convert_file_src(path: &Path) -> Result { #[cfg(not(any(windows, target_os = "android")))] const BASE: &str = "asset://localhost/"; - macro_rules! theseus_try { - ($test:expr) => { - match $test { - Ok(val) => val, - Err(e) => { - return Err(TheseusSerializableError::Theseus(e.into())) - } - } - }; - } - - let path = theseus_try!(canonicalize(path)); + let path = canonicalize(path)?; let path = path.to_string_lossy(); let encoded = urlencoding::encode(&path); - Ok(theseus_try!(Url::parse(&format!("{BASE}{encoded}")))) + Ok(Url::parse(&format!("{BASE}{encoded}"))?) } diff --git a/packages/app-lib/Cargo.toml b/packages/app-lib/Cargo.toml index 9bb14ce8a4c..fcf1edca17b 100644 --- a/packages/app-lib/Cargo.toml +++ b/packages/app-lib/Cargo.toml @@ -9,6 +9,7 @@ name = "export_app_events" required-features = ["export-ts"] [dependencies] +anyhow = { workspace = true } ariadne = { workspace = true } async-minecraft-ping = { workspace = true, features = ["srv"] } async-recursion = { workspace = true } @@ -39,7 +40,6 @@ dunce = { workspace = true } either = { workspace = true } encoding_rs = { workspace = true } enumset = { workspace = true } -eyre = { workspace = true } flate2 = { workspace = true } fs4 = { workspace = true, features = ["tokio"] } futures = { workspace = true, features = ["alloc", "async-await"] } diff --git a/packages/app-lib/src/api/instance/synced_packs/operations.rs b/packages/app-lib/src/api/instance/synced_packs/operations.rs index 33346d78154..54f7d58f1a6 100644 --- a/packages/app-lib/src/api/instance/synced_packs/operations.rs +++ b/packages/app-lib/src/api/instance/synced_packs/operations.rs @@ -309,12 +309,16 @@ pub(in crate::api::instance) async fn seed_from_instance( { Ok(candidate) => candidate, Err(error) - if matches!( - error.raw.as_ref(), - crate::ErrorKind::JSONError(_) - | crate::ErrorKind::InputError(_) - | crate::ErrorKind::StdIOError(_) - ) => + if error.is::() + || error.is::() + || matches!( + error.downcast_ref::(), + Some( + crate::ErrorKind::JSONError(_) + | crate::ErrorKind::InputError(_) + | crate::ErrorKind::StdIOError(_) + ) + ) => { tracing::warn!( "Skipping pack {} from instance {instance_id} while initializing pack sync: {error}", @@ -400,8 +404,8 @@ async fn sync_pack_inner( match sync_pack_once(instance_id, project_path, automatic).await { Err(error) if matches!( - error.raw.as_ref(), - crate::ErrorKind::PackSyncChanged + error.downcast_ref::(), + Some(crate::ErrorKind::PackSyncChanged) ) => { tokio::task::yield_now().await; diff --git a/packages/app-lib/src/api/instance/synced_packs/reconciliation.rs b/packages/app-lib/src/api/instance/synced_packs/reconciliation.rs index cc3231eb3fe..015e5dda93a 100644 --- a/packages/app-lib/src/api/instance/synced_packs/reconciliation.rs +++ b/packages/app-lib/src/api/instance/synced_packs/reconciliation.rs @@ -836,9 +836,11 @@ async fn apply_instance_inner( Ok(prepared) => prepared, Err(error) if matches!( - error.raw.as_ref(), - crate::ErrorKind::PackSyncChanged - | crate::ErrorKind::Ratelimited { .. } + error.downcast_ref::(), + Some( + crate::ErrorKind::PackSyncChanged + | crate::ErrorKind::Ratelimited { .. } + ) ) => { return Err(error); @@ -1068,8 +1070,8 @@ pub(crate) async fn reconcile_after_content_change(instance_id: &str) { match capture_after_change(instance_id).await { Err(error) if matches!( - error.raw.as_ref(), - crate::ErrorKind::PackSyncChanged + error.downcast_ref::(), + Some(crate::ErrorKind::PackSyncChanged) ) => { tokio::task::yield_now().await; diff --git a/packages/app-lib/src/api/instance/synced_packs/worker.rs b/packages/app-lib/src/api/instance/synced_packs/worker.rs index d376ee86668..36be1802153 100644 --- a/packages/app-lib/src/api/instance/synced_packs/worker.rs +++ b/packages/app-lib/src/api/instance/synced_packs/worker.rs @@ -13,12 +13,12 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::time::Duration; use tokio::sync::{MutexGuard, oneshot}; use tokio::time::Instant; -use tracing_error::InstrumentError; #[derive(Default)] struct Queue { instances: BTreeSet, - waiters: BTreeMap>>>, + waiters: + BTreeMap>>>>, retry_at: BTreeMap, all: bool, running: bool, @@ -49,7 +49,7 @@ pub(super) fn queue_all() { } } -pub(crate) async fn flush(instance_id: &str) -> crate::Result<()> { +pub(crate) async fn flush(instance_id: &str) -> Result<(), Arc> { let state = State::get().await?; let (sender, receiver) = oneshot::channel(); { @@ -57,11 +57,13 @@ pub(crate) async fn flush(instance_id: &str) -> crate::Result<()> { if let Some(retry_at) = queue.retry_at.get(instance_id) { let remaining = retry_at.saturating_duration_since(Instant::now()); if !remaining.is_zero() { - return Err(crate::ErrorKind::Ratelimited { - retry_in_seconds: remaining.as_secs() - + u64::from(remaining.subsec_nanos() != 0), - } - .into()); + return Err(Arc::new( + crate::ErrorKind::Ratelimited { + retry_in_seconds: remaining.as_secs() + + u64::from(remaining.subsec_nanos() != 0), + } + .as_error(), + )); } } queue.instances.insert(instance_id.to_owned()); @@ -77,6 +79,7 @@ pub(crate) async fn flush(instance_id: &str) -> crate::Result<()> { "Pack sync worker stopped before completing the request." .to_owned(), ) + .as_error() })? } @@ -133,10 +136,12 @@ fn start(state: Arc) { (instance_id, pending) }; let result = - super::reconciliation::run_queued(&instance_id, &state).await; + super::reconciliation::run_queued(&instance_id, &state) + .await + .map_err(Arc::new); if let Err(error) = &result { - match error.raw.as_ref() { - crate::ErrorKind::PackSyncChanged => { + match error.downcast_ref::() { + Some(crate::ErrorKind::PackSyncChanged) => { let mut queue = state.pack_sync_worker.queue.lock(); queue.instances.insert(instance_id.clone()); if !pending.is_empty() { @@ -148,7 +153,9 @@ fn start(state: Arc) { } continue; } - crate::ErrorKind::Ratelimited { retry_in_seconds } => { + Some(crate::ErrorKind::Ratelimited { + retry_in_seconds, + }) => { let retry_at = Instant::now() + Duration::from_secs((*retry_in_seconds).max(1)); { @@ -182,12 +189,7 @@ fn start(state: Arc) { } } for sender in pending { - let result = - result.as_ref().map(|_| ()).map_err(|error| crate::Error { - raw: error.raw.clone(), - source: error.raw.clone().in_current_span(), - }); - let _ = sender.send(result); + let _ = sender.send(result.clone()); } } }); diff --git a/packages/app-lib/src/error.rs b/packages/app-lib/src/error.rs index 1b957f0a235..a6f0434e5f6 100644 --- a/packages/app-lib/src/error.rs +++ b/packages/app-lib/src/error.rs @@ -1,11 +1,8 @@ //! Theseus error type -use std::sync::Arc; - use crate::util; use data_url::DataUrlError; use derive_more::Display; use serde::{Deserialize, Serialize}; -use tracing_error::InstrumentError; #[derive(Serialize, Deserialize, Debug, Display)] #[display("{description}")] @@ -49,9 +46,6 @@ pub enum ErrorKind { #[error("Pack sync inputs changed during preparation")] PackSyncChanged, - #[error("{0:?}")] - Any(eyre::Report), - #[error("Filesystem error: {0}")] FSError(String), @@ -247,55 +241,7 @@ fn format_seconds(seconds: u64) -> String { ) } -#[derive(Debug)] -pub struct Error { - pub raw: Arc, - pub source: tracing_error::TracedError>, -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.source.source() - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(fmt, "{}", self.source) - } -} - -impl Clone for Error { - fn clone(&self) -> Self { - Self { - raw: Arc::clone(&self.raw), - source: Arc::clone(&self.raw).in_current_span(), - } - } -} - -impl> From for Error { - fn from(source: E) -> Self { - let error = Into::::into(source); - let boxed_error = Arc::new(error); - - Self { - raw: boxed_error.clone(), - source: boxed_error.in_current_span(), - } - } -} - -impl From for Error { - fn from(value: eyre::Report) -> Self { - let error = Arc::new(ErrorKind::Any(value)); - - Self { - raw: error.clone(), - source: error.in_current_span(), - } - } -} +pub type Error = anyhow::Error; impl ErrorKind { pub fn as_error(self) -> Error { diff --git a/packages/app-lib/src/install/model.rs b/packages/app-lib/src/install/model.rs index 0746774d610..44633292796 100644 --- a/packages/app-lib/src/install/model.rs +++ b/packages/app-lib/src/install/model.rs @@ -676,8 +676,8 @@ impl InstallErrorView { phase: Some(phase), message: error.to_string(), reason: None, - api: match error.raw.as_ref() { - crate::ErrorKind::LabrinthError(error) => { + api: match error.downcast_ref::() { + Some(crate::ErrorKind::LabrinthError(error)) => { Some(InstallApiErrorDetails { error: error.error.clone(), status: error.status, diff --git a/packages/app-lib/src/install/runner.rs b/packages/app-lib/src/install/runner.rs index 33a5ca261a2..48326f16ce2 100644 --- a/packages/app-lib/src/install/runner.rs +++ b/packages/app-lib/src/install/runner.rs @@ -19,6 +19,7 @@ use crate::api::pack::install_from::{ get_local_pack_instance, }; use crate::api::pack::install_mrpack::install_zipped_mrpack_files_with_reporter; +use crate::event::EventError; use crate::event::InstancePayloadType; use crate::event::emit::emit_instance; use crate::state::instances::adapters::sqlite::content_rows; @@ -28,11 +29,20 @@ use crate::state::{ ModLoader, State, }; use crate::util::fetch::DownloadReason; +use crate::util::io::IOError; +use crate::util::jre::JREError; +use async_tungstenite::tungstenite; +use async_zip::error::ZipError; +use sqlx::migrate::MigrateError; use std::collections::{HashMap, HashSet}; use std::future::Future; -use std::path::PathBuf; +use std::path::{PathBuf, StripPrefixError}; use std::sync::{Arc, LazyLock, Mutex, Weak}; -use tokio::sync::{Mutex as AsyncMutex, MutexGuard, OwnedMutexGuard}; +use tokio::sync::oneshot::error::RecvError; +use tokio::sync::{ + AcquireError, Mutex as AsyncMutex, MutexGuard, OwnedMutexGuard, +}; +use tokio::task::JoinError; use uuid::Uuid; /// Admission covers setup and deletion. A target reservation stays with its worker @@ -1796,7 +1806,9 @@ fn install_error_view( error, context, ); - if let ErrorKind::SharedInstanceUnavailable(reason) = error.raw.as_ref() { + if let Some(ErrorKind::SharedInstanceUnavailable(reason)) = + error.downcast_ref() + { view.reason = Some(*reason); } view @@ -1808,7 +1820,11 @@ fn install_error_code( ) -> &'static str { use InstallPhaseId::*; - match error.raw.as_ref() { + let Some(kind) = error.downcast_ref::() else { + return library_error_code(phase, error); + }; + + match kind { ErrorKind::SharedInstanceUnavailable(_) => { "shared_instance_unavailable" } @@ -1834,29 +1850,14 @@ fn install_error_code( _ => "launcher_error", }, ErrorKind::JREError(_) => "java_error", - ErrorKind::NoValueFor(_) | ErrorKind::MetadataError(_) => match phase { - ResolvingLoader => "loader_error", - PreparingJava => "java_error", - _ => "metadata_error", - }, + ErrorKind::NoValueFor(_) | ErrorKind::MetadataError(_) => { + metadata_error_code(phase) + } ErrorKind::FetchError(_) | ErrorKind::ApiIsDownError(_) | ErrorKind::WSError(_) | ErrorKind::WSClosedError(_) | ErrorKind::Ratelimited { .. } => "network_error", - ErrorKind::Any(_) - if matches!( - phase, - DownloadingPackFile - | DownloadingContent - | ResolvingMinecraft - | ResolvingLoader - | PreparingJava - | DownloadingMinecraft - ) => - { - "network_error" - } ErrorKind::LabrinthError(_) => "api_error", ErrorKind::HashError(_, _) => "hash_error", ErrorKind::ZipError(_) => "archive_error", @@ -1873,11 +1874,54 @@ fn install_error_code( | ErrorKind::RecvError(_) | ErrorKind::AcquireError(_) | ErrorKind::EventError(_) => "internal_error", - ErrorKind::OtherError(_) | ErrorKind::Any(_) => "internal_error", + ErrorKind::OtherError(_) => "internal_error", _ => "unknown_error", } } +fn library_error_code( + phase: InstallPhaseId, + error: &crate::Error, +) -> &'static str { + if error.is::() || error.is::() { + "network_error" + } else if error.is::() { + "java_error" + } else if error.is::() { + metadata_error_code(phase) + } else if error.is::() { + "archive_error" + } else if error.is::() + || error.is::() + { + "path_error" + } else if error.is::() || error.is::() { + "filesystem_error" + } else if error.is::() + || error.is::() + { + "parse_error" + } else if error.is::() || error.is::() { + "database_error" + } else if error.is::() + || error.is::() + || error.is::() + || error.is::() + { + "internal_error" + } else { + "unknown_error" + } +} + +fn metadata_error_code(phase: InstallPhaseId) -> &'static str { + match phase { + InstallPhaseId::ResolvingLoader => "loader_error", + InstallPhaseId::PreparingJava => "java_error", + _ => "metadata_error", + } +} + fn current_instance_id(job_state: &InstallJobState) -> Option { match &job_state.target { InstallTarget::NewInstance { instance_id } => instance_id.clone(), diff --git a/packages/app-lib/src/state/friends.rs b/packages/app-lib/src/state/friends.rs index ebf250e69fe..b00680a1d03 100644 --- a/packages/app-lib/src/state/friends.rs +++ b/packages/app-lib/src/state/friends.rs @@ -411,7 +411,7 @@ impl FriendsSocket { .await; if let Err(ref e) = result - && let ErrorKind::LabrinthError(e) = &*e.raw + && let Some(ErrorKind::LabrinthError(e)) = e.downcast_ref() && e.error == "not_found" { return Err(ErrorKind::OtherError(format!( diff --git a/packages/app-lib/src/state/instances/commands/content_mutation.rs b/packages/app-lib/src/state/instances/commands/content_mutation.rs index 76197116675..83f8345cf9f 100644 --- a/packages/app-lib/src/state/instances/commands/content_mutation.rs +++ b/packages/app-lib/src/state/instances/commands/content_mutation.rs @@ -11,6 +11,7 @@ use crate::state::{ KnownModrinthFile, ProjectType, State, cache_file_hash_metadata, file_modified_at_ns, }; +use crate::util::io::IOError; use chrono::Utc; use std::path::Path; use tokio::fs; @@ -77,12 +78,16 @@ enum PreparedChange { } fn adoption_can_be_deferred(error: &crate::Error) -> bool { - matches!( - error.raw.as_ref(), - crate::ErrorKind::StdIOError(_) - | crate::ErrorKind::IOError(_) - | crate::ErrorKind::InputError(_) - ) + error.is::() + || error.is::() + || matches!( + error.downcast_ref::(), + Some( + crate::ErrorKind::StdIOError(_) + | crate::ErrorKind::IOError(_) + | crate::ErrorKind::InputError(_) + ) + ) } struct PendingContentChange { diff --git a/packages/app-lib/src/state/minecraft_auth.rs b/packages/app-lib/src/state/minecraft_auth.rs index b835ad439df..5b848bb2940 100644 --- a/packages/app-lib/src/state/minecraft_auth.rs +++ b/packages/app-lib/src/state/minecraft_auth.rs @@ -475,22 +475,26 @@ impl Credentials { match res { Ok(_) => Ok(Some(creds)), Err(err) => { - if let ErrorKind::MinecraftAuthenticationError( - MinecraftAuthenticationError::Request { - ref source, - .. - }, - ) = *err.raw + let auth_error = err + .downcast_ref::() + .or_else(|| match err.downcast_ref::() { + Some(ErrorKind::MinecraftAuthenticationError( + error, + )) => Some(error), + _ => None, + }); + + if let Some(MinecraftAuthenticationError::Request { + source, + .. + }) = auth_error && (source.is_connect() || source.is_timeout()) { return Ok(Some(creds)); } - if matches!( - &*err.raw, - ErrorKind::MinecraftAuthenticationError(source) - if source.is_invalid_grant() - ) { + if auth_error.is_some_and(|error| error.is_invalid_grant()) + { Self::remove(creds.offline_profile.id, exec).await?; if let Some((_, mut user)) = diff --git a/packages/app-lib/src/state/mod.rs b/packages/app-lib/src/state/mod.rs index 22c4e11e17b..0f34101481e 100644 --- a/packages/app-lib/src/state/mod.rs +++ b/packages/app-lib/src/state/mod.rs @@ -1,5 +1,6 @@ //! Theseus state management system use crate::util::fetch::{FetchSemaphore, IoSemaphore}; +use anyhow::anyhow; use dashmap::DashMap; use std::sync::atomic::AtomicBool; use std::sync::{Arc, LazyLock}; @@ -71,7 +72,7 @@ pub mod server_join_log; enum StartupPhase { Pending, Ready, - Failed(crate::Error), + Failed(String), } static LAUNCHER_STATE: OnceCell> = OnceCell::const_new(); @@ -187,7 +188,7 @@ impl State { .await; STATE_STARTUP.send_replace(match &result { Ok(_) => StartupPhase::Ready, - Err(error) => StartupPhase::Failed(error.clone()), + Err(error) => StartupPhase::Failed(format!("{error:#}")), }); let state = result?; @@ -327,7 +328,7 @@ impl State { .get() .expect("state is set before startup is marked ready"), )), - StartupPhase::Failed(error) => Err(error.clone()), + StartupPhase::Failed(message) => Err(anyhow!("{message}")), StartupPhase::Pending => { unreachable!( "wait_for returns only once the phase has left Pending" diff --git a/packages/app-lib/src/util/fetch.rs b/packages/app-lib/src/util/fetch.rs index f861f4cb0f6..ac756114142 100644 --- a/packages/app-lib/src/util/fetch.rs +++ b/packages/app-lib/src/util/fetch.rs @@ -4,9 +4,9 @@ use crate::event::LoadingBarId; use crate::event::emit::emit_loading; use crate::util::content_hash::{ContentHasher, temporary_file}; use crate::{ErrorKind, LabrinthError}; +use anyhow::Context; use bytes::Bytes; use chrono::{DateTime, TimeDelta, Utc}; -use eyre::{Context, eyre}; use governor::clock::{Clock, DefaultClock}; use governor::{DefaultDirectRateLimiter, Quota, RateLimiter}; use parking_lot::Mutex; @@ -666,11 +666,9 @@ async fn read_memory_response( .try_with(|_| ()) .is_err() { - return response - .bytes() - .await - .wrap_err_with(|| eyre!("failed to read response body from {url}")) - .map_err(Into::into); + return response.bytes().await.with_context(|| { + format!("failed to read response body from {url}") + }); } use futures::StreamExt; let total_size = total_size.unwrap_or(0); @@ -679,8 +677,8 @@ async fn read_memory_response( while let Some(chunk) = crate::install::control::download_step(stream.next()).await? { - let chunk = chunk.wrap_err_with(|| { - eyre!("failed to read response body from {url}") + let chunk = chunk.with_context(|| { + format!("failed to read response body from {url}") })?; bytes.extend_from_slice(&chunk); if let Some((bar, total)) = loading_bar diff --git a/packages/app-lib/src/util/io.rs b/packages/app-lib/src/util/io.rs index f55fa252e1b..052ea8c86fe 100644 --- a/packages/app-lib/src/util/io.rs +++ b/packages/app-lib/src/util/io.rs @@ -1,7 +1,7 @@ // IO error // A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error. -use eyre::{Context, ContextCompat, Result, eyre}; +use anyhow::{Context, Result}; use std::{ io::{ErrorKind, Write}, path::Path, @@ -187,8 +187,6 @@ pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result { { use std::os::unix::fs::MetadataExt; - use eyre::eyre; - // we need to use `symlink_metadata` instead of `metadata`, because // if this file is a symlink, we need to query the symlink file itself, // rather than the target. @@ -197,9 +195,9 @@ pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result { // this fixes moving JRE dirs. let old_meta = std::fs::symlink_metadata(old_dir) - .wrap_err_with(|| eyre!("getting meta of old dir {old_dir:?}"))?; + .with_context(|| format!("getting meta of old dir {old_dir:?}"))?; let new_meta = std::fs::symlink_metadata(new_dir) - .wrap_err_with(|| eyre!("getting meta of new dir {new_dir:?}"))?; + .with_context(|| format!("getting meta of new dir {new_dir:?}"))?; Ok(old_meta.dev() == new_meta.dev()) } @@ -207,9 +205,9 @@ pub fn is_same_disk(old_dir: &Path, new_dir: &Path) -> Result { #[cfg(windows)] { let old_dir = canonicalize(old_dir) - .wrap_err_with(|| eyre!("canonicalizing {old_dir:?}"))?; + .with_context(|| format!("canonicalizing {old_dir:?}"))?; let new_dir = canonicalize(new_dir) - .wrap_err_with(|| eyre!("canonicalizing {new_dir:?}"))?; + .with_context(|| format!("canonicalizing {new_dir:?}"))?; let old_component = old_dir.components().next(); let new_component = new_dir.components().next(); @@ -233,9 +231,9 @@ pub async fn rename_or_move( let to_parent = to .parent() - .wrap_err_with(|| eyre!("getting parent of `to` dir {to:?}"))?; - let same_disk = is_same_disk(from, to_parent).wrap_err_with(|| { - eyre!("checking if `to_parent` ({to_parent:?}) and `from` ({from:?}) are on the same disk") + .with_context(|| format!("getting parent of `to` dir {to:?}"))?; + let same_disk = is_same_disk(from, to_parent).with_context(|| { + format!("checking if `to_parent` ({to_parent:?}) and `from` ({from:?}) are on the same disk") })?; if same_disk { @@ -245,10 +243,10 @@ pub async fn rename_or_move( source: e, path: from.to_string_lossy().to_string(), }) - .wrap_err_with(|| eyre!("moving {from:?} to {to:?} on same disk")) + .with_context(|| format!("moving {from:?} to {to:?} on same disk")) } else { move_recursive(from, to).await.with_context(|| { - eyre!("moving {from:?} to {to:?} on different disks") + format!("moving {from:?} to {to:?} on different disks") }) } } @@ -258,30 +256,30 @@ async fn move_recursive(from: &Path, to: &Path) -> Result<()> { if from.is_file() { copy(from, to) .await - .wrap_err_with(|| eyre!("copying {from:?} to {to:?}"))?; - remove_file(from).await.wrap_err_with(|| { - eyre!("removing {from:?} after copying to {to:?}") + .with_context(|| format!("copying {from:?} to {to:?}"))?; + remove_file(from).await.with_context(|| { + format!("removing {from:?} after copying to {to:?}") })?; return Ok(()); } create_dir(to) .await - .wrap_err_with(|| eyre!("creating dir for {to:?}"))?; + .with_context(|| format!("creating dir for {to:?}"))?; let mut dir = read_dir(from) .await - .wrap_err_with(|| eyre!("reading dir {from:?}"))?; + .with_context(|| format!("reading dir {from:?}"))?; while let Some(entry) = dir .next_entry() .await - .wrap_err_with(|| eyre!("reading dir entry in {from:?}"))? + .with_context(|| format!("reading dir entry in {from:?}"))? { let new_path = to.join(entry.file_name()); move_recursive(&entry.path(), &new_path) .await .with_context(|| { - eyre!("moving {:?} to {new_path:?}", entry.path()) + format!("moving {:?} to {new_path:?}", entry.path()) })?; }