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.
This commit is contained in:
Lucas Schwiderski 2023-02-27 11:10:14 +01:00
parent c5b2e136fa
commit bb671c5fd2
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
4 changed files with 79 additions and 4 deletions

View file

@ -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<ModInfo> {
let data = fs::read(&info.path)

View file

@ -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");
}),
};
}
});

View file

@ -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<State> {
)
.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)

View file

@ -24,6 +24,11 @@ pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>>
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<FileInfo> = Selector::new("dtmm.action.add-mod");
pub(crate) const ACTION_FINISH_ADD_MOD: Selector<SingleUse<ModInfo>> =
Selector::new("dtmm.action.finish-add-mod");
@ -145,6 +150,7 @@ pub(crate) struct State {
mods: Vector<ModInfo>,
selected_mod_index: Option<usize>,
is_deployment_in_progress: bool,
is_reset_in_progress: bool,
game_dir: Arc<PathBuf>,
data_dir: Arc<PathBuf>,
ctx: Arc<sdk::Context>,
@ -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<T: Data> Lens<Vector<T>, 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<State> 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)