diff --git a/Cargo.lock b/Cargo.lock index a029489..25c5e4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -756,6 +756,7 @@ dependencies = [ "dtmt-shared", "futures", "lazy_static", + "nexusmods", "oodle-sys", "path-slash", "sdk", diff --git a/crates/dtmm/Cargo.toml b/crates/dtmm/Cargo.toml index 2265229..11b71f7 100644 --- a/crates/dtmm/Cargo.toml +++ b/crates/dtmm/Cargo.toml @@ -15,6 +15,7 @@ dtmt-shared = { path = "../../lib/dtmt-shared", version = "*" } futures = "0.3.25" oodle-sys = { path = "../../lib/oodle-sys", version = "*" } sdk = { path = "../../lib/sdk", version = "*" } +nexusmods = { path = "../../lib/nexusmods", version = "*" } serde_sjson = { path = "../../lib/serde_sjson", version = "*" } serde = { version = "1.0.152", features = ["derive", "rc"] } tokio = { version = "1.23.0", features = ["rt", "fs", "tracing", "sync"] } diff --git a/crates/dtmm/src/controller/app.rs b/crates/dtmm/src/controller/app.rs index 9aa5a6d..64c4184 100644 --- a/crates/dtmm/src/controller/app.rs +++ b/crates/dtmm/src/controller/app.rs @@ -8,13 +8,14 @@ use color_eyre::{Help, Report, Result}; use druid::im::Vector; use druid::{FileInfo, ImageBuf}; use dtmt_shared::ModConfig; +use nexusmods::Api as NexusApi; use tokio::fs::{self, DirEntry}; use tokio::runtime::Runtime; use tokio_stream::wrappers::ReadDirStream; use tokio_stream::StreamExt; use zip::ZipArchive; -use crate::state::{ActionState, ModInfo, ModOrder, PackageInfo}; +use crate::state::{ActionState, ModInfo, ModOrder, NexusInfo, PackageInfo}; use crate::util::config::{ConfigSerialize, LoadOrderEntry}; use super::read_sjson_file; @@ -26,6 +27,17 @@ pub(crate) async fn import_mod(state: ActionState, info: FileInfo) -> Result Result Result<()> { async fn read_mod_dir_entry(res: Result) -> Result { let entry = res?; let config_path = entry.path().join("dtmt.cfg"); + let nexus_path = entry.path().join("nexus.sjson"); let index_path = entry.path().join("files.sjson"); let cfg: ModConfig = read_sjson_file(&config_path) .await .wrap_err_with(|| format!("Failed to read mod config '{}'", config_path.display()))?; + let nexus: Option = match read_sjson_file(&nexus_path) + .await + .wrap_err_with(|| format!("Failed to read Nexus info '{}'", nexus_path.display())) + { + Ok(nexus) => Some(nexus), + Err(err) if err.is::() => match err.downcast_ref::() { + Some(err) if err.kind() == std::io::ErrorKind::NotFound => None, + _ => return Err(err), + }, + Err(err) => return Err(err), + }; + let files: HashMap> = read_sjson_file(&index_path) .await .wrap_err_with(|| format!("Failed to read file index '{}'", index_path.display()))?; @@ -222,7 +255,7 @@ async fn read_mod_dir_entry(res: Result) -> Result { .into_iter() .map(|(name, files)| Arc::new(PackageInfo::new(name, files.into_iter().collect()))) .collect(); - let info = ModInfo::new(cfg, packages, image); + let info = ModInfo::new(cfg, packages, image, nexus); Ok(info) } diff --git a/crates/dtmm/src/state/data.rs b/crates/dtmm/src/state/data.rs index 09e3a97..848c6bb 100644 --- a/crates/dtmm/src/state/data.rs +++ b/crates/dtmm/src/state/data.rs @@ -5,6 +5,7 @@ use druid::{ Data, ImageBuf, Lens, WindowHandle, WindowId, }; use dtmt_shared::ModConfig; +use time::OffsetDateTime; use super::SelectedModLens; @@ -69,6 +70,15 @@ impl From for ModDependency { } } +#[derive(Clone, Data, Debug, Lens, serde::Serialize, serde::Deserialize)] +pub(crate) struct NexusInfo { + pub id: u64, + pub version: Option, + #[data(ignore)] + #[serde(with = "time::serde::timestamp")] + pub updated: OffsetDateTime, +} + #[derive(Clone, Data, Debug, Lens)] pub(crate) struct ModInfo { pub id: String, @@ -87,6 +97,7 @@ pub(crate) struct ModInfo { #[data(ignore)] pub resources: ModResourceInfo, pub depends: Vector, + pub nexus: Option, } impl ModInfo { @@ -94,6 +105,7 @@ impl ModInfo { cfg: ModConfig, packages: Vector>, image: Option, + nexus: Option, ) -> Self { Self { id: cfg.id, @@ -112,6 +124,7 @@ impl ModInfo { localization: cfg.resources.localization, }, depends: cfg.depends.into_iter().map(ModDependency::from).collect(), + nexus, } } }