diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index dd0306f..d58a20f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -7,6 +7,7 @@ - dtmt: split `build` into `build` and `package` - dtmt: implement deploying built bundles - dtmm: indicate when a deployment is necessary +- dtmm: check for Steam game update before deployment === Fixed diff --git a/crates/dtmm/src/controller/app.rs b/crates/dtmm/src/controller/app.rs index aba2442..c2f5962 100644 --- a/crates/dtmm/src/controller/app.rs +++ b/crates/dtmm/src/controller/app.rs @@ -8,7 +8,6 @@ use color_eyre::{Help, Result}; use druid::im::Vector; use druid::FileInfo; use dtmt_shared::ModConfig; -use serde::Deserialize; use tokio::fs::{self, DirEntry}; use tokio::runtime::Runtime; use tokio_stream::wrappers::ReadDirStream; @@ -18,6 +17,8 @@ use zip::ZipArchive; use crate::state::{ModInfo, PackageInfo, State}; use crate::util::config::{ConfigSerialize, LoadOrderEntry}; +use super::read_sjson_file; + #[tracing::instrument(skip(state))] pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result { let data = fs::read(&info.path) @@ -144,16 +145,6 @@ pub(crate) async fn save_settings(state: State) -> Result<()> { }) } -async fn read_sjson_file(path: P) -> Result -where - T: for<'a> Deserialize<'a>, - P: AsRef + std::fmt::Debug, -{ - let buf = fs::read(path).await.wrap_err("failed to read file")?; - let data = String::from_utf8(buf).wrap_err("invalid UTF8")?; - serde_sjson::from_str(&data).wrap_err("failed to deserialize") -} - #[tracing::instrument(skip_all,fields( name = ?res.as_ref().map(|entry| entry.file_name()) ))] diff --git a/crates/dtmm/src/controller/game.rs b/crates/dtmm/src/controller/game.rs index cbb1b02..f504b3a 100644 --- a/crates/dtmm/src/controller/game.rs +++ b/crates/dtmm/src/controller/game.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use std::sync::Arc; use color_eyre::eyre::Context; -use color_eyre::{eyre, Help, Result}; +use color_eyre::{eyre, Help, Report, Result}; use futures::stream; use futures::StreamExt; use path_slash::PathBufExt; @@ -21,6 +21,7 @@ use tokio::fs; use tokio::io::AsyncWriteExt; use tracing::Instrument; +use super::read_sjson_file; use crate::state::{PackageInfo, State}; const MOD_BUNDLE_NAME: &str = "packages/mods"; @@ -32,7 +33,7 @@ const MOD_DATA_SCRIPT: &str = "scripts/mods/mod_data"; const SETTINGS_FILE_PATH: &str = "application_settings/settings_common.ini"; const DEPLOYMENT_DATA_PATH: &str = "dtmm-deployment.sjson"; -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] struct DeploymentData { bundles: Vec, #[serde(with = "time::serde::iso8601")] @@ -535,6 +536,42 @@ pub(crate) async fn deploy_mods(state: State) -> Result<()> { } } + let (game_info, deployment_info) = tokio::try_join!( + async { + tokio::task::spawn_blocking(dtmt_shared::collect_game_info) + .await + .map_err(Report::new) + }, + async { + let path = state.game_dir.join(DEPLOYMENT_DATA_PATH); + match read_sjson_file::<_, DeploymentData>(path) + .await + { + Ok(data) => Ok(Some(data)), + Err(err) => { + if let Some(err) = err.downcast_ref::() && err.kind() == ErrorKind::NotFound { + Ok(None) + } else { + Err(err).wrap_err("failed to read deployment data") + } + } + } + } + ) + .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); + + if deployment_info + .as_ref() + .map(|i| game_info.last_updated > i.timestamp) + .unwrap_or(false) + { + eyre::bail!("Game was updated since last mod deployment. Please reset first."); + } + tracing::info!( "Deploying {} mods to {}", state.mods.len(), diff --git a/crates/dtmm/src/controller/mod.rs b/crates/dtmm/src/controller/mod.rs new file mode 100644 index 0000000..f7f250d --- /dev/null +++ b/crates/dtmm/src/controller/mod.rs @@ -0,0 +1,23 @@ +use std::path::Path; + +use color_eyre::{eyre::Context, Result}; +use serde::Deserialize; +use tokio::fs; + +pub mod app; +pub mod game; +pub mod worker; + +#[tracing::instrument] +async fn read_sjson_file(path: P) -> Result +where + T: for<'a> Deserialize<'a>, + P: AsRef + std::fmt::Debug, +{ + let path = path.as_ref(); + let buf = fs::read(path) + .await + .wrap_err_with(|| format!("failed to read file '{}'", path.display()))?; + let data = String::from_utf8(buf).wrap_err("invalid UTF8")?; + serde_sjson::from_str(&data).wrap_err("failed to deserialize SJSON") +} diff --git a/crates/dtmm/src/main.rs b/crates/dtmm/src/main.rs index 667fc07..21b9f37 100644 --- a/crates/dtmm/src/main.rs +++ b/crates/dtmm/src/main.rs @@ -17,11 +17,7 @@ use crate::controller::app::load_mods; use crate::controller::worker::work_thread; use crate::state::{Delegate, State}; -mod controller { - pub mod app; - pub mod game; - pub mod worker; -} +mod controller; mod state; mod util { pub mod config; @@ -66,6 +62,8 @@ fn main() -> Result<()> { let game_info = dtmt_shared::collect_game_info()?; + tracing::debug!(?config, ?game_info); + let initial_state = { let mut state = State::new( config.path, diff --git a/lib/dtmt-shared/src/lib.rs b/lib/dtmt-shared/src/lib.rs index 46e7950..fa8c407 100644 --- a/lib/dtmt-shared/src/lib.rs +++ b/lib/dtmt-shared/src/lib.rs @@ -34,6 +34,7 @@ pub struct ModConfig { pub const STEAMAPP_ID: u32 = 1361210; +#[derive(Debug)] pub struct GameInfo { pub path: PathBuf, pub last_updated: OffsetDateTime,