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:
parent
0c071b5b0a
commit
41344f022d
4 changed files with 78 additions and 9 deletions
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue