Implement mod dependencies #63
4 changed files with 47 additions and 26 deletions
|
@ -533,7 +533,14 @@ pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (game_info, deployment_info) = tokio::try_join!(
|
let (_, game_info, deployment_info) = tokio::try_join!(
|
||||||
|
async {
|
||||||
|
let path = state.game_dir.join("bundle");
|
||||||
|
fs::metadata(&path)
|
||||||
|
.await
|
||||||
|
.wrap_err("Failed to open game bundle directory")
|
||||||
|
.with_suggestion(|| "Double-check 'Game Directory' in the Settings tab.")
|
||||||
|
},
|
||||||
async {
|
async {
|
||||||
tokio::task::spawn_blocking(dtmt_shared::collect_game_info)
|
tokio::task::spawn_blocking(dtmt_shared::collect_game_info)
|
||||||
.await
|
.await
|
||||||
|
@ -557,16 +564,16 @@ pub(crate) async fn deploy_mods(state: ActionState) -> Result<()> {
|
||||||
)
|
)
|
||||||
.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")?;
|
|
||||||
|
|
||||||
tracing::debug!(?game_info, ?deployment_info);
|
tracing::debug!(?game_info, ?deployment_info);
|
||||||
|
|
||||||
if deployment_info
|
if let Some(game_info) = game_info {
|
||||||
.as_ref()
|
if deployment_info
|
||||||
.map(|i| game_info.last_updated > i.timestamp)
|
.as_ref()
|
||||||
.unwrap_or(false)
|
.map(|i| game_info.last_updated > i.timestamp)
|
||||||
{
|
.unwrap_or(false)
|
||||||
eyre::bail!("Game was updated since last mod deployment. Please reset first.");
|
{
|
||||||
|
eyre::bail!("Game was updated since last mod deployment. Please reset first.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
|
|
|
@ -8,13 +8,17 @@ use std::sync::Arc;
|
||||||
use clap::command;
|
use clap::command;
|
||||||
use clap::value_parser;
|
use clap::value_parser;
|
||||||
use clap::Arg;
|
use clap::Arg;
|
||||||
|
use color_eyre::eyre;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::{Report, Result};
|
use color_eyre::{Report, Result};
|
||||||
use druid::AppLauncher;
|
use druid::AppLauncher;
|
||||||
|
use druid::SingleUse;
|
||||||
|
use druid::Target;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::controller::app::load_mods;
|
use crate::controller::app::load_mods;
|
||||||
use crate::controller::worker::work_thread;
|
use crate::controller::worker::work_thread;
|
||||||
|
use crate::state::ACTION_SHOW_ERROR_DIALOG;
|
||||||
use crate::state::{Delegate, State};
|
use crate::state::{Delegate, State};
|
||||||
|
|
||||||
mod controller;
|
mod controller;
|
||||||
|
@ -57,17 +61,32 @@ fn main() -> Result<()> {
|
||||||
oodle_sys::init(matches.get_one::<String>("oodle"));
|
oodle_sys::init(matches.get_one::<String>("oodle"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
let delegate = Delegate::new(action_tx);
|
||||||
|
|
||||||
|
let launcher = AppLauncher::with_window(ui::window::main::new()).delegate(delegate);
|
||||||
|
|
||||||
|
let event_sink = launcher.get_external_handle();
|
||||||
|
|
||||||
let config = util::config::read_config(&default_config_path, &matches)
|
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();
|
||||||
let game_info = dtmt_shared::collect_game_info()?;
|
|
||||||
|
|
||||||
tracing::debug!(?config, ?game_info);
|
tracing::debug!(?config, ?game_info);
|
||||||
|
|
||||||
|
let game_dir = config.game_dir.or_else(|| game_info.map(|i| i.path));
|
||||||
|
if game_dir.is_none() {
|
||||||
|
let err =
|
||||||
|
eyre::eyre!("No Game Directory set. Head to the 'Settings' tab to set it manually",);
|
||||||
|
event_sink
|
||||||
|
.submit_command(ACTION_SHOW_ERROR_DIALOG, SingleUse::new(err), Target::Auto)
|
||||||
|
.expect("failed to send command");
|
||||||
|
}
|
||||||
|
|
||||||
let initial_state = {
|
let initial_state = {
|
||||||
let mut state = State::new(
|
let mut state = State::new(
|
||||||
config.path,
|
config.path,
|
||||||
config.game_dir.unwrap_or(game_info.path),
|
game_dir.unwrap_or_default(),
|
||||||
config.data_dir.unwrap_or_default(),
|
config.data_dir.unwrap_or_default(),
|
||||||
);
|
);
|
||||||
state.mods = load_mods(state.get_mod_dir(), config.mod_order.iter())
|
state.mods = load_mods(state.get_mod_dir(), config.mod_order.iter())
|
||||||
|
@ -75,12 +94,6 @@ fn main() -> Result<()> {
|
||||||
state
|
state
|
||||||
};
|
};
|
||||||
|
|
||||||
let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
||||||
let delegate = Delegate::new(action_tx);
|
|
||||||
|
|
||||||
let launcher = AppLauncher::with_window(ui::window::main::new()).delegate(delegate);
|
|
||||||
|
|
||||||
let event_sink = launcher.get_external_handle();
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let event_sink = Arc::new(RwLock::new(event_sink));
|
let event_sink = Arc::new(RwLock::new(event_sink));
|
||||||
let action_rx = Arc::new(RwLock::new(action_rx));
|
let action_rx = Arc::new(RwLock::new(action_rx));
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub fn error<T: Data>(err: Report, parent: WindowHandle) -> WindowDesc<T> {
|
||||||
|
|
||||||
let widget = Flex::column()
|
let widget = Flex::column()
|
||||||
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
.main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||||
|
.must_fill_main_axis(true)
|
||||||
.cross_axis_alignment(CrossAxisAlignment::End)
|
.cross_axis_alignment(CrossAxisAlignment::End)
|
||||||
.with_child(text)
|
.with_child(text)
|
||||||
.with_spacer(20.)
|
.with_spacer(20.)
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use color_eyre::eyre;
|
|
||||||
use color_eyre::Result;
|
|
||||||
|
|
||||||
mod log;
|
mod log;
|
||||||
|
|
||||||
pub use log::*;
|
pub use log::*;
|
||||||
|
@ -40,11 +37,12 @@ pub struct GameInfo {
|
||||||
pub last_updated: OffsetDateTime,
|
pub last_updated: OffsetDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_game_info() -> Result<GameInfo> {
|
pub fn collect_game_info() -> Option<GameInfo> {
|
||||||
let mut dir = if let Some(dir) = SteamDir::locate() {
|
let mut dir = if let Some(dir) = SteamDir::locate() {
|
||||||
dir
|
dir
|
||||||
} else {
|
} else {
|
||||||
eyre::bail!("Failed to locate Steam installation")
|
tracing::debug!("Failed to locate Steam installation");
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let found = dir
|
let found = dir
|
||||||
|
@ -52,15 +50,17 @@ pub fn collect_game_info() -> Result<GameInfo> {
|
||||||
.and_then(|app| app.vdf.get("LastUpdated").map(|v| (app.path.clone(), v)));
|
.and_then(|app| app.vdf.get("LastUpdated").map(|v| (app.path.clone(), v)));
|
||||||
|
|
||||||
let Some((path, last_updated)) = found else {
|
let Some((path, last_updated)) = found else {
|
||||||
eyre::bail!("Failed to find game installation");
|
tracing::debug!("Found Steam, but failed to find game installation");
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(last_updated) = last_updated
|
let Some(last_updated) = last_updated
|
||||||
.as_value()
|
.as_value()
|
||||||
.and_then(|v| v.to::<i64>())
|
.and_then(|v| v.to::<i64>())
|
||||||
.and_then(|v| OffsetDateTime::from_unix_timestamp(v).ok()) else {
|
.and_then(|v| OffsetDateTime::from_unix_timestamp(v).ok()) else {
|
||||||
eyre::bail!("Couldn't read 'LastUpdate'.");
|
tracing::error!("Found Steam game, but couldn't read 'LastUpdate'.");
|
||||||
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(GameInfo { path, last_updated })
|
Some(GameInfo { path, last_updated })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue