Improve deployments and resets #50
6 changed files with 69 additions and 18 deletions
|
@ -7,6 +7,7 @@
|
||||||
- dtmt: split `build` into `build` and `package`
|
- dtmt: split `build` into `build` and `package`
|
||||||
- dtmt: implement deploying built bundles
|
- dtmt: implement deploying built bundles
|
||||||
- dtmm: indicate when a deployment is necessary
|
- dtmm: indicate when a deployment is necessary
|
||||||
|
- dtmm: check for Steam game update before deployment
|
||||||
|
|
||||||
=== Fixed
|
=== Fixed
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ use color_eyre::{Help, Result};
|
||||||
use druid::im::Vector;
|
use druid::im::Vector;
|
||||||
use druid::FileInfo;
|
use druid::FileInfo;
|
||||||
use dtmt_shared::ModConfig;
|
use dtmt_shared::ModConfig;
|
||||||
use serde::Deserialize;
|
|
||||||
use tokio::fs::{self, DirEntry};
|
use tokio::fs::{self, DirEntry};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio_stream::wrappers::ReadDirStream;
|
use tokio_stream::wrappers::ReadDirStream;
|
||||||
|
@ -18,6 +17,8 @@ use zip::ZipArchive;
|
||||||
use crate::state::{ModInfo, PackageInfo, State};
|
use crate::state::{ModInfo, PackageInfo, State};
|
||||||
use crate::util::config::{ConfigSerialize, LoadOrderEntry};
|
use crate::util::config::{ConfigSerialize, LoadOrderEntry};
|
||||||
|
|
||||||
|
use super::read_sjson_file;
|
||||||
|
|
||||||
#[tracing::instrument(skip(state))]
|
#[tracing::instrument(skip(state))]
|
||||||
pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo> {
|
pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo> {
|
||||||
let data = fs::read(&info.path)
|
let data = fs::read(&info.path)
|
||||||
|
@ -144,16 +145,6 @@ pub(crate) async fn save_settings(state: State) -> Result<()> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_sjson_file<P, T>(path: P) -> Result<T>
|
|
||||||
where
|
|
||||||
T: for<'a> Deserialize<'a>,
|
|
||||||
P: AsRef<Path> + 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(
|
#[tracing::instrument(skip_all,fields(
|
||||||
name = ?res.as_ref().map(|entry| entry.file_name())
|
name = ?res.as_ref().map(|entry| entry.file_name())
|
||||||
))]
|
))]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::{eyre, Help, Result};
|
use color_eyre::{eyre, Help, Report, Result};
|
||||||
use futures::stream;
|
use futures::stream;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use path_slash::PathBufExt;
|
use path_slash::PathBufExt;
|
||||||
|
@ -21,6 +21,7 @@ use tokio::fs;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tracing::Instrument;
|
use tracing::Instrument;
|
||||||
|
|
||||||
|
use super::read_sjson_file;
|
||||||
use crate::state::{PackageInfo, State};
|
use crate::state::{PackageInfo, State};
|
||||||
|
|
||||||
const MOD_BUNDLE_NAME: &str = "packages/mods";
|
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 SETTINGS_FILE_PATH: &str = "application_settings/settings_common.ini";
|
||||||
const DEPLOYMENT_DATA_PATH: &str = "dtmm-deployment.sjson";
|
const DEPLOYMENT_DATA_PATH: &str = "dtmm-deployment.sjson";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct DeploymentData {
|
struct DeploymentData {
|
||||||
bundles: Vec<String>,
|
bundles: Vec<String>,
|
||||||
#[serde(with = "time::serde::iso8601")]
|
#[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::<std::io::Error>() && 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!(
|
tracing::info!(
|
||||||
"Deploying {} mods to {}",
|
"Deploying {} mods to {}",
|
||||||
state.mods.len(),
|
state.mods.len(),
|
||||||
|
|
23
crates/dtmm/src/controller/mod.rs
Normal file
23
crates/dtmm/src/controller/mod.rs
Normal file
|
@ -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<P, T>(path: P) -> Result<T>
|
||||||
|
where
|
||||||
|
T: for<'a> Deserialize<'a>,
|
||||||
|
P: AsRef<Path> + 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")
|
||||||
|
}
|
|
@ -17,11 +17,7 @@ use crate::controller::app::load_mods;
|
||||||
use crate::controller::worker::work_thread;
|
use crate::controller::worker::work_thread;
|
||||||
use crate::state::{Delegate, State};
|
use crate::state::{Delegate, State};
|
||||||
|
|
||||||
mod controller {
|
mod controller;
|
||||||
pub mod app;
|
|
||||||
pub mod game;
|
|
||||||
pub mod worker;
|
|
||||||
}
|
|
||||||
mod state;
|
mod state;
|
||||||
mod util {
|
mod util {
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
@ -66,6 +62,8 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let game_info = dtmt_shared::collect_game_info()?;
|
let game_info = dtmt_shared::collect_game_info()?;
|
||||||
|
|
||||||
|
tracing::debug!(?config, ?game_info);
|
||||||
|
|
||||||
let initial_state = {
|
let initial_state = {
|
||||||
let mut state = State::new(
|
let mut state = State::new(
|
||||||
config.path,
|
config.path,
|
||||||
|
|
|
@ -34,6 +34,7 @@ pub struct ModConfig {
|
||||||
|
|
||||||
pub const STEAMAPP_ID: u32 = 1361210;
|
pub const STEAMAPP_ID: u32 = 1361210;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GameInfo {
|
pub struct GameInfo {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub last_updated: OffsetDateTime,
|
pub last_updated: OffsetDateTime,
|
||||||
|
|
Loading…
Add table
Reference in a new issue