diff --git a/crates/dtmm/src/controller/app.rs b/crates/dtmm/src/controller/app.rs new file mode 100644 index 0000000..01dc22c --- /dev/null +++ b/crates/dtmm/src/controller/app.rs @@ -0,0 +1,105 @@ +use std::collections::HashMap; +use std::io::{Cursor, Read}; + +use color_eyre::eyre::{self, Context}; +use color_eyre::{Help, Result}; +use druid::FileInfo; +use dtmt_shared::ModConfig; +use tokio::fs; +use zip::ZipArchive; + +use crate::state::{ModInfo, PackageInfo, State}; + +#[tracing::instrument(skip(state))] +pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result { + let data = fs::read(&info.path) + .await + .wrap_err_with(|| format!("failed to read file {}", info.path.display()))?; + let data = Cursor::new(data); + + let mut archive = ZipArchive::new(data).wrap_err("failed to open ZIP archive")?; + + if tracing::enabled!(tracing::Level::DEBUG) { + let names = archive.file_names().fold(String::new(), |mut s, name| { + s.push('\n'); + s.push_str(name); + s + }); + tracing::debug!("Archive contents:{}", names); + } + + let dir_name = { + let f = archive.by_index(0).wrap_err("archive is empty")?; + + if !f.is_dir() { + let err = eyre::eyre!("archive does not have a top-level directory"); + return Err(err).with_suggestion(|| "Use 'dtmt build' to create the mod archive."); + } + + let name = f.name(); + // The directory name is returned with a trailing slash, which we don't want + name[..(name.len().saturating_sub(1))].to_string() + }; + + tracing::info!("Importing mod {}", dir_name); + + let mod_cfg: ModConfig = { + let mut f = archive + .by_name(&format!("{}/{}", dir_name, "dtmt.cfg")) + .wrap_err("failed to read mod config from archive")?; + let mut buf = Vec::with_capacity(f.size() as usize); + f.read_to_end(&mut buf) + .wrap_err("failed to read mod config from archive")?; + + let data = String::from_utf8(buf).wrap_err("mod config is not valid UTF-8")?; + + serde_sjson::from_str(&data).wrap_err("failed to deserialize mod config")? + }; + + tracing::debug!(?mod_cfg); + + let files: HashMap> = { + let mut f = archive + .by_name(&format!("{}/{}", dir_name, "files.sjson")) + .wrap_err("failed to read file index from archive")?; + let mut buf = Vec::with_capacity(f.size() as usize); + f.read_to_end(&mut buf) + .wrap_err("failed to read file index from archive")?; + + let data = String::from_utf8(buf).wrap_err("file index is not valid UTF-8")?; + + serde_sjson::from_str(&data).wrap_err("failed to deserialize file index")? + }; + + tracing::trace!(?files); + + let mod_dir = state.get_mod_dir(); + + tracing::trace!("Creating mods directory {}", mod_dir.display()); + fs::create_dir_all(&mod_dir) + .await + .wrap_err_with(|| format!("failed to create data directory {}", mod_dir.display()))?; + + tracing::trace!("Extracting mod archive to {}", mod_dir.display()); + archive + .extract(&mod_dir) + .wrap_err_with(|| format!("failed to extract archive to {}", mod_dir.display()))?; + + let packages = files + .into_iter() + .map(|(name, files)| PackageInfo::new(name, files.into_iter().collect())) + .collect(); + let info = ModInfo::new(mod_cfg, packages); + + 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.id); + fs::remove_dir_all(&mod_dir) + .await + .wrap_err_with(|| format!("failed to remove directory {}", mod_dir.display()))?; + + Ok(()) +} diff --git a/crates/dtmm/src/controller/engine.rs b/crates/dtmm/src/controller/game.rs similarity index 85% rename from crates/dtmm/src/controller/engine.rs rename to crates/dtmm/src/controller/game.rs index 7d202e2..508e84a 100644 --- a/crates/dtmm/src/controller/engine.rs +++ b/crates/dtmm/src/controller/game.rs @@ -1,14 +1,11 @@ -use std::collections::HashMap; use std::ffi::CString; -use std::io::{Cursor, ErrorKind, Read}; +use std::io::{Cursor, ErrorKind}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; use color_eyre::eyre::Context; use color_eyre::{eyre, Help, Result}; -use druid::FileInfo; -use dtmt_shared::ModConfig; use futures::stream; use futures::StreamExt; use sdk::filetype::lua; @@ -20,9 +17,8 @@ use sdk::{ use tokio::fs; use tokio::io::AsyncWriteExt; use tracing::Instrument; -use zip::ZipArchive; -use crate::state::{ModInfo, PackageInfo, State}; +use crate::state::{PackageInfo, State}; const MOD_BUNDLE_NAME: &str = "packages/mods"; const BOOT_BUNDLE_NAME: &str = "packages/boot"; @@ -577,97 +573,3 @@ pub(crate) async fn reset_mod_deployment(state: State) -> Result<()> { Ok(()) } - -#[tracing::instrument(skip(state))] -pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result { - let data = fs::read(&info.path) - .await - .wrap_err_with(|| format!("failed to read file {}", info.path.display()))?; - let data = Cursor::new(data); - - let mut archive = ZipArchive::new(data).wrap_err("failed to open ZIP archive")?; - - if tracing::enabled!(tracing::Level::DEBUG) { - let names = archive.file_names().fold(String::new(), |mut s, name| { - s.push('\n'); - s.push_str(name); - s - }); - tracing::debug!("Archive contents:{}", names); - } - - let dir_name = { - let f = archive.by_index(0).wrap_err("archive is empty")?; - - if !f.is_dir() { - let err = eyre::eyre!("archive does not have a top-level directory"); - return Err(err).with_suggestion(|| "Use 'dtmt build' to create the mod archive."); - } - - let name = f.name(); - // The directory name is returned with a trailing slash, which we don't want - name[..(name.len().saturating_sub(1))].to_string() - }; - - tracing::info!("Importing mod {}", dir_name); - - let mod_cfg: ModConfig = { - let mut f = archive - .by_name(&format!("{}/{}", dir_name, "dtmt.cfg")) - .wrap_err("failed to read mod config from archive")?; - let mut buf = Vec::with_capacity(f.size() as usize); - f.read_to_end(&mut buf) - .wrap_err("failed to read mod config from archive")?; - - let data = String::from_utf8(buf).wrap_err("mod config is not valid UTF-8")?; - - serde_sjson::from_str(&data).wrap_err("failed to deserialize mod config")? - }; - - tracing::debug!(?mod_cfg); - - let files: HashMap> = { - let mut f = archive - .by_name(&format!("{}/{}", dir_name, "files.sjson")) - .wrap_err("failed to read file index from archive")?; - let mut buf = Vec::with_capacity(f.size() as usize); - f.read_to_end(&mut buf) - .wrap_err("failed to read file index from archive")?; - - let data = String::from_utf8(buf).wrap_err("file index is not valid UTF-8")?; - - serde_sjson::from_str(&data).wrap_err("failed to deserialize file index")? - }; - - tracing::trace!(?files); - - let mod_dir = state.get_mod_dir(); - - tracing::trace!("Creating mods directory {}", mod_dir.display()); - fs::create_dir_all(&mod_dir) - .await - .wrap_err_with(|| format!("failed to create data directory {}", mod_dir.display()))?; - - tracing::trace!("Extracting mod archive to {}", mod_dir.display()); - archive - .extract(&mod_dir) - .wrap_err_with(|| format!("failed to extract archive to {}", mod_dir.display()))?; - - let packages = files - .into_iter() - .map(|(name, files)| PackageInfo::new(name, files.into_iter().collect())) - .collect(); - let info = ModInfo::new(mod_cfg, packages); - - 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.id); - fs::remove_dir_all(&mod_dir) - .await - .wrap_err_with(|| format!("failed to remove directory {}", mod_dir.display()))?; - - Ok(()) -} diff --git a/crates/dtmm/src/controller/worker.rs b/crates/dtmm/src/controller/worker.rs index 58b3827..1c11b2a 100644 --- a/crates/dtmm/src/controller/worker.rs +++ b/crates/dtmm/src/controller/worker.rs @@ -6,8 +6,13 @@ use tokio::runtime::Runtime; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::RwLock; -use crate::controller::engine::*; -use crate::state::*; +use crate::controller::app::*; +use crate::controller::game::*; +use crate::state::AsyncAction; +use crate::state::{ + ACTION_FINISH_ADD_MOD, ACTION_FINISH_DELETE_SELECTED_MOD, ACTION_FINISH_DEPLOY, + ACTION_FINISH_RESET_DEPLOYMENT, ACTION_LOG, +}; async fn handle_action( event_sink: Arc>, diff --git a/crates/dtmm/src/main.rs b/crates/dtmm/src/main.rs index bf38c65..885cfcf 100644 --- a/crates/dtmm/src/main.rs +++ b/crates/dtmm/src/main.rs @@ -16,7 +16,8 @@ use crate::controller::worker::work_thread; use crate::state::{Delegate, State}; mod controller { - pub mod engine; + pub mod app; + pub mod game; pub mod worker; } mod state;