From bb671c5fd2bd149cc23609ec61abee509dbf1481 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Mon, 27 Feb 2023 11:10:14 +0100 Subject: [PATCH] feat: Add button to reset mod deployment For now this merely recovers backed-up game files and leaves mod bundles in-tact. The game doesn't care about those anyways. Closes #8. --- crates/dtmm/src/engine.rs | 31 +++++++++++++++++++++++++++++++ crates/dtmm/src/main.rs | 13 +++++++++++++ crates/dtmm/src/main_window.rs | 10 ++++++---- crates/dtmm/src/state.rs | 29 +++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/crates/dtmm/src/engine.rs b/crates/dtmm/src/engine.rs index dea3d53..891778a 100644 --- a/crates/dtmm/src/engine.rs +++ b/crates/dtmm/src/engine.rs @@ -547,6 +547,37 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> { Ok(()) } +#[tracing::instrument(skip(state))] +pub(crate) async fn reset_mod_deployment(state: State) -> Result<()> { + let paths = [BUNDLE_DATABASE_NAME, BOOT_BUNDLE_NAME]; + let bundle_dir = state.get_game_dir().join("bundle"); + + tracing::info!("Resetting mod deployment in {}", bundle_dir.display()); + + for p in paths { + let path = bundle_dir.join(p); + let backup = bundle_dir.join(&format!("{}.bak", p)); + + tracing::debug!( + "Copying from backup: {} -> {}", + backup.display(), + path.display() + ); + + fs::copy(&backup, &path) + .await + .wrap_err_with(|| format!("failed to '{}' restore from backup", p))?; + + tracing::debug!("Deleting backup: {}", backup.display(),); + + fs::remove_file(&backup) + .await + .wrap_err_with(|| format!("failed to remove backup '{}'", p))?; + } + + Ok(()) +} + #[tracing::instrument(skip(state))] pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result { let data = fs::read(&info.path) diff --git a/crates/dtmm/src/main.rs b/crates/dtmm/src/main.rs index 32c15f8..352aeb0 100644 --- a/crates/dtmm/src/main.rs +++ b/crates/dtmm/src/main.rs @@ -19,10 +19,12 @@ use druid::SingleUse; use druid::Target; use engine::delete_mod; use engine::import_mod; +use engine::reset_mod_deployment; use serde::Deserialize; use serde::Serialize; use state::ACTION_FINISH_ADD_MOD; use state::ACTION_FINISH_DELETE_SELECTED_MOD; +use state::ACTION_FINISH_RESET_DEPLOYMENT; use tokio::runtime::Runtime; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::RwLock; @@ -102,6 +104,17 @@ fn work_thread( ) .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); + } + + event_sink + .write() + .await + .submit_command(ACTION_FINISH_RESET_DEPLOYMENT, (), Target::Auto) + .expect("failed to send command"); + }), }; } }); diff --git a/crates/dtmm/src/main_window.rs b/crates/dtmm/src/main_window.rs index 107b3aa..1b06c95 100644 --- a/crates/dtmm/src/main_window.rs +++ b/crates/dtmm/src/main_window.rs @@ -7,7 +7,7 @@ use druid::{ lens, FileDialogOptions, FileSpec, Insets, LensExt, SingleUse, Widget, WidgetExt, WindowDesc, }; -use crate::state::{ModInfo, PathBufFormatter, State, View}; +use crate::state::{ModInfo, PathBufFormatter, State, View, ACTION_START_RESET_DEPLOYMENT}; use crate::state::{ ACTION_ADD_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP, ACTION_SELECT_MOD, ACTION_START_DELETE_SELECTED_MOD, ACTION_START_DEPLOY, @@ -60,9 +60,11 @@ fn build_top_bar() -> impl Widget { ) .with_default_spacer() .with_child( - Button::new("Run Game").on_click(|_ctx, _state: &mut State, _env| { - todo!(); - }), + Button::new("Reset Mods") + .on_click(|ctx, _state: &mut State, _env| { + ctx.submit_command(ACTION_START_RESET_DEPLOYMENT); + }) + .disabled_if(|data, _| !data.can_reset_deployment()), ), ) .padding(theme::TOP_BAR_INSETS) diff --git a/crates/dtmm/src/state.rs b/crates/dtmm/src/state.rs index 42f2b41..76f1c8a 100644 --- a/crates/dtmm/src/state.rs +++ b/crates/dtmm/src/state.rs @@ -24,6 +24,11 @@ pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector> pub(crate) const ACTION_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy"); pub(crate) const ACTION_FINISH_DEPLOY: Selector = Selector::new("dtmm.action.finish-deploy"); +pub(crate) const ACTION_START_RESET_DEPLOYMENT: Selector = + Selector::new("dtmm.action.start-reset-deployment"); +pub(crate) const ACTION_FINISH_RESET_DEPLOYMENT: Selector = + Selector::new("dtmm.action.finish-reset-deployment"); + pub(crate) const ACTION_ADD_MOD: Selector = Selector::new("dtmm.action.add-mod"); pub(crate) const ACTION_FINISH_ADD_MOD: Selector> = Selector::new("dtmm.action.finish-add-mod"); @@ -145,6 +150,7 @@ pub(crate) struct State { mods: Vector, selected_mod_index: Option, is_deployment_in_progress: bool, + is_reset_in_progress: bool, game_dir: Arc, data_dir: Arc, ctx: Arc, @@ -163,6 +169,7 @@ impl State { mods: Vector::new(), selected_mod_index: None, is_deployment_in_progress: false, + is_reset_in_progress: false, game_dir: Arc::new(config.game_dir.unwrap_or_default()), data_dir: Arc::new(config.data_dir.unwrap_or_default()), } @@ -208,6 +215,10 @@ impl State { !self.is_deployment_in_progress } + pub fn can_reset_deployment(&self) -> bool { + !self.is_reset_in_progress + } + pub(crate) fn get_game_dir(&self) -> &PathBuf { &self.game_dir } @@ -292,6 +303,7 @@ impl Lens, Vector<(usize, T)>> for IndexedVectorLens { pub(crate) enum AsyncAction { DeployMods(State), + ResetDeployment(State), AddMod((State, FileInfo)), DeleteMod((State, ModInfo)), } @@ -334,6 +346,23 @@ impl AppDelegate for Delegate { state.is_deployment_in_progress = false; Handled::Yes } + cmd if cmd.is(ACTION_START_RESET_DEPLOYMENT) => { + if self + .sender + .send(AsyncAction::ResetDeployment(state.clone())) + .is_ok() + { + state.is_reset_in_progress = true; + } else { + tracing::error!("Failed to queue action to reset mod deployment"); + } + + Handled::Yes + } + cmd if cmd.is(ACTION_FINISH_RESET_DEPLOYMENT) => { + state.is_reset_in_progress = false; + Handled::Yes + } cmd if cmd.is(ACTION_SELECT_MOD) => { let index = cmd .get(ACTION_SELECT_MOD)