From 762cf03aa8899e712c3de392eaca73d6a1e883a6 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Wed, 8 Mar 2023 11:36:57 +0100 Subject: [PATCH 1/3] fix(dtmm): Strip ANSI from error message in log view Until they are implemented to color the text, they only hinder legibility. --- Cargo.lock | 45 ++++++++++++++++++++++++++++++++++++- crates/dtmm/Cargo.toml | 1 + crates/dtmm/src/util/log.rs | 3 ++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69eeed1..2709c2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,12 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.2" @@ -704,6 +710,7 @@ dependencies = [ "sdk", "serde", "serde_sjson", + "strip-ansi-escapes", "time", "tokio", "tokio-stream", @@ -1425,7 +1432,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db8c31eaef73f18e0d938785e01ab471ec73e3f90c3389e84335ade689ba953b" dependencies = [ - "arrayvec", + "arrayvec 0.7.2", "serde", ] @@ -2401,6 +2408,15 @@ dependencies = [ "regex", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.10.0" @@ -2692,6 +2708,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + [[package]] name = "unic-bidi" version = "0.9.0" @@ -2818,6 +2840,27 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "arrayvec 0.5.2", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/crates/dtmm/Cargo.toml b/crates/dtmm/Cargo.toml index a54177b..261a994 100644 --- a/crates/dtmm/Cargo.toml +++ b/crates/dtmm/Cargo.toml @@ -25,3 +25,4 @@ zip = "0.6.4" tokio-stream = { version = "0.1.12", features = ["fs"] } path-slash = "0.2.1" time = { version = "0.3.20", features = ["serde", "serde-well-known", "local-offset"] } +strip-ansi-escapes = "0.1.1" diff --git a/crates/dtmm/src/util/log.rs b/crates/dtmm/src/util/log.rs index e6a019e..1b379dc 100644 --- a/crates/dtmm/src/util/log.rs +++ b/crates/dtmm/src/util/log.rs @@ -20,7 +20,8 @@ impl ChannelWriter { impl std::io::Write for ChannelWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { let tx = self.tx.clone(); - let string = String::from_utf8_lossy(buf).to_string(); + let stripped = strip_ansi_escapes::strip(buf)?; + let string = String::from_utf8_lossy(&stripped).to_string(); // The `send` errors when the receiving end has closed. // But there's not much we can do at that point, so we just ignore it. -- 2.45.3 From 658d996315b10470660bcc78828704cf4a95bcc0 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Wed, 8 Mar 2023 20:06:07 +0100 Subject: [PATCH 2/3] feat(dtmm): Implement error dialog Closes #37. --- CHANGELOG.adoc | 1 + Cargo.lock | 1 + crates/dtmm/Cargo.toml | 1 + crates/dtmm/src/controller/app.rs | 16 ++-- crates/dtmm/src/controller/game.rs | 22 +++--- crates/dtmm/src/controller/worker.rs | 61 ++++++++++----- crates/dtmm/src/state/data.rs | 9 ++- crates/dtmm/src/state/delegate.rs | 107 +++++++++++++++++++++++---- crates/dtmm/src/ui/mod.rs | 1 + crates/dtmm/src/ui/window/dialog.rs | 36 +++++++++ crates/dtmm/src/ui/window/main.rs | 18 ++++- crates/dtmm/src/util/config.rs | 6 +- 12 files changed, 221 insertions(+), 58 deletions(-) create mode 100644 crates/dtmm/src/ui/window/dialog.rs diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 48bc35f..f61467b 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -9,6 +9,7 @@ - dtmm: indicate when a deployment is necessary - dtmm: check for Steam game update before deployment - dtmm: remove unused bundles from previous deployment +- dtmm: show dialog for critical errors === Fixed diff --git a/Cargo.lock b/Cargo.lock index 2709c2c..5f2a35a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -705,6 +705,7 @@ dependencies = [ "druid", "dtmt-shared", "futures", + "lazy_static", "oodle-sys", "path-slash", "sdk", diff --git a/crates/dtmm/Cargo.toml b/crates/dtmm/Cargo.toml index 261a994..5e11a9b 100644 --- a/crates/dtmm/Cargo.toml +++ b/crates/dtmm/Cargo.toml @@ -26,3 +26,4 @@ tokio-stream = { version = "0.1.12", features = ["fs"] } path-slash = "0.2.1" time = { version = "0.3.20", features = ["serde", "serde-well-known", "local-offset"] } strip-ansi-escapes = "0.1.1" +lazy_static = "1.4.0" diff --git a/crates/dtmm/src/controller/app.rs b/crates/dtmm/src/controller/app.rs index c2f5962..3a8adc5 100644 --- a/crates/dtmm/src/controller/app.rs +++ b/crates/dtmm/src/controller/app.rs @@ -14,13 +14,13 @@ use tokio_stream::wrappers::ReadDirStream; use tokio_stream::StreamExt; use zip::ZipArchive; -use crate::state::{ModInfo, PackageInfo, State}; +use crate::state::{ActionState, ModInfo, PackageInfo}; use crate::util::config::{ConfigSerialize, LoadOrderEntry}; use super::read_sjson_file; #[tracing::instrument(skip(state))] -pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result { +pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result { let data = fs::read(&info.path) .await .wrap_err_with(|| format!("failed to read file {}", info.path.display()))?; @@ -95,16 +95,16 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result tracing::trace!(?files); - let mod_dir = state.get_mod_dir(); + let mod_dir = state.mod_dir; tracing::trace!("Creating mods directory {}", mod_dir.display()); - fs::create_dir_all(&mod_dir) + fs::create_dir_all(Arc::as_ref(&mod_dir)) .await .wrap_err_with(|| format!("failed to create data directory {}", mod_dir.display()))?; tracing::trace!("Extracting mod archive to {}", mod_dir.display()); archive - .extract(&mod_dir) + .extract(Arc::as_ref(&mod_dir)) .wrap_err_with(|| format!("failed to extract archive to {}", mod_dir.display()))?; let packages = files @@ -117,8 +117,8 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result } #[tracing::instrument(skip(state))] -pub(crate) async fn delete_mod(state: State, info: &ModInfo) -> Result<()> { - let mod_dir = state.get_mod_dir().join(&info.id); +pub(crate) async fn delete_mod(state: ActionState, info: &ModInfo) -> Result<()> { + let mod_dir = state.mod_dir.join(&info.id); fs::remove_dir_all(&mod_dir) .await .wrap_err_with(|| format!("failed to remove directory {}", mod_dir.display()))?; @@ -127,7 +127,7 @@ pub(crate) async fn delete_mod(state: State, info: &ModInfo) -> Result<()> { } #[tracing::instrument(skip(state))] -pub(crate) async fn save_settings(state: State) -> Result<()> { +pub(crate) async fn save_settings(state: ActionState) -> Result<()> { let cfg = ConfigSerialize::from(&state); tracing::info!("Saving settings to '{}'", state.config_path.display()); diff --git a/crates/dtmm/src/controller/game.rs b/crates/dtmm/src/controller/game.rs index a69691c..9260908 100644 --- a/crates/dtmm/src/controller/game.rs +++ b/crates/dtmm/src/controller/game.rs @@ -22,7 +22,7 @@ use tokio::io::AsyncWriteExt; use tracing::Instrument; use super::read_sjson_file; -use crate::state::{PackageInfo, State}; +use crate::state::{ActionState, PackageInfo}; const MOD_BUNDLE_NAME: &str = "packages/mods"; const BOOT_BUNDLE_NAME: &str = "packages/boot"; @@ -100,7 +100,7 @@ where } #[tracing::instrument(skip_all)] -async fn patch_game_settings(state: Arc) -> Result<()> { +async fn patch_game_settings(state: Arc) -> Result<()> { let settings_path = state.game_dir.join("bundle").join(SETTINGS_FILE_PATH); let settings = read_file_with_backup(&settings_path) @@ -146,7 +146,7 @@ fn make_package(info: &PackageInfo) -> Result { Ok(pkg) } -fn build_mod_data_lua(state: Arc) -> String { +fn build_mod_data_lua(state: Arc) -> String { let mut lua = String::from("return {\n"); // DMF is handled explicitely by the loading procedures, as it actually drives most of that @@ -203,7 +203,7 @@ fn build_mod_data_lua(state: Arc) -> String { } #[tracing::instrument(skip_all)] -async fn build_bundles(state: Arc) -> Result> { +async fn build_bundles(state: Arc) -> Result> { let mut mod_bundle = Bundle::new(MOD_BUNDLE_NAME.to_string()); let mut tasks = Vec::new(); @@ -227,7 +227,7 @@ async fn build_bundles(state: Arc) -> Result> { let span = tracing::trace_span!("building mod packages", name = mod_info.name); let _enter = span.enter(); - let mod_dir = state.get_mod_dir().join(&mod_info.id); + let mod_dir = state.mod_dir.join(&mod_info.id); for pkg_info in &mod_info.packages { let span = tracing::trace_span!("building package", name = pkg_info.name); let _enter = span.enter(); @@ -320,7 +320,7 @@ async fn build_bundles(state: Arc) -> Result> { } #[tracing::instrument(skip_all)] -async fn patch_boot_bundle(state: Arc) -> Result> { +async fn patch_boot_bundle(state: Arc) -> Result> { let bundle_dir = Arc::new(state.game_dir.join("bundle")); let bundle_path = bundle_dir.join(format!("{:x}", Murmur64::hash(BOOT_BUNDLE_NAME.as_bytes()))); @@ -381,7 +381,7 @@ async fn patch_boot_bundle(state: Arc) -> Result> { let bundle_name = Murmur64::hash(&pkg_info.name) .to_string() .to_ascii_lowercase(); - let src = state.get_mod_dir().join(&mod_info.id).join(&bundle_name); + let src = state.mod_dir.join(&mod_info.id).join(&bundle_name); { let bin = fs::read(&src) @@ -461,7 +461,7 @@ async fn patch_boot_bundle(state: Arc) -> Result> { } #[tracing::instrument(skip_all, fields(bundles = bundles.as_ref().len()))] -async fn patch_bundle_database(state: Arc, bundles: B) -> Result<()> +async fn patch_bundle_database(state: Arc, bundles: B) -> Result<()> where B: AsRef<[Bundle]>, { @@ -499,7 +499,7 @@ where } #[tracing::instrument(skip_all, fields(bundles = bundles.as_ref().len()))] -async fn write_deployment_data(state: Arc, bundles: B) -> Result<()> +async fn write_deployment_data(state: Arc, bundles: B) -> Result<()> where B: AsRef<[Bundle]>, { @@ -525,7 +525,7 @@ where game_dir = %state.game_dir.display(), mods = state.mods.len() ))] -pub(crate) async fn deploy_mods(state: State) -> Result<()> { +pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> { let state = Arc::new(state); { @@ -643,7 +643,7 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> { } #[tracing::instrument(skip(state))] -pub(crate) async fn reset_mod_deployment(state: State) -> Result<()> { +pub(crate) async fn reset_mod_deployment(state: ActionState) -> Result<()> { let boot_bundle_path = format!("{:016x}", Murmur64::hash(BOOT_BUNDLE_NAME.as_bytes())); let paths = [BUNDLE_DATABASE_NAME, &boot_bundle_path, SETTINGS_FILE_PATH]; let bundle_dir = state.game_dir.join("bundle"); diff --git a/crates/dtmm/src/controller/worker.rs b/crates/dtmm/src/controller/worker.rs index 49785b4..294af0e 100644 --- a/crates/dtmm/src/controller/worker.rs +++ b/crates/dtmm/src/controller/worker.rs @@ -1,5 +1,8 @@ use std::sync::Arc; +use color_eyre::eyre::Context; +use color_eyre::Help; +use color_eyre::Report; use color_eyre::Result; use druid::{ExtEventSink, SingleUse, Target}; use tokio::runtime::Runtime; @@ -10,11 +13,19 @@ use crate::controller::app::*; use crate::controller::game::*; use crate::state::AsyncAction; use crate::state::ACTION_FINISH_SAVE_SETTINGS; +use crate::state::ACTION_SHOW_ERROR_DIALOG; use crate::state::{ ACTION_FINISH_ADD_MOD, ACTION_FINISH_DELETE_SELECTED_MOD, ACTION_FINISH_DEPLOY, ACTION_FINISH_RESET_DEPLOYMENT, ACTION_LOG, }; +async fn send_error(sink: Arc>, err: Report) { + sink.write() + .await + .submit_command(ACTION_SHOW_ERROR_DIALOG, SingleUse::new(err), Target::Auto) + .expect("failed to send command"); +} + async fn handle_action( event_sink: Arc>, action_queue: Arc>>, @@ -23,8 +34,9 @@ async fn handle_action( let event_sink = event_sink.clone(); match action { AsyncAction::DeployMods(state) => tokio::spawn(async move { - if let Err(err) = deploy_mods(state).await { - tracing::error!("Failed to deploy mods: {:?}", err); + if let Err(err) = deploy_mods(state).await.wrap_err("failed to deploy mods") { + tracing::error!("{:?}", err); + send_error(event_sink.clone(), err).await; } event_sink @@ -33,8 +45,11 @@ async fn handle_action( .submit_command(ACTION_FINISH_DEPLOY, (), Target::Auto) .expect("failed to send command"); }), - AsyncAction::AddMod((state, info)) => tokio::spawn(async move { - match import_mod(state, info).await { + AsyncAction::AddMod(state, info) => tokio::spawn(async move { + match import_mod(state, info) + .await + .wrap_err("failed to import mod") + { Ok(mod_info) => { event_sink .write() @@ -47,18 +62,22 @@ async fn handle_action( .expect("failed to send command"); } Err(err) => { - tracing::error!("Failed to import mod: {:?}", err); + tracing::error!("{:?}", err); + send_error(event_sink.clone(), err).await; } } }), - AsyncAction::DeleteMod((state, info)) => tokio::spawn(async move { - if let Err(err) = delete_mod(state, &info).await { - tracing::error!( - "Failed to delete mod files. \ - You might want to clean up the data directory manually. \ - Reason: {:?}", - err - ); + AsyncAction::DeleteMod(state, info) => tokio::spawn(async move { + let mod_dir = state.mod_dir.join(&info.id); + if let Err(err) = delete_mod(state, &info) + .await + .wrap_err("failed to delete mod files") + .with_suggestion(|| { + format!("Clean the folder '{}' manually", mod_dir.display()) + }) + { + tracing::error!("{:?}", err); + send_error(event_sink.clone(), err).await; } event_sink @@ -72,8 +91,12 @@ async fn handle_action( .expect("failed to send command"); }), AsyncAction::ResetDeployment(state) => tokio::spawn(async move { - if let Err(err) = reset_mod_deployment(state).await { - tracing::error!("Failed to reset mod deployment: {:?}", err); + if let Err(err) = reset_mod_deployment(state) + .await + .wrap_err("failed to reset mod deployment") + { + tracing::error!("{:?}", err); + send_error(event_sink.clone(), err).await; } event_sink @@ -83,8 +106,12 @@ async fn handle_action( .expect("failed to send command"); }), AsyncAction::SaveSettings(state) => tokio::spawn(async move { - if let Err(err) = save_settings(state).await { - tracing::error!("Failed to save settings: {:?}", err); + if let Err(err) = save_settings(state) + .await + .wrap_err("failed to save settings") + { + tracing::error!("{:?}", err); + send_error(event_sink.clone(), err).await; } event_sink diff --git a/crates/dtmm/src/state/data.rs b/crates/dtmm/src/state/data.rs index 65156a3..8021840 100644 --- a/crates/dtmm/src/state/data.rs +++ b/crates/dtmm/src/state/data.rs @@ -1,6 +1,9 @@ use std::{path::PathBuf, sync::Arc}; -use druid::{im::Vector, Data, Lens}; +use druid::{ + im::{HashMap, Vector}, + Data, Lens, WindowHandle, WindowId, +}; use dtmt_shared::ModConfig; use super::SelectedModLens; @@ -86,6 +89,9 @@ pub(crate) struct State { pub config_path: Arc, #[lens(ignore)] #[data(ignore)] + pub windows: HashMap, + #[lens(ignore)] + #[data(ignore)] pub ctx: Arc, } @@ -110,6 +116,7 @@ impl State { game_dir: Arc::new(game_dir), data_dir: Arc::new(data_dir), log: Arc::new(String::new()), + windows: HashMap::new(), } } diff --git a/crates/dtmm/src/state/delegate.rs b/crates/dtmm/src/state/delegate.rs index 6b05b96..506cbf8 100644 --- a/crates/dtmm/src/state/delegate.rs +++ b/crates/dtmm/src/state/delegate.rs @@ -1,10 +1,14 @@ -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; +use color_eyre::Report; use druid::{ - AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse, Target, + im::Vector, AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse, + Target, WindowHandle, WindowId, }; use tokio::sync::mpsc::UnboundedSender; +use crate::ui::window; + use super::{ModInfo, State}; pub(crate) const ACTION_SELECT_MOD: Selector = Selector::new("dtmm.action.select-mod"); @@ -37,12 +41,42 @@ pub(crate) const ACTION_FINISH_SAVE_SETTINGS: Selector = pub(crate) const ACTION_SET_DIRTY: Selector = Selector::new("dtmm.action.set-dirty"); +pub(crate) const ACTION_SHOW_ERROR_DIALOG: Selector> = + Selector::new("dtmm.action.show-error-dialog"); + +pub(crate) const ACTION_SET_WINDOW_HANDLE: Selector> = + Selector::new("dtmm.action.set-window-handle"); + +// A sub-selection of `State`'s fields that are required in `AsyncAction`s and that are +// `Send + Sync` +pub(crate) struct ActionState { + pub mods: Vector>, + pub game_dir: Arc, + pub data_dir: Arc, + pub mod_dir: Arc, + pub config_path: Arc, + pub ctx: Arc, +} + +impl From for ActionState { + fn from(state: State) -> Self { + Self { + mods: state.mods, + game_dir: state.game_dir, + mod_dir: Arc::new(state.data_dir.join("mods")), + data_dir: state.data_dir, + config_path: state.config_path, + ctx: state.ctx, + } + } +} + pub(crate) enum AsyncAction { - DeployMods(State), - ResetDeployment(State), - AddMod((State, FileInfo)), - DeleteMod((State, Arc)), - SaveSettings(State), + DeployMods(ActionState), + ResetDeployment(ActionState), + AddMod(ActionState, FileInfo), + DeleteMod(ActionState, Arc), + SaveSettings(ActionState), } pub(crate) struct Delegate { @@ -73,7 +107,7 @@ impl AppDelegate for Delegate { cmd if cmd.is(ACTION_START_DEPLOY) => { if self .sender - .send(AsyncAction::DeployMods(state.clone())) + .send(AsyncAction::DeployMods(state.clone().into())) .is_ok() { state.is_deployment_in_progress = true; @@ -91,7 +125,7 @@ impl AppDelegate for Delegate { cmd if cmd.is(ACTION_START_RESET_DEPLOYMENT) => { if self .sender - .send(AsyncAction::ResetDeployment(state.clone())) + .send(AsyncAction::ResetDeployment(state.clone().into())) .is_ok() { state.is_reset_in_progress = true; @@ -147,11 +181,12 @@ impl AppDelegate for Delegate { cmd if cmd.is(ACTION_START_DELETE_SELECTED_MOD) => { let info = cmd .get(ACTION_START_DELETE_SELECTED_MOD) - .and_then(|info| info.take()) + .and_then(SingleUse::take) .expect("command type matched but didn't contain the expected value"); + if self .sender - .send(AsyncAction::DeleteMod((state.clone(), info))) + .send(AsyncAction::DeleteMod(state.clone().into(), info)) .is_err() { tracing::error!("Failed to queue action to deploy mods"); @@ -162,8 +197,9 @@ impl AppDelegate for Delegate { cmd if cmd.is(ACTION_FINISH_DELETE_SELECTED_MOD) => { let info = cmd .get(ACTION_FINISH_DELETE_SELECTED_MOD) - .and_then(|info| info.take()) + .and_then(SingleUse::take) .expect("command type matched but didn't contain the expected value"); + let found = state.mods.iter().enumerate().find(|(_, i)| i.id == info.id); let Some((index, _)) = found else { return Handled::No; @@ -177,9 +213,10 @@ impl AppDelegate for Delegate { let info = cmd .get(ACTION_ADD_MOD) .expect("command type matched but didn't contain the expected value"); + if self .sender - .send(AsyncAction::AddMod((state.clone(), info.clone()))) + .send(AsyncAction::AddMod(state.clone().into(), info.clone())) .is_err() { tracing::error!("Failed to queue action to add mod"); @@ -190,9 +227,11 @@ impl AppDelegate for Delegate { let info = cmd .get(ACTION_FINISH_ADD_MOD) .expect("command type matched but didn't contain the expected value"); + if let Some(info) = info.take() { state.add_mod(info); } + Handled::Yes } cmd if cmd.is(ACTION_LOG) => { @@ -209,7 +248,7 @@ impl AppDelegate for Delegate { state.is_next_save_pending = true; } else if self .sender - .send(AsyncAction::SaveSettings(state.clone())) + .send(AsyncAction::SaveSettings(state.clone().into())) .is_ok() { state.is_save_in_progress = true; @@ -233,6 +272,31 @@ impl AppDelegate for Delegate { state.dirty = true; Handled::Yes } + cmd if cmd.is(ACTION_SHOW_ERROR_DIALOG) => { + let err = cmd + .get(ACTION_SHOW_ERROR_DIALOG) + .and_then(SingleUse::take) + .expect("command type matched but didn't contain the expected value"); + + let window = state + .windows + .get(&window::main::WINDOW_ID) + .expect("root window does not exist"); + + let dialog = window::dialog::error::(err, window.clone()); + ctx.new_window(dialog); + + Handled::Yes + } + cmd if cmd.is(ACTION_SET_WINDOW_HANDLE) => { + let (id, handle) = cmd + .get(ACTION_SET_WINDOW_HANDLE) + .and_then(SingleUse::take) + .expect("command type matched but didn't contain the expected value"); + + state.windows.insert(id, handle); + Handled::Yes + } cmd => { if cfg!(debug_assertions) { tracing::warn!("Unknown command: {:?}", cmd); @@ -241,4 +305,19 @@ impl AppDelegate for Delegate { } } } + + fn window_added( + &mut self, + id: WindowId, + handle: WindowHandle, + data: &mut State, + _: &Env, + _: &mut DelegateCtx, + ) { + data.windows.insert(id, handle); + } + + fn window_removed(&mut self, id: WindowId, data: &mut State, _: &Env, _: &mut DelegateCtx) { + data.windows.remove(&id); + } } diff --git a/crates/dtmm/src/ui/mod.rs b/crates/dtmm/src/ui/mod.rs index cf8554f..12f66c9 100644 --- a/crates/dtmm/src/ui/mod.rs +++ b/crates/dtmm/src/ui/mod.rs @@ -1,5 +1,6 @@ pub mod theme; pub mod widget; pub mod window { + pub mod dialog; pub mod main; } diff --git a/crates/dtmm/src/ui/window/dialog.rs b/crates/dtmm/src/ui/window/dialog.rs new file mode 100644 index 0000000..3b6802a --- /dev/null +++ b/crates/dtmm/src/ui/window/dialog.rs @@ -0,0 +1,36 @@ +use color_eyre::Report; +use druid::widget::{Button, CrossAxisAlignment, Flex, Label, LineBreaking, MainAxisAlignment}; +use druid::{Data, WidgetExt, WindowDesc, WindowHandle, WindowLevel, WindowSizePolicy}; + +const ERROR_DIALOG_SIZE: (f64, f64) = (750., 400.); + +pub fn error(err: Report, parent: WindowHandle) -> WindowDesc { + let msg = format!("A critical error ocurred: {:?}", err); + let stripped = + strip_ansi_escapes::strip(msg.as_bytes()).expect("failed to strip ANSI in error"); + let msg = String::from_utf8_lossy(&stripped); + + let text = Label::new(msg.to_string()).with_line_break_mode(LineBreaking::WordWrap); + + let button = Button::new("Ok") + .on_click(|ctx, _, _| { + ctx.window().close(); + }) + .align_right(); + + let widget = Flex::column() + .main_axis_alignment(MainAxisAlignment::SpaceBetween) + .cross_axis_alignment(CrossAxisAlignment::End) + .with_child(text) + .with_spacer(20.) + .with_child(button) + .padding(10.); + + WindowDesc::new(widget) + .title("Error") + .with_min_size(ERROR_DIALOG_SIZE) + .resizable(false) + .window_size_policy(WindowSizePolicy::Content) + .set_always_on_top(true) + .set_level(WindowLevel::Modal(parent)) +} diff --git a/crates/dtmm/src/ui/window/main.rs b/crates/dtmm/src/ui/window/main.rs index d500610..f48b5c3 100644 --- a/crates/dtmm/src/ui/window/main.rs +++ b/crates/dtmm/src/ui/window/main.rs @@ -1,25 +1,30 @@ use std::sync::Arc; use druid::im::Vector; -use druid::lens; use druid::widget::{ Button, Checkbox, CrossAxisAlignment, Flex, Label, LineBreaking, List, MainAxisAlignment, Maybe, Scroll, SizedBox, Split, TextBox, ViewSwitcher, }; +use druid::{lens, LifeCycleCtx}; use druid::{ Color, FileDialogOptions, FileSpec, FontDescriptor, FontFamily, Key, LensExt, SingleUse, - TextAlignment, Widget, WidgetExt, WindowDesc, + TextAlignment, Widget, WidgetExt, WindowDesc, WindowId, }; +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_START_DELETE_SELECTED_MOD, ACTION_START_DEPLOY, - ACTION_START_RESET_DEPLOYMENT, + ACTION_SELECT_MOD, ACTION_SET_WINDOW_HANDLE, ACTION_START_DELETE_SELECTED_MOD, + ACTION_START_DEPLOY, ACTION_START_RESET_DEPLOYMENT, }; use crate::ui::theme; use crate::ui::widget::controller::{AutoScrollController, DirtyStateController}; use crate::ui::widget::PathBufFormatter; +lazy_static! { + pub static ref WINDOW_ID: WindowId = WindowId::next(); +} + const TITLE: &str = "Darktide Mod Manager"; const WINDOW_SIZE: (f64, f64) = (1080., 720.); const MOD_DETAILS_MIN_WIDTH: f64 = 325.; @@ -324,4 +329,9 @@ fn build_window() -> impl Widget { .with_flex_child(build_main(), 1.0) .with_child(build_log_view()) .controller(DirtyStateController) + .on_added(|_, ctx: &mut LifeCycleCtx, _, _| { + ctx.submit_command( + ACTION_SET_WINDOW_HANDLE.with(SingleUse::new((*WINDOW_ID, ctx.window().clone()))), + ); + }) } diff --git a/crates/dtmm/src/util/config.rs b/crates/dtmm/src/util/config.rs index 2c942ea..41fef73 100644 --- a/crates/dtmm/src/util/config.rs +++ b/crates/dtmm/src/util/config.rs @@ -7,7 +7,7 @@ use clap::{parser::ValueSource, ArgMatches}; use color_eyre::{eyre::Context, Result}; use serde::{Deserialize, Serialize}; -use crate::state::{ModInfo, State}; +use crate::state::{ActionState, ModInfo}; #[derive(Clone, Debug, Serialize)] pub(crate) struct LoadOrderEntrySerialize<'a> { @@ -31,8 +31,8 @@ pub(crate) struct ConfigSerialize<'a> { mod_order: Vec>, } -impl<'a> From<&'a State> for ConfigSerialize<'a> { - fn from(state: &'a State) -> Self { +impl<'a> From<&'a ActionState> for ConfigSerialize<'a> { + fn from(state: &'a ActionState) -> Self { Self { game_dir: &state.game_dir, data_dir: &state.data_dir, -- 2.45.3 From a8db19cf9faad94814f3f3f2620a954f56d7202a Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Wed, 8 Mar 2023 20:34:45 +0100 Subject: [PATCH 3/3] refactor: Capitalize error messages --- crates/dtmm/src/controller/app.rs | 38 ++++++------ crates/dtmm/src/controller/game.rs | 90 ++++++++++++++-------------- crates/dtmm/src/controller/mod.rs | 6 +- crates/dtmm/src/controller/worker.rs | 10 ++-- crates/dtmm/src/main.rs | 4 +- crates/dtmm/src/util/config.rs | 12 ++-- crates/dtmt/src/cmd/build.rs | 32 +++++----- crates/dtmt/src/cmd/bundle/inject.rs | 8 +-- crates/dtmt/src/cmd/bundle/list.rs | 2 +- crates/dtmt/src/cmd/dictionary.rs | 2 +- crates/dtmt/src/cmd/new.rs | 6 +- crates/dtmt/src/cmd/package.rs | 10 ++-- crates/dtmt/src/main.rs | 4 +- lib/sdk/src/bundle/file.rs | 12 ++-- lib/sdk/src/bundle/mod.rs | 4 +- lib/sdk/src/filetype/lua.rs | 2 +- lib/sdk/src/filetype/package.rs | 4 +- lib/sdk/src/murmur/mod.rs | 4 +- 18 files changed, 125 insertions(+), 125 deletions(-) diff --git a/crates/dtmm/src/controller/app.rs b/crates/dtmm/src/controller/app.rs index 3a8adc5..ce8f2a2 100644 --- a/crates/dtmm/src/controller/app.rs +++ b/crates/dtmm/src/controller/app.rs @@ -23,10 +23,10 @@ use super::read_sjson_file; pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result { let data = fs::read(&info.path) .await - .wrap_err_with(|| format!("failed to read file {}", info.path.display()))?; + .wrap_err_with(|| format!("Failed to read file {}", info.path.display()))?; let data = Cursor::new(data); - let mut archive = ZipArchive::new(data).wrap_err("failed to open ZIP archive")?; + let mut archive = ZipArchive::new(data).wrap_err("Failed to open ZIP archive")?; if tracing::enabled!(tracing::Level::DEBUG) { let names = archive.file_names().fold(String::new(), |mut s, name| { @@ -38,7 +38,7 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result Result Result Result Result<()> let mod_dir = state.mod_dir.join(&info.id); fs::remove_dir_all(&mod_dir) .await - .wrap_err_with(|| format!("failed to remove directory {}", mod_dir.display()))?; + .wrap_err_with(|| format!("Failed to remove directory {}", mod_dir.display()))?; Ok(()) } @@ -133,7 +133,7 @@ pub(crate) async fn save_settings(state: ActionState) -> Result<()> { tracing::info!("Saving settings to '{}'", state.config_path.display()); tracing::debug!(?cfg); - let data = serde_sjson::to_string(&cfg).wrap_err("failed to serialize config")?; + let data = serde_sjson::to_string(&cfg).wrap_err("Failed to serialize config")?; fs::write(state.config_path.as_ref(), &data) .await @@ -155,11 +155,11 @@ async fn read_mod_dir_entry(res: Result) -> Result { let cfg: ModConfig = read_sjson_file(&config_path) .await - .wrap_err_with(|| format!("failed to read mod config '{}'", config_path.display()))?; + .wrap_err_with(|| format!("Failed to read mod config '{}'", config_path.display()))?; let files: HashMap> = read_sjson_file(&index_path) .await - .wrap_err_with(|| format!("failed to read file index '{}'", index_path.display()))?; + .wrap_err_with(|| format!("Failed to read file index '{}'", index_path.display()))?; let packages = files .into_iter() @@ -186,12 +186,12 @@ where } Err(err) => { return Err(err) - .wrap_err_with(|| format!("failed to open directory '{}'", mod_dir.display())); + .wrap_err_with(|| format!("Failed to open directory '{}'", mod_dir.display())); } }; let stream = ReadDirStream::new(read_dir) - .map(|res| res.wrap_err("failed to read dir entry")) + .map(|res| res.wrap_err("Failed to read dir entry")) .then(read_mod_dir_entry); tokio::pin!(stream); diff --git a/crates/dtmm/src/controller/game.rs b/crates/dtmm/src/controller/game.rs index 9260908..62502c4 100644 --- a/crates/dtmm/src/controller/game.rs +++ b/crates/dtmm/src/controller/game.rs @@ -74,7 +74,7 @@ where ); fs::copy(path, &backup_path).await.wrap_err_with(|| { format!( - "failed to back up {} '{}' to '{}'", + "Failed to back up {} '{}' to '{}'", file_name, path.display(), backup_path.display() @@ -83,13 +83,13 @@ where tracing::debug!("Reading {} from original '{}'", file_name, path.display()); fs::read(path).await.wrap_err_with(|| { - format!("failed to read {} file: {}", file_name, path.display()) + format!("Failed to read {} file: {}", file_name, path.display()) })? } Err(err) => { return Err(err).wrap_err_with(|| { format!( - "failed to read {} from backup '{}'", + "Failed to read {} from backup '{}'", file_name, backup_path.display() ) @@ -105,12 +105,12 @@ async fn patch_game_settings(state: Arc) -> Result<()> { let settings = read_file_with_backup(&settings_path) .await - .wrap_err("failed to read settings.ini")?; - let settings = String::from_utf8(settings).wrap_err("settings.ini is not valid UTF-8")?; + .wrap_err("Failed to read settings.ini")?; + let settings = String::from_utf8(settings).wrap_err("Settings.ini is not valid UTF-8")?; let mut f = fs::File::create(&settings_path) .await - .wrap_err_with(|| format!("failed to open {}", settings_path.display()))?; + .wrap_err_with(|| format!("Failed to open {}", settings_path.display()))?; let Some(i) = settings.find("boot_script =") else { eyre::bail!("couldn't find 'boot_script' field"); @@ -138,7 +138,7 @@ fn make_package(info: &PackageInfo) -> Result { .next() .ok_or_else(|| eyre::eyre!("missing file extension")) .and_then(BundleFileType::from_str) - .wrap_err("invalid file name in package info")?; + .wrap_err("Invalid file name in package info")?; let name: String = it.collect(); pkg.add_file(file_type, name); } @@ -216,9 +216,9 @@ async fn build_bundles(state: Arc) -> Result> { let _enter = span.enter(); let lua = build_mod_data_lua(state.clone()); - let lua = CString::new(lua).wrap_err("failed to build CString from mod data Lua string")?; + let lua = CString::new(lua).wrap_err("Failed to build CString from mod data Lua string")?; let file = - lua::compile(MOD_DATA_SCRIPT, &lua).wrap_err("failed to compile mod data Lua file")?; + lua::compile(MOD_DATA_SCRIPT, &lua).wrap_err("Failed to compile mod data Lua file")?; mod_bundle.add_file(file); } @@ -232,11 +232,11 @@ async fn build_bundles(state: Arc) -> Result> { let span = tracing::trace_span!("building package", name = pkg_info.name); let _enter = span.enter(); - let pkg = make_package(pkg_info).wrap_err("failed to make package")?; + let pkg = make_package(pkg_info).wrap_err("Failed to make package")?; let mut variant = BundleFileVariant::new(); let bin = pkg .to_binary() - .wrap_err("failed to serialize package to binary")?; + .wrap_err("Failed to serialize package to binary")?; variant.set_data(bin); let mut file = BundleFile::new(pkg_info.name.clone(), BundleFileType::Package); file.add_variant(variant); @@ -260,11 +260,11 @@ async fn build_bundles(state: Arc) -> Result> { let task = async move { let bundle = { let bin = fs::read(&src).await.wrap_err_with(|| { - format!("failed to read bundle file '{}'", src.display()) + format!("Failed to read bundle file '{}'", src.display()) })?; let name = Bundle::get_name_from_path(&ctx, &src); Bundle::from_binary(&ctx, name, bin) - .wrap_err_with(|| format!("failed to parse bundle '{}'", src.display()))? + .wrap_err_with(|| format!("Failed to parse bundle '{}'", src.display()))? }; tracing::debug!( @@ -283,7 +283,7 @@ async fn build_bundles(state: Arc) -> Result> { let _ = fs::remove_file(&dest).await; fs::copy(&src, &dest).await.wrap_err_with(|| { format!( - "failed to copy bundle {pkg_name} for mod {mod_name}. src: {}, dest: {}", + "Failed to copy bundle {pkg_name} for mod {mod_name}. Src: {}, dest: {}", src.display(), dest.display() ) @@ -311,7 +311,7 @@ async fn build_bundles(state: Arc) -> Result> { tracing::trace!("Writing mod bundle to '{}'", path.display()); fs::write(&path, mod_bundle.to_binary()?) .await - .wrap_err_with(|| format!("failed to write bundle to '{}'", path.display()))?; + .wrap_err_with(|| format!("Failed to write bundle to '{}'", path.display()))?; } bundles.push(mod_bundle); @@ -329,14 +329,14 @@ async fn patch_boot_bundle(state: Arc) -> Result> { let mut boot_bundle = async { let bin = read_file_with_backup(&bundle_path) .await - .wrap_err("failed to read boot bundle")?; + .wrap_err("Failed to read boot bundle")?; Bundle::from_binary(&state.ctx, BOOT_BUNDLE_NAME.to_string(), bin) - .wrap_err("failed to parse boot bundle") + .wrap_err("Failed to parse boot bundle") } .instrument(tracing::trace_span!("read boot bundle")) .await - .wrap_err_with(|| format!("failed to read bundle '{}'", BOOT_BUNDLE_NAME))?; + .wrap_err_with(|| format!("Failed to read bundle '{}'", BOOT_BUNDLE_NAME))?; { tracing::trace!("Adding mod package file to boot bundle"); @@ -386,11 +386,11 @@ async fn patch_boot_bundle(state: Arc) -> Result> { { let bin = fs::read(&src) .await - .wrap_err_with(|| format!("failed to read bundle file '{}'", src.display()))?; + .wrap_err_with(|| format!("Failed to read bundle file '{}'", src.display()))?; let name = Bundle::get_name_from_path(&state.ctx, &src); let dml_bundle = Bundle::from_binary(&state.ctx, name, bin) - .wrap_err_with(|| format!("failed to parse bundle '{}'", src.display()))?; + .wrap_err_with(|| format!("Failed to parse bundle '{}'", src.display()))?; bundles.push(dml_bundle); }; @@ -416,14 +416,14 @@ async fn patch_boot_bundle(state: Arc) -> Result> { let _ = fs::remove_file(&dest).await; fs::copy(&src, &dest).await.wrap_err_with(|| { format!( - "failed to copy bundle {pkg_name} for mod {mod_name}. src: {}, dest: {}", + "Failed to copy bundle {pkg_name} for mod {mod_name}. Src: {}, dest: {}", src.display(), dest.display() ) })?; } - let pkg = make_package(pkg_info).wrap_err("failed to create package file for dml")?; + let pkg = make_package(pkg_info).wrap_err("Failed to create package file for dml")?; variant.set_data(pkg.to_binary()?); let mut f = BundleFile::new(DML_BUNDLE_NAME.to_string(), BundleFileType::Package); @@ -437,9 +437,9 @@ async fn patch_boot_bundle(state: Arc) -> Result> { let _enter = span.enter(); let lua = include_str!("../../assets/mod_main.lua"); - let lua = CString::new(lua).wrap_err("failed to build CString from mod main Lua string")?; + let lua = CString::new(lua).wrap_err("Failed to build CString from mod main Lua string")?; let file = - lua::compile(MOD_BOOT_SCRIPT, &lua).wrap_err("failed to compile mod main Lua file")?; + lua::compile(MOD_BOOT_SCRIPT, &lua).wrap_err("Failed to compile mod main Lua file")?; boot_bundle.add_file(file); } @@ -447,10 +447,10 @@ async fn patch_boot_bundle(state: Arc) -> Result> { async { let bin = boot_bundle .to_binary() - .wrap_err("failed to serialize boot bundle")?; + .wrap_err("Failed to serialize boot bundle")?; fs::write(&bundle_path, bin) .await - .wrap_err_with(|| format!("failed to write main bundle: {}", bundle_path.display())) + .wrap_err_with(|| format!("Failed to write main bundle: {}", bundle_path.display())) } .instrument(tracing::trace_span!("write boot bundle")) .await?; @@ -471,9 +471,9 @@ where let mut db = { let bin = read_file_with_backup(&database_path) .await - .wrap_err("failed to read bundle database")?; + .wrap_err("Failed to read bundle database")?; let mut r = Cursor::new(bin); - let db = BundleDatabase::from_binary(&mut r).wrap_err("failed to parse bundle database")?; + let db = BundleDatabase::from_binary(&mut r).wrap_err("Failed to parse bundle database")?; tracing::trace!("Finished parsing bundle database"); db }; @@ -486,7 +486,7 @@ where { let bin = db .to_binary() - .wrap_err("failed to serialize bundle database")?; + .wrap_err("Failed to serialize bundle database")?; fs::write(&database_path, bin).await.wrap_err_with(|| { format!( "failed to write bundle database to '{}'", @@ -512,11 +512,11 @@ where .collect(), }; let path = state.game_dir.join(DEPLOYMENT_DATA_PATH); - let data = serde_sjson::to_string(&info).wrap_err("failed to serizalie deployment data")?; + let data = serde_sjson::to_string(&info).wrap_err("Failed to serizalie deployment data")?; fs::write(&path, &data) .await - .wrap_err_with(|| format!("failed to write deployment data to '{}'", path.display()))?; + .wrap_err_with(|| format!("Failed to write deployment data to '{}'", path.display()))?; Ok(()) } @@ -552,15 +552,15 @@ pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> { if let Some(err) = err.downcast_ref::() && err.kind() == ErrorKind::NotFound { Ok(None) } else { - Err(err).wrap_err("failed to read deployment data") + Err(err).wrap_err("Failed to read deployment data") } } } } ) - .wrap_err("failed to gather deployment information")?; + .wrap_err("Failed to gather deployment information")?; - let game_info = game_info.wrap_err("failed to collect Steam info")?; + let game_info = game_info.wrap_err("Failed to collect Steam info")?; tracing::debug!(?game_info, ?deployment_info); @@ -581,12 +581,12 @@ pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> { tracing::info!("Build mod bundles"); let mut bundles = build_bundles(state.clone()) .await - .wrap_err("failed to build mod bundles")?; + .wrap_err("Failed to build mod bundles")?; tracing::info!("Patch boot bundle"); let mut more_bundles = patch_boot_bundle(state.clone()) .await - .wrap_err("failed to patch boot bundle")?; + .wrap_err("Failed to patch boot bundle")?; bundles.append(&mut more_bundles); if let Some(info) = &deployment_info { @@ -609,7 +609,7 @@ pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> { tracing::debug!("Removing unused bundle '{}'", file_name); if let Err(err) = fs::remove_file(&path).await.wrap_err_with(|| { - format!("failed to remove unused bundle '{}'", path.display()) + format!("Failed to remove unused bundle '{}'", path.display()) }) { tracing::error!("{:?}", err); } @@ -626,17 +626,17 @@ pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> { tracing::info!("Patch game settings"); patch_game_settings(state.clone()) .await - .wrap_err("failed to patch game settings")?; + .wrap_err("Failed to patch game settings")?; tracing::info!("Patching bundle database"); patch_bundle_database(state.clone(), &bundles) .await - .wrap_err("failed to patch bundle database")?; + .wrap_err("Failed to patch bundle database")?; tracing::info!("Writing deployment data"); write_deployment_data(state.clone(), &bundles) .await - .wrap_err("failed to write deployment data")?; + .wrap_err("Failed to write deployment data")?; tracing::info!("Finished deploying mods"); Ok(()) @@ -662,14 +662,14 @@ pub(crate) async fn reset_mod_deployment(state: ActionState) -> Result<()> { } Err(err) => { return Err(err).wrap_err_with(|| { - format!("failed to read deployment info at '{}'", path.display()) + format!("Failed to read deployment info at '{}'", path.display()) }); } }; - let data = String::from_utf8(data).wrap_err("invalid UTF8 in deployment data")?; + let data = String::from_utf8(data).wrap_err("Invalid UTF8 in deployment data")?; - serde_sjson::from_str(&data).wrap_err("invalid SJSON in deployment data")? + serde_sjson::from_str(&data).wrap_err("Invalid SJSON in deployment data")? }; for name in info.bundles { @@ -697,7 +697,7 @@ pub(crate) async fn reset_mod_deployment(state: ActionState) -> Result<()> { fs::copy(&backup, &path) .await - .wrap_err_with(|| format!("failed to copy from '{}'", backup.display()))?; + .wrap_err_with(|| format!("Failed to copy from '{}'", backup.display()))?; tracing::debug!("Deleting backup: {}", backup.display()); @@ -705,7 +705,7 @@ pub(crate) async fn reset_mod_deployment(state: ActionState) -> Result<()> { Ok(_) => Ok(()), Err(err) if err.kind() == ErrorKind::NotFound => Ok(()), Err(err) => { - Err(err).wrap_err_with(|| format!("failed to remove '{}'", backup.display())) + Err(err).wrap_err_with(|| format!("Failed to remove '{}'", backup.display())) } } } diff --git a/crates/dtmm/src/controller/mod.rs b/crates/dtmm/src/controller/mod.rs index f7f250d..eacc3ef 100644 --- a/crates/dtmm/src/controller/mod.rs +++ b/crates/dtmm/src/controller/mod.rs @@ -17,7 +17,7 @@ where let path = path.as_ref(); let buf = fs::read(path) .await - .wrap_err_with(|| format!("failed to read file '{}'", path.display()))?; - let data = String::from_utf8(buf).wrap_err("invalid UTF8")?; - serde_sjson::from_str(&data).wrap_err("failed to deserialize SJSON") + .wrap_err_with(|| format!("Failed to read file '{}'", path.display()))?; + let data = String::from_utf8(buf).wrap_err("Invalid UTF8")?; + serde_sjson::from_str(&data).wrap_err("Failed to deserialize SJSON") } diff --git a/crates/dtmm/src/controller/worker.rs b/crates/dtmm/src/controller/worker.rs index 294af0e..48e9c1e 100644 --- a/crates/dtmm/src/controller/worker.rs +++ b/crates/dtmm/src/controller/worker.rs @@ -34,7 +34,7 @@ async fn handle_action( let event_sink = event_sink.clone(); match action { AsyncAction::DeployMods(state) => tokio::spawn(async move { - if let Err(err) = deploy_mods(state).await.wrap_err("failed to deploy mods") { + if let Err(err) = deploy_mods(state).await.wrap_err("Failed to deploy mods") { tracing::error!("{:?}", err); send_error(event_sink.clone(), err).await; } @@ -48,7 +48,7 @@ async fn handle_action( AsyncAction::AddMod(state, info) => tokio::spawn(async move { match import_mod(state, info) .await - .wrap_err("failed to import mod") + .wrap_err("Failed to import mod") { Ok(mod_info) => { event_sink @@ -71,7 +71,7 @@ async fn handle_action( let mod_dir = state.mod_dir.join(&info.id); if let Err(err) = delete_mod(state, &info) .await - .wrap_err("failed to delete mod files") + .wrap_err("Failed to delete mod files") .with_suggestion(|| { format!("Clean the folder '{}' manually", mod_dir.display()) }) @@ -93,7 +93,7 @@ async fn handle_action( AsyncAction::ResetDeployment(state) => tokio::spawn(async move { if let Err(err) = reset_mod_deployment(state) .await - .wrap_err("failed to reset mod deployment") + .wrap_err("Failed to reset mod deployment") { tracing::error!("{:?}", err); send_error(event_sink.clone(), err).await; @@ -108,7 +108,7 @@ async fn handle_action( AsyncAction::SaveSettings(state) => tokio::spawn(async move { if let Err(err) = save_settings(state) .await - .wrap_err("failed to save settings") + .wrap_err("Failed to save settings") { tracing::error!("{:?}", err); send_error(event_sink.clone(), err).await; diff --git a/crates/dtmm/src/main.rs b/crates/dtmm/src/main.rs index 21b9f37..4a6b153 100644 --- a/crates/dtmm/src/main.rs +++ b/crates/dtmm/src/main.rs @@ -58,7 +58,7 @@ fn main() -> Result<()> { } let config = util::config::read_config(&default_config_path, &matches) - .wrap_err("failed to read config file")?; + .wrap_err("Failed to read config file")?; let game_info = dtmt_shared::collect_game_info()?; @@ -71,7 +71,7 @@ fn main() -> Result<()> { config.data_dir.unwrap_or_default(), ); state.mods = load_mods(state.get_mod_dir(), config.mod_order.iter()) - .wrap_err("failed to load mods")?; + .wrap_err("Failed to load mods")?; state }; diff --git a/crates/dtmm/src/util/config.rs b/crates/dtmm/src/util/config.rs index 41fef73..bf2a72b 100644 --- a/crates/dtmm/src/util/config.rs +++ b/crates/dtmm/src/util/config.rs @@ -113,10 +113,10 @@ where match fs::read(path) { Ok(data) => { let data = String::from_utf8(data).wrap_err_with(|| { - format!("config file {} contains invalid UTF-8", path.display()) + format!("Config file '{}' contains invalid UTF-8", path.display()) })?; let mut cfg: Config = serde_sjson::from_str(&data) - .wrap_err_with(|| format!("invalid config file {}", path.display()))?; + .wrap_err_with(|| format!("Invalid config file {}", path.display()))?; cfg.path = path.clone(); Ok(cfg) @@ -124,7 +124,7 @@ where Err(err) if err.kind() == ErrorKind::NotFound => { if matches.value_source("config") != Some(ValueSource::DefaultValue) { return Err(err) - .wrap_err_with(|| format!("failed to read config file {}", path.display()))?; + .wrap_err_with(|| format!("Failed to read config file {}", path.display()))?; } { @@ -132,7 +132,7 @@ where .parent() .expect("a file path always has a parent directory"); fs::create_dir_all(parent).wrap_err_with(|| { - format!("failed to create directories {}", parent.display()) + format!("Failed to create directories {}", parent.display()) })?; } @@ -145,7 +145,7 @@ where { let data = serde_sjson::to_string(&config) - .wrap_err("failed to serialize default config value")?; + .wrap_err("Failed to serialize default config value")?; fs::write(&config.path, data).wrap_err_with(|| { format!( "failed to write default config to {}", @@ -157,7 +157,7 @@ where Ok(config) } Err(err) => { - Err(err).wrap_err_with(|| format!("failed to read config file {}", path.display())) + Err(err).wrap_err_with(|| format!("Failed to read config file {}", path.display())) } } } diff --git a/crates/dtmt/src/cmd/build.rs b/crates/dtmt/src/cmd/build.rs index a49c9a1..e47d1ea 100644 --- a/crates/dtmt/src/cmd/build.rs +++ b/crates/dtmt/src/cmd/build.rs @@ -66,7 +66,7 @@ async fn find_project_config(dir: Option) -> Result { let (path, mut file) = if let Some(path) = dir { let file = File::open(&path.join(PROJECT_CONFIG_NAME)) .await - .wrap_err_with(|| format!("failed to open file: {}", path.display())) + .wrap_err_with(|| format!("Failed to open file: {}", path.display())) .with_suggestion(|| { format!( "Make sure the file at '{}' exists and is readable", @@ -90,7 +90,7 @@ async fn find_project_config(dir: Option) -> Result { } Err(err) => { let err = Report::new(err) - .wrap_err(format!("failed to open file: {}", path.display())); + .wrap_err(format!("Failed to open file: {}", path.display())); return Err(err); } } @@ -100,10 +100,10 @@ async fn find_project_config(dir: Option) -> Result { let mut buf = String::new(); file.read_to_string(&mut buf) .await - .wrap_err("invalid UTF-8")?; + .wrap_err("Invalid UTF-8")?; let mut cfg: ModConfig = - serde_sjson::from_str(&buf).wrap_err("failed to deserialize mod config")?; + serde_sjson::from_str(&buf).wrap_err("Failed to deserialize mod config")?; cfg.dir = path; Ok(cfg) } @@ -175,18 +175,18 @@ where path.set_extension("package"); let sjson = fs::read_to_string(&path) .await - .wrap_err_with(|| format!("failed to read file {}", path.display()))?; + .wrap_err_with(|| format!("Failed to read file {}", path.display()))?; let pkg_name = package.to_slash_lossy().to_string(); let pkg = Package::from_sjson(sjson, pkg_name.clone(), root) .await - .wrap_err_with(|| format!("invalid package file {}", &pkg_name))?; + .wrap_err_with(|| format!("Invalid package file {}", &pkg_name))?; compile_package_files(&pkg, root) .await - .wrap_err("failed to compile package") + .wrap_err("Failed to compile package") .and_then(|files| compile_bundle(pkg_name, files)) - .wrap_err("failed to build bundle") + .wrap_err("Failed to build bundle") } fn normalize_file_path>(path: P) -> Result { @@ -211,7 +211,7 @@ pub(crate) async fn read_project_config(dir: Option) -> Result) -> Result) -> Result Result<()> fs::create_dir_all(out_path) .await - .wrap_err_with(|| format!("failed to create output directory '{}'", out_path.display()))?; + .wrap_err_with(|| format!("Failed to create output directory '{}'", out_path.display()))?; let file_map = Arc::new(Mutex::new(FileIndexMap::new())); @@ -335,7 +335,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> ); fs::write(&path, &data) .await - .wrap_err_with(|| format!("failed to write bundle to '{}'", path.display()))?; + .wrap_err_with(|| format!("Failed to write bundle to '{}'", path.display()))?; if let Some(game_dir) = game_dir.as_ref() { let path = game_dir.join(&name); @@ -347,7 +347,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> ); fs::write(&path, &data) .await - .wrap_err_with(|| format!("failed to write bundle to '{}'", path.display()))?; + .wrap_err_with(|| format!("Failed to write bundle to '{}'", path.display()))?; } Ok(()) @@ -355,7 +355,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> try_join_all(tasks) .await - .wrap_err("failed to build mod bundles")?; + .wrap_err("Failed to build mod bundles")?; { let file_map = file_map.lock().await; @@ -363,7 +363,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> let path = out_path.join("files.sjson"); fs::write(&path, data) .await - .wrap_err_with(|| format!("failed to write file index to '{}'", path.display()))?; + .wrap_err_with(|| format!("Failed to write file index to '{}'", path.display()))?; } tracing::info!("Compiled bundles written to '{}'", out_path.display()); diff --git a/crates/dtmt/src/cmd/bundle/inject.rs b/crates/dtmt/src/cmd/bundle/inject.rs index 9c47686..2b86691 100644 --- a/crates/dtmt/src/cmd/bundle/inject.rs +++ b/crates/dtmt/src/cmd/bundle/inject.rs @@ -61,7 +61,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { if let Some(name) = matches.get_one::("replace") { let mut file = File::open(&file_path) .await - .wrap_err_with(|| format!("failed to open '{}'", file_path.display()))?; + .wrap_err_with(|| format!("Failed to open '{}'", file_path.display()))?; if let Some(variant) = bundle .files_mut() @@ -72,7 +72,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { let mut data = Vec::new(); file.read_to_end(&mut data) .await - .wrap_err("failed to read input file")?; + .wrap_err("Failed to read input file")?; variant.set_data(data); } else { let err = eyre::eyre!("No file '{}' in this bundle.", name) @@ -99,11 +99,11 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { let out_path = matches.get_one::("output").unwrap_or(bundle_path); let data = bundle .to_binary() - .wrap_err("failed to write changed bundle to output")?; + .wrap_err("Failed to write changed bundle to output")?; fs::write(out_path, &data) .await - .wrap_err("failed to write data to output file")?; + .wrap_err("Failed to write data to output file")?; Ok(()) } else { diff --git a/crates/dtmt/src/cmd/bundle/list.rs b/crates/dtmt/src/cmd/bundle/list.rs index b985ad2..dd72ad2 100644 --- a/crates/dtmt/src/cmd/bundle/list.rs +++ b/crates/dtmt/src/cmd/bundle/list.rs @@ -98,7 +98,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { async move { if let Err(err) = print_bundle_contents(&ctx, &p, fmt) .await - .wrap_err_with(|| format!("failed to list contents of bundle {}", p.display())) + .wrap_err_with(|| format!("Failed to list contents of bundle {}", p.display())) { tracing::error!("{err:?}"); } diff --git a/crates/dtmt/src/cmd/dictionary.rs b/crates/dtmt/src/cmd/dictionary.rs index 20ec1fc..0400519 100644 --- a/crates/dtmt/src/cmd/dictionary.rs +++ b/crates/dtmt/src/cmd/dictionary.rs @@ -122,7 +122,7 @@ pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<( .expect("required argument not found"); u64::from_str_radix(s, 16) - .wrap_err("failed to parse argument as hexadecimal string")? + .wrap_err("Failed to parse argument as hexadecimal string")? }; let groups = sub_matches diff --git a/crates/dtmt/src/cmd/new.rs b/crates/dtmt/src/cmd/new.rs index 187706c..77fa649 100644 --- a/crates/dtmt/src/cmd/new.rs +++ b/crates/dtmt/src/cmd/new.rs @@ -86,7 +86,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> let root = if let Some(dir) = matches.get_one::("root") { if dir == "." { std::env::current_dir() - .wrap_err("the current working dir is invalid") + .wrap_err("The current working dir is invalid") .with_suggestion(|| "Change to a different directory.")? } else { PathBuf::from(dir) @@ -142,13 +142,13 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> .recursive(true) .create(&dir) .await - .wrap_err_with(|| format!("failed to create directory {}", dir.display()))?; + .wrap_err_with(|| format!("Failed to create directory {}", dir.display()))?; tracing::trace!("Writing file {}", path.display()); fs::write(&path, content.as_bytes()) .await - .wrap_err_with(|| format!("failed to write content to path {}", path.display())) + .wrap_err_with(|| format!("Failed to write content to path {}", path.display())) }); futures::stream::iter(templates) diff --git a/crates/dtmt/src/cmd/package.rs b/crates/dtmt/src/cmd/package.rs index 2a64a9f..eab826c 100644 --- a/crates/dtmt/src/cmd/package.rs +++ b/crates/dtmt/src/cmd/package.rs @@ -52,7 +52,7 @@ async fn process_dir_entry(res: Result) -> Result<(OsString, Vec)> let data = fs::read(&path) .await - .wrap_err_with(|| format!("failed to read '{}'", path.display()))?; + .wrap_err_with(|| format!("Failed to read '{}'", path.display()))?; Ok((entry.file_name(), data)) } @@ -87,7 +87,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> let data = fs::read(&path) .await - .wrap_err_with(|| format!("failed to read mod config at {}", path.display()))?; + .wrap_err_with(|| format!("Failed to read mod config at {}", path.display()))?; zip.start_file(name.to_slash_lossy(), Default::default())?; zip.write_all(&data)?; @@ -101,10 +101,10 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> ); let read_dir = fs::read_dir(&path) .await - .wrap_err_with(|| format!("failed to read directory '{}'", path.display()))?; + .wrap_err_with(|| format!("Failed to read directory '{}'", path.display()))?; let stream = ReadDirStream::new(read_dir) - .map(|res| res.wrap_err("failed to read dir entry")) + .map(|res| res.wrap_err("Failed to read dir entry")) .then(process_dir_entry); tokio::pin!(stream); @@ -121,7 +121,7 @@ pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> fs::write(&dest, data.into_inner()) .await - .wrap_err_with(|| format!("failed to write mod archive to '{}'", dest.display())) + .wrap_err_with(|| format!("Failed to write mod archive to '{}'", dest.display())) .with_suggestion(|| "Make sure that parent directories exist.".to_string())?; tracing::info!("Mod archive written to {}", dest.display()); diff --git a/crates/dtmt/src/main.rs b/crates/dtmt/src/main.rs index 15c6758..8c7b40f 100644 --- a/crates/dtmt/src/main.rs +++ b/crates/dtmt/src/main.rs @@ -75,7 +75,7 @@ async fn main() -> Result<()> { tokio::spawn(async move { let res = File::open(&path) .await - .wrap_err_with(|| format!("failed to open dictionary file: {}", path.display())); + .wrap_err_with(|| format!("Failed to open dictionary file: {}", path.display())); let f = match res { Ok(f) => f, @@ -102,7 +102,7 @@ async fn main() -> Result<()> { tokio::spawn(async move { let conf = tokio::task::spawn_blocking(|| { confy::load::(clap::crate_name!(), None) - .wrap_err("failed to load global configuration") + .wrap_err("Failed to load global configuration") }) .await; diff --git a/lib/sdk/src/bundle/file.rs b/lib/sdk/src/bundle/file.rs index 872b48b..9881660 100644 --- a/lib/sdk/src/bundle/file.rs +++ b/lib/sdk/src/bundle/file.rs @@ -548,7 +548,7 @@ impl BundleFile { let _enter = span.enter(); let header = BundleFileVariant::read_header(r) - .wrap_err_with(|| format!("failed to read header {i}"))?; + .wrap_err_with(|| format!("Failed to read header {i}"))?; // TODO: Figure out how `header.unknown_1` correlates to `properties::DATA` // if props.contains(Properties::DATA) { @@ -572,18 +572,18 @@ impl BundleFile { let data = vec![]; let s = r .read_string_len(header.size) - .wrap_err("failed to read data file name")?; + .wrap_err("Failed to read data file name")?; (data, Some(s)) } else { let mut data = vec![0; header.size]; r.read_exact(&mut data) - .wrap_err_with(|| format!("failed to read file {i}"))?; + .wrap_err_with(|| format!("Failed to read file {i}"))?; let data_file_name = if header.len_data_file_name > 0 { let s = r .read_string_len(header.len_data_file_name) - .wrap_err("failed to read data file name")?; + .wrap_err("Failed to read data file name")?; Some(s) } else { None @@ -662,7 +662,7 @@ impl BundleFile { match file_type { BundleFileType::Lua => { let sjson = - CString::new(sjson.as_ref()).wrap_err("failed to build CString from SJSON")?; + CString::new(sjson.as_ref()).wrap_err("Failed to build CString from SJSON")?; lua::compile(name, sjson) } BundleFileType::Unknown(_) => { @@ -784,7 +784,7 @@ impl BundleFile { } }; - let res = res.wrap_err_with(|| format!("failed to decompile file {name}")); + let res = res.wrap_err_with(|| format!("Failed to decompile file {name}")); match res { Ok(files) => files, Err(err) => { diff --git a/lib/sdk/src/bundle/mod.rs b/lib/sdk/src/bundle/mod.rs index 8b8d7c5..af87d29 100644 --- a/lib/sdk/src/bundle/mod.rs +++ b/lib/sdk/src/bundle/mod.rs @@ -164,7 +164,7 @@ impl Bundle { OodleLZ_FuzzSafe::No, OodleLZ_CheckCRC::No, ) - .wrap_err_with(|| format!("failed to decompress chunk {chunk_index}"))?; + .wrap_err_with(|| format!("Failed to decompress chunk {chunk_index}"))?; if unpacked_size_tracked < CHUNK_SIZE { raw_buffer.resize(unpacked_size_tracked, 0); @@ -192,7 +192,7 @@ impl Bundle { let _enter = span.enter(); let file = BundleFile::from_reader(ctx, &mut r, *props) - .wrap_err_with(|| format!("failed to read file {i}"))?; + .wrap_err_with(|| format!("Failed to read file {i}"))?; files.push(file); } diff --git a/lib/sdk/src/filetype/lua.rs b/lib/sdk/src/filetype/lua.rs index 68b95e3..720988f 100644 --- a/lib/sdk/src/filetype/lua.rs +++ b/lib/sdk/src/filetype/lua.rs @@ -38,7 +38,7 @@ where lua::lua_setglobal(state, b"code\0".as_ptr() as _); let name = CString::new(name.as_bytes()) - .wrap_err_with(|| format!("cannot convert name into CString: {}", name))?; + .wrap_err_with(|| format!("Cannot convert name into CString: {}", name))?; lua::lua_pushstring(state, name.as_ptr() as _); lua::lua_setglobal(state, b"name\0".as_ptr() as _); diff --git a/lib/sdk/src/filetype/package.rs b/lib/sdk/src/filetype/package.rs index 9545677..1f50f81 100644 --- a/lib/sdk/src/filetype/package.rs +++ b/lib/sdk/src/filetype/package.rs @@ -148,7 +148,7 @@ impl Package { None } else { let t = BundleFileType::from_str(ty) - .wrap_err("invalid file type in package definition")?; + .wrap_err("Invalid file type in package definition")?; Some(t) }; @@ -200,7 +200,7 @@ impl Package { } } - serde_sjson::to_string(&map).wrap_err("failed to serialize Package to SJSON") + serde_sjson::to_string(&map).wrap_err("Failed to serialize Package to SJSON") } #[tracing::instrument("Package::from_binary", skip(binary, ctx), fields(binary_len = binary.as_ref().len()))] diff --git a/lib/sdk/src/murmur/mod.rs b/lib/sdk/src/murmur/mod.rs index 7ede170..a2a9ef3 100644 --- a/lib/sdk/src/murmur/mod.rs +++ b/lib/sdk/src/murmur/mod.rs @@ -56,7 +56,7 @@ impl TryFrom<&str> for Murmur64 { fn try_from(value: &str) -> Result { u64::from_str_radix(value, 16) .map(Self) - .wrap_err_with(|| format!("failed to convert value to Murmur64: {value}")) + .wrap_err_with(|| format!("Failed to convert value to Murmur64: {value}")) } } @@ -164,7 +164,7 @@ impl TryFrom<&str> for Murmur32 { fn try_from(value: &str) -> Result { u32::from_str_radix(value, 16) .map(Self) - .wrap_err_with(|| format!("failed to convert value to Murmur32: {value}")) + .wrap_err_with(|| format!("Failed to convert value to Murmur32: {value}")) } } -- 2.45.3