feat(dtmm): Delete mod files

Only files in `data_dir` will be deleted, deployed bundles will stay
for now. See #29 for the rational.

Closes #24.
This commit is contained in:
Lucas Schwiderski 2023-02-23 13:30:39 +01:00
parent 0c071b5b0a
commit 41344f022d
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
4 changed files with 78 additions and 9 deletions

View file

@ -544,3 +544,13 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo>
Ok(info)
}
#[tracing::instrument(skip(state))]
pub(crate) async fn delete_mod(state: State, info: &ModInfo) -> Result<()> {
let mod_dir = state.get_mod_dir().join(info.get_id());
fs::remove_dir_all(&mod_dir)
.await
.wrap_err_with(|| format!("failed to remove directory {}", mod_dir.display()))?;
Ok(())
}

View file

@ -17,10 +17,12 @@ use druid::AppLauncher;
use druid::ExtEventSink;
use druid::SingleUse;
use druid::Target;
use engine::delete_mod;
use engine::import_mod;
use serde::Deserialize;
use serde::Serialize;
use state::ACTION_FINISH_ADD_MOD;
use state::ACTION_FINISH_DELETE_SELECTED_MOD;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::sync::RwLock;
@ -83,6 +85,26 @@ fn work_thread(
}
}
}),
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
);
}
event_sink
.write()
.await
.submit_command(
ACTION_FINISH_DELETE_SELECTED_MOD,
SingleUse::new(info),
Target::Auto,
)
.expect("failed to send command");
}),
};
}
});

View file

@ -3,12 +3,14 @@ use druid::widget::{
Align, Button, CrossAxisAlignment, Flex, Label, List, MainAxisAlignment, Maybe, Scroll, Split,
TextBox, ViewSwitcher,
};
use druid::{lens, FileDialogOptions, FileSpec, Insets, LensExt, Widget, WidgetExt, WindowDesc};
use druid::{
lens, FileDialogOptions, FileSpec, Insets, LensExt, SingleUse, Widget, WidgetExt, WindowDesc,
};
use crate::state::{ModInfo, PathBufFormatter, State, View, ACTION_ADD_MOD};
use crate::state::{ModInfo, PathBufFormatter, State, View};
use crate::state::{
ACTION_DELETE_SELECTED_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP,
ACTION_SELECT_MOD, ACTION_START_DEPLOY,
ACTION_ADD_MOD, ACTION_SELECTED_MOD_DOWN, ACTION_SELECTED_MOD_UP, ACTION_SELECT_MOD,
ACTION_START_DELETE_SELECTED_MOD, ACTION_START_DEPLOY,
};
use crate::theme;
use crate::widget::ExtraWidgetExt;
@ -163,7 +165,13 @@ fn build_mod_details() -> impl Widget<State> {
});
let button_delete_mod = Button::new("Delete Mod")
.on_click(|ctx, _state, _env| ctx.submit_command(ACTION_DELETE_SELECTED_MOD))
.on_click(|ctx, data: &mut Option<ModInfo>, _env| {
if let Some(info) = data {
ctx.submit_command(
ACTION_START_DELETE_SELECTED_MOD.with(SingleUse::new(info.clone())),
);
}
})
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
.lens(State::selected_mod);

View file

@ -16,8 +16,10 @@ pub(crate) const ACTION_SELECT_MOD: Selector<usize> = Selector::new("dtmm.action
pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector =
Selector::new("dtmm.action.selected-mod-down");
pub(crate) const ACTION_DELETE_SELECTED_MOD: Selector =
Selector::new("dtmm.action.delete-selected-mod");
pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
Selector::new("dtmm.action.srart-delete-selected-mod");
pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
Selector::new("dtmm.action.finish-delete-selected-mod");
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");
@ -284,6 +286,7 @@ impl<T: Data> Lens<Vector<T>, Vector<(usize, T)>> for IndexedVectorLens {
pub(crate) enum AsyncAction {
DeployMods(State),
AddMod((State, FileInfo)),
DeleteMod((State, ModInfo)),
}
pub(crate) struct Delegate {
@ -360,8 +363,34 @@ impl AppDelegate<State> for Delegate {
state.selected_mod_index = Some(i + 1);
Handled::Yes
}
cmd if cmd.is(ACTION_DELETE_SELECTED_MOD) => {
let Some(index) = state.selected_mod_index else {
cmd if cmd.is(ACTION_START_DELETE_SELECTED_MOD) => {
let info = cmd
.get(ACTION_FINISH_DELETE_SELECTED_MOD)
.and_then(|info| info.take())
.expect("command type matched but didn't contain the expected value");
if self
.sender
.send(AsyncAction::DeleteMod((state.clone(), info)))
.is_ok()
{
state.is_deployment_in_progress = true;
} else {
tracing::error!("Failed to queue action to deploy mods");
}
Handled::Yes
}
cmd if cmd.is(ACTION_FINISH_DELETE_SELECTED_MOD) => {
let info = cmd
.get(ACTION_FINISH_DELETE_SELECTED_MOD)
.and_then(|info| info.take())
.expect("command type matched but didn't contain the expected value");
let mods = state.get_mods();
let found = mods
.iter()
.enumerate()
.find(|(_, i)| i.get_id() == info.get_id());
let Some((index, _)) = found else {
return Handled::No;
};