diff options
Diffstat (limited to 'src-tauri/src')
-rw-r--r-- | src-tauri/src/main.rs | 42 | ||||
-rw-r--r-- | src-tauri/src/mod_management/mod.rs | 176 | ||||
-rw-r--r-- | src-tauri/src/repair_and_verify/mod.rs | 31 |
3 files changed, 246 insertions, 3 deletions
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 7db9e089..1ddb4c69 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -15,10 +15,12 @@ mod github; use github::release_notes::{get_northstar_release_notes, check_is_flightcore_outdated}; mod repair_and_verify; -use repair_and_verify::{verify_game_files, disable_all_but_core}; +use repair_and_verify::{clean_up_download_folder, disable_all_but_core, verify_game_files}; mod mod_management; -use mod_management::{set_mod_enabled_status, get_installed_mods_and_properties}; +use mod_management::{ + fc_download_mod_and_install, get_installed_mods_and_properties, set_mod_enabled_status, +}; use tauri::Manager; use tauri_plugin_store::PluginBuilder; @@ -92,6 +94,8 @@ fn main() { get_northstar_release_notes, linux_checks, get_installed_mods_caller, + install_mod_caller, + clean_up_download_folder_caller, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); @@ -275,7 +279,9 @@ async fn verify_game_files_caller(game_install: GameInstall) -> Result<String, S } #[tauri::command] -async fn get_enabled_mods_caller(game_install: GameInstall) -> Result<serde_json::value::Value, String> { +async fn get_enabled_mods_caller( + game_install: GameInstall, +) -> Result<serde_json::value::Value, String> { get_enabled_mods(game_install) } @@ -297,3 +303,33 @@ async fn disable_all_but_core_caller(game_install: GameInstall) -> Result<(), St async fn get_installed_mods_caller(game_install: GameInstall) -> Result<Vec<NorthstarMod>, String> { get_installed_mods_and_properties(game_install) } + +#[tauri::command] +/// Installs the specified mod +async fn install_mod_caller( + game_install: GameInstall, + thunderstore_mod_string: String, +) -> Result<(), String> { + fc_download_mod_and_install(game_install.clone(), thunderstore_mod_string).await?; + match clean_up_download_folder(game_install, false) { + Ok(()) => Ok(()), + Err(err) => { + println!("Failed to delete download folder due to {}", err); + // Failure to delete download folder is not an error in mod install + // As such ignore. User can still force delete if need be + Ok(()) + } + } +} + +#[tauri::command] +/// Installs the specified mod +async fn clean_up_download_folder_caller( + game_install: GameInstall, + force: bool, +) -> Result<(), String> { + match clean_up_download_folder(game_install, force) { + Ok(()) => Ok(()), + Err(err) => Err(err.to_string()), + } +} diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index ecfd3835..96bb1a0f 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -1,4 +1,5 @@ // This file contains various mod management functions +use async_recursion::async_recursion; use anyhow::{anyhow, Result}; use app::NorthstarMod; @@ -130,3 +131,178 @@ pub fn get_installed_mods_and_properties( Ok(installed_mods) } + +async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result<String, String> { + + // TODO: This will crash the thread if not internet connection exist. `match` should be used instead + let index = thermite::api::get_package_index().await.unwrap().to_vec(); + + // String replace works but more care should be taken in the future + let ts_mod_string_url = thunderstore_mod_string.replace("-", "/"); + + for ns_mod in index { + // Iterate over all versions of a given mod + for (_key, ns_mod) in &ns_mod.versions { + if ns_mod.url.contains(&ts_mod_string_url) { + dbg!(ns_mod.clone()); + return Ok(ns_mod.url.clone()); + } + } + } + + Err("Could not find mod on Thunderstore".to_string()) +} + +/// Adds given Thunderstore mod string to the given `mod.json` +/// This way we can later check whether a mod is outdated based on the TS mod string +fn add_thunderstore_mod_string( + path_to_mod_json: String, + thunderstore_mod_string: String, +) -> Result<(), anyhow::Error> { + + // Read file into string and parse it + let data = std::fs::read_to_string(path_to_mod_json.clone())?; + let parsed_json: serde_json::Value = json5::from_str(&data)?; + + // Insert the Thunderstore mod string + let mut parsed_json = parsed_json.as_object().unwrap().clone(); + parsed_json.insert( + "ThunderstoreModString".to_string(), + serde_json::Value::String(thunderstore_mod_string), + ); + + // And write back to disk + std::fs::write( + path_to_mod_json, + serde_json::to_string_pretty(&parsed_json)?, + )?; + + Ok(()) +} + +/// Returns a vector of modstrings containing the dependencies of a given mod +async fn get_mod_dependencies( + thunderstore_mod_string: String, +) -> Result<Vec<String>, anyhow::Error> { + dbg!(thunderstore_mod_string.clone()); + + // TODO: This will crash the thread if not internet connection exist. `match` should be used instead + let index = thermite::api::get_package_index().await.unwrap().to_vec(); + + // String replace works but more care should be taken in the future + let ts_mod_string_url = thunderstore_mod_string.replace("-", "/"); + + // Iterate over index + for ns_mod in index { + // Iterate over all versions of a given mod + for (_key, ns_mod) in &ns_mod.versions { + if ns_mod.url.contains(&ts_mod_string_url) { + dbg!(ns_mod.clone()); + return Ok(ns_mod.deps.clone()); + } + } + } + Ok(Vec::<String>::new()) +} + +// Copied from `libtermite` source code and modified +// Should be replaced with a library call to libthermite in the future +/// Download and install mod to the specified target. +#[async_recursion] +pub async fn fc_download_mod_and_install( + game_install: GameInstall, + thunderstore_mod_string: String, +) -> Result<(), String> { + // Get mods and download directories + let download_directory = format!( + "{}/___flightcore-temp-download-dir/", + game_install.game_path + ); + let mods_directory = format!("{}/R2Northstar/mods/", game_install.game_path); + + // Early return on empty string + if thunderstore_mod_string.len() == 0 { + return Err("Passed empty string".to_string()); + } + + let deps = match get_mod_dependencies(thunderstore_mod_string.clone()).await { + Ok(deps) => deps, + Err(err) => return Err(err.to_string()), + }; + dbg!(deps.clone()); + + // Recursively install dependencies + for dep in deps { + match fc_download_mod_and_install(game_install.clone(), dep).await { + Ok(()) => (), + Err(err) => { + if err.to_string() == "Cannot install Northstar as a mod!" { + continue; // For Northstar as a dependency, we just skip it + } + else { + return Err(err.to_string()) + } + } + }; + } + + // Prevent installing Northstar as a mod + // While it would fail during install anyway, having explicit error message is nicer + let blacklisted_mods = ["northstar-Northstar", "northstar-NorthstarReleaseCandidate", "ebkr-r2modman"]; + for blacklisted_mod in blacklisted_mods { + if thunderstore_mod_string.contains(blacklisted_mod) { + return Err("Cannot install Northstar as a mod!".to_string()); + } + } + + // Get download URL for the specified mod + let download_url = get_ns_mod_download_url(thunderstore_mod_string.clone()).await?; + + // Create download directory + match std::fs::create_dir_all(download_directory.clone()) { + Ok(()) => (), + Err(err) => return Err(err.to_string()), + }; + + let name = thunderstore_mod_string.clone(); + let path = format!( + "{}/___flightcore-temp-download-dir/{}.zip", + game_install.game_path, name + ); + + // Download the mod + let f = match thermite::core::actions::download_file(&download_url, path.clone()).await { + Ok(f) => f, + Err(e) => return Err(e.to_string()), + }; + + // Extract the mod to the mods directory + let pkg = match thermite::core::actions::install_mod(&f, std::path::Path::new(&mods_directory)) + { + Ok(pkg) => pkg, + Err(err) => return Err(err.to_string()), + }; + dbg!(pkg.clone()); + + // Add Thunderstore mod string to `mod.json` of installed NorthstarMods + for nsmod in pkg.mods { + let path_to_current_mod_json = format!( + "{}/{}/mod.json", + mods_directory, + nsmod.path.to_string_lossy() + ); + match add_thunderstore_mod_string(path_to_current_mod_json, thunderstore_mod_string.clone()) + { + Ok(()) => (), + Err(err) => { + println!("Failed setting modstring for {}", nsmod.name); + println!("{}", err); + } + } + } + + // Delete downloaded zip file + std::fs::remove_file(path).unwrap(); + + Ok(()) +} diff --git a/src-tauri/src/repair_and_verify/mod.rs b/src-tauri/src/repair_and_verify/mod.rs index 8f8fe633..188d3821 100644 --- a/src-tauri/src/repair_and_verify/mod.rs +++ b/src-tauri/src/repair_and_verify/mod.rs @@ -2,6 +2,7 @@ use app::{get_enabled_mods, GameInstall}; use crate::mod_management::set_mod_enabled_status; +use anyhow::anyhow; /// Verifies Titanfall2 game files pub fn verify_game_files(game_install: GameInstall) -> Result<String, String> { @@ -34,3 +35,33 @@ pub fn disable_all_but_core(game_install: GameInstall) -> Result<(), String> { Ok(()) } + +/// Deletes download folder +/// If `force` is FALSE, bails on non-empty folder +/// If `force` is TRUE, deletes folder even if non-empty +pub fn clean_up_download_folder( + game_install: GameInstall, + force: bool, +) -> Result<(), anyhow::Error> { + // Get download directory + let download_directory = format!( + "{}/___flightcore-temp-download-dir/", + game_install.game_path + ); + + // Check if files in folder + let download_dir_contents = std::fs::read_dir(download_directory.clone())?; + // dbg!(download_dir_contents); + + let mut count = 0; + download_dir_contents.inspect(|_| count += 1).for_each(drop); + + if count > 0 && !force { + return Err(anyhow!("Folder not empty, not deleting")); + } + + // Delete folder + std::fs::remove_dir_all(download_directory)?; + + Ok(()) +} |