From 9c0e47987cdeb5bfba2693e7fd44067ce8c89c87 Mon Sep 17 00:00:00 2001 From: GeckoEidechse Date: Sun, 16 Jul 2023 19:13:19 +0200 Subject: 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 a2aca85a..2ca613ca 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, -- cgit v1.2.3