From 9c0e47987cdeb5bfba2693e7fd44067ce8c89c87 Mon Sep 17 00:00:00 2001 From: GeckoEidechse Date: Sun, 16 Jul 2023 19:13:19 +0200 Subject: [PATCH 1/2] feat: Initial support for reading NS packages dir --- src-tauri/src/mod_management/mod.rs | 200 +++++++++++++++++++++++++++- 1 file changed, 196 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index a2aca85a0..2ca613ca9 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -4,15 +4,17 @@ use crate::constants::{BLACKLISTED_MODS, CORE_MODS}; use async_recursion::async_recursion; use crate::NorthstarMod; -use anyhow::Result; +use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use std::string::ToString; use std::{fs, path::PathBuf}; mod legacy; use crate::GameInstall; #[derive(Debug, Clone)] -struct ParsedThunderstoreModString { +pub struct ParsedThunderstoreModString { author_name: String, mod_name: String, version: String, @@ -22,6 +24,12 @@ impl std::str::FromStr for ParsedThunderstoreModString { type Err = &'static str; // todo use an better error management fn from_str(s: &str) -> Result { + // Check whether Thunderstore string passse reges + let re = regex::Regex::new(r"^[a-zA-Z0-9_]+-[a-zA-Z0-9_]+-\d+\.\d+\.\d++$").unwrap(); + if !re.is_match(s) { + return Err("Incorrect format"); + } + let mut parts = s.split('-'); let author_name = parts.next().ok_or("None value on author_name")?.to_string(); @@ -36,6 +44,12 @@ impl std::str::FromStr for ParsedThunderstoreModString { } } +impl ToString for ParsedThunderstoreModString { + fn to_string(&self) -> String { + format!("{}-{}-{}", self.author_name, self.mod_name, self.version) + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ThunderstoreManifest { name: String, @@ -170,6 +184,175 @@ pub fn set_mod_enabled_status( Ok(()) } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ModJson { + #[serde(rename = "Name")] + name: String, + #[serde(rename = "Version")] + version: Option, +} + +/// Parse `mods` folder for installed mods. +pub fn parse_mods_in_package( + package_mods_path: PathBuf, + thunderstore_mod_string: ParsedThunderstoreModString, +) -> Result, anyhow::Error> { + dbg!(package_mods_path.clone()); + let paths = match std::fs::read_dir(package_mods_path) { + Ok(paths) => paths, + Err(_err) => return Err(anyhow!("No mods folder found")), + }; + + let mut directories: Vec = Vec::new(); + let mut mods: Vec = Vec::new(); + + log::info!("----------- {paths:?}"); + + // Get list of folders in `mods` directory + for path in paths { + // dbg!(path); + log::info!("{path:?}"); + let my_path = path.unwrap().path(); + log::info!("{my_path:?}"); + + let md = std::fs::metadata(my_path.clone()).unwrap(); + if md.is_dir() { + directories.push(my_path); + } + } + + dbg!(directories.clone()); + // todo!(); + // Iterate over folders and check if they are Northstar mods + for directory in directories { + let directory_str = directory.to_str().unwrap().to_string(); + // Check if mod.json exists + let mod_json_path = format!("{}/mod.json", directory_str); + if !std::path::Path::new(&mod_json_path).exists() { + continue; + } + + // Parse mod.json and get mod name + + // Read file into string and parse it + let data = std::fs::read_to_string(mod_json_path.clone())?; + let parsed_mod_json: ModJson = match json5::from_str(&data) { + Ok(parsed_json) => parsed_json, + Err(err) => { + log::warn!("Failed parsing {} with {}", mod_json_path, err.to_string()); + continue; + } + }; + + // Get directory path + let mod_directory = directory.to_str().unwrap().to_string(); + + let ns_mod = NorthstarMod { + name: parsed_mod_json.name, + version: parsed_mod_json.version, + thunderstore_mod_string: Some(thunderstore_mod_string.to_string()), + enabled: false, // Placeholder + directory: mod_directory, + }; + + mods.push(ns_mod); + } + + // Return found mod names + Ok(mods) +} + +/// Parse `packages` folder for installed mods. +pub fn parse_installed_package_mods( + game_install: &GameInstall, +) -> Result, anyhow::Error> { + let mut collected_mods: Vec = Vec::new(); + + let packages_folder = format!("{}/R2Northstar/packages/", game_install.game_path); + + let packages_dir = match fs::read_dir(packages_folder) { + Ok(res) => res, + Err(err) => { + // We couldn't read directory, probably cause it doesn't exist yet. + // In that case we just say no package mods installed. + log::warn!("{err}"); + return Ok(vec![]); + } + }; + + // Iteratore over folders in `packages` dir + for entry in packages_dir { + let entry = entry?; + let entry_path = entry.path(); + let entry_str = entry_path.file_name().unwrap().to_str().unwrap(); + log::warn!("Trying: {entry_str}"); + + // Use the struct's from_str function to verify format + if entry_path.is_dir() { + let package_thunderstore_string = match ParsedThunderstoreModString::from_str(entry_str) + { + Ok(res) => res, + Err(err) => { + log::warn!( + "Not a Thunderstore mod string \"{}\" cause: {}", + entry_path.display(), + err + ); + continue; + } + }; + let manifest_path = entry_path.join("manifest.json"); + let mods_path = entry_path.join("mods"); + + // Ensure `manifest.json` and `mods/` dir exist + if manifest_path.exists() && mods_path.is_dir() { + dbg!(mods_path.clone()); + + // Do something with mod path here + log::warn!("{}", mods_path.to_string_lossy()); + + // let paths = match std::fs::read_dir(mods_path) { + // Ok(paths) => paths, + // Err(_err) => todo!(), + // }; + + // // Get list of folders in `mods` directory + // for path in paths { + // log::info!("{path:?}"); + // let my_path = path.unwrap().path(); + // log::info!("{my_path:?}"); + + // let md = std::fs::metadata(my_path.clone()).unwrap(); + // if !md.is_dir() { + // continue; + // } + log::warn!("Found: {}", mods_path.display()); + let mods = + match parse_mods_in_package(mods_path, package_thunderstore_string.clone()) { + Ok(res) => res, + Err(err) => { + log::warn!("Failed parsing cause: {err}"); + continue; + } + }; + dbg!(mods.clone()); + collected_mods.extend(mods); + // } + + // let ns_mod = NorthstarMod { + // name: parsed_mod_json.name, + // version: parsed_mod_json.version, + // thunderstore_mod_string, + // enabled: false, // Placeholder + // directory: mod_directory, + // }; + } + } + } + + Ok(collected_mods) +} + /// Gets list of installed mods and their properties /// - name /// - is enabled? @@ -177,12 +360,21 @@ pub fn set_mod_enabled_status( pub fn get_installed_mods_and_properties( game_install: GameInstall, ) -> Result, String> { - // Get actually installed mods - let found_installed_mods = match legacy::parse_installed_mods(&game_install) { + log::info!("{game_install:?}"); + // Get installed mods from packages + let mut found_installed_mods = match parse_installed_package_mods(&game_install) { + Ok(res) => res, + Err(err) => return Err(err.to_string()), + }; + // Get installed legacy mods + let found_installed_legacy_mods = match legacy::parse_installed_mods(&game_install) { Ok(res) => res, Err(err) => return Err(err.to_string()), }; + // Combine list of package and legacy mods + found_installed_mods.extend(found_installed_legacy_mods); + // Get enabled mods as JSON let enabled_mods: serde_json::Value = match get_enabled_mods(&game_install) { Ok(enabled_mods) => enabled_mods, From babb50234988d4d17c5803c392a414b07fac30d6 Mon Sep 17 00:00:00 2001 From: GeckoEidechse Date: Mon, 17 Jul 2023 22:09:04 +0200 Subject: [PATCH 2/2] chore: Code cleanup --- src-tauri/src/mod_management/mod.rs | 48 ++--------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index 2ca613ca9..683b06a7b 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -184,6 +184,7 @@ pub fn set_mod_enabled_status( Ok(()) } +/// Resembles the bare minimum keys in Northstar `mods.json` #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ModJson { #[serde(rename = "Name")] @@ -197,7 +198,6 @@ pub fn parse_mods_in_package( package_mods_path: PathBuf, thunderstore_mod_string: ParsedThunderstoreModString, ) -> Result, anyhow::Error> { - dbg!(package_mods_path.clone()); let paths = match std::fs::read_dir(package_mods_path) { Ok(paths) => paths, Err(_err) => return Err(anyhow!("No mods folder found")), @@ -206,23 +206,15 @@ pub fn parse_mods_in_package( let mut directories: Vec = Vec::new(); let mut mods: Vec = Vec::new(); - log::info!("----------- {paths:?}"); - // Get list of folders in `mods` directory for path in paths { - // dbg!(path); - log::info!("{path:?}"); let my_path = path.unwrap().path(); - log::info!("{my_path:?}"); - let md = std::fs::metadata(my_path.clone()).unwrap(); if md.is_dir() { directories.push(my_path); } } - dbg!(directories.clone()); - // todo!(); // Iterate over folders and check if they are Northstar mods for directory in directories { let directory_str = directory.to_str().unwrap().to_string(); @@ -232,8 +224,6 @@ pub fn parse_mods_in_package( continue; } - // Parse mod.json and get mod name - // Read file into string and parse it let data = std::fs::read_to_string(mod_json_path.clone())?; let parsed_mod_json: ModJson = match json5::from_str(&data) { @@ -282,10 +272,8 @@ pub fn parse_installed_package_mods( // Iteratore over folders in `packages` dir for entry in packages_dir { - let entry = entry?; - let entry_path = entry.path(); + let entry_path = entry?.path(); let entry_str = entry_path.file_name().unwrap().to_str().unwrap(); - log::warn!("Trying: {entry_str}"); // Use the struct's from_str function to verify format if entry_path.is_dir() { @@ -306,27 +294,6 @@ pub fn parse_installed_package_mods( // Ensure `manifest.json` and `mods/` dir exist if manifest_path.exists() && mods_path.is_dir() { - dbg!(mods_path.clone()); - - // Do something with mod path here - log::warn!("{}", mods_path.to_string_lossy()); - - // let paths = match std::fs::read_dir(mods_path) { - // Ok(paths) => paths, - // Err(_err) => todo!(), - // }; - - // // Get list of folders in `mods` directory - // for path in paths { - // log::info!("{path:?}"); - // let my_path = path.unwrap().path(); - // log::info!("{my_path:?}"); - - // let md = std::fs::metadata(my_path.clone()).unwrap(); - // if !md.is_dir() { - // continue; - // } - log::warn!("Found: {}", mods_path.display()); let mods = match parse_mods_in_package(mods_path, package_thunderstore_string.clone()) { Ok(res) => res, @@ -335,17 +302,7 @@ pub fn parse_installed_package_mods( continue; } }; - dbg!(mods.clone()); collected_mods.extend(mods); - // } - - // let ns_mod = NorthstarMod { - // name: parsed_mod_json.name, - // version: parsed_mod_json.version, - // thunderstore_mod_string, - // enabled: false, // Placeholder - // directory: mod_directory, - // }; } } } @@ -360,7 +317,6 @@ pub fn parse_installed_package_mods( pub fn get_installed_mods_and_properties( game_install: GameInstall, ) -> Result, String> { - log::info!("{game_install:?}"); // Get installed mods from packages let mut found_installed_mods = match parse_installed_package_mods(&game_install) { Ok(res) => res,