diff options
Diffstat (limited to 'src-tauri/src')
-rw-r--r-- | src-tauri/src/constants.rs | 16 | ||||
-rw-r--r-- | src-tauri/src/github/mod.rs | 181 | ||||
-rw-r--r-- | src-tauri/src/github/pull_requests.rs | 25 | ||||
-rw-r--r-- | src-tauri/src/github/release_notes.rs | 10 | ||||
-rw-r--r-- | src-tauri/src/lib.rs | 276 | ||||
-rw-r--r-- | src-tauri/src/main.rs | 87 | ||||
-rw-r--r-- | src-tauri/src/mod_management/mod.rs | 88 | ||||
-rw-r--r-- | src-tauri/src/northstar/mod.rs | 18 | ||||
-rw-r--r-- | src-tauri/src/platform_specific/linux.rs | 4 | ||||
-rw-r--r-- | src-tauri/src/platform_specific/windows.rs | 6 | ||||
-rw-r--r-- | src-tauri/src/repair_and_verify/mod.rs | 10 | ||||
-rw-r--r-- | src-tauri/src/thunderstore/mod.rs | 4 |
12 files changed, 551 insertions, 174 deletions
diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs index 57a10e02..8c88df1e 100644 --- a/src-tauri/src/constants.rs +++ b/src-tauri/src/constants.rs @@ -1,6 +1,6 @@ // This file stores various global constants values - use const_format::concatcp; +use std::time::Duration; // FlightCore user agent for web requests pub const APP_USER_AGENT: &str = concatcp!("FlightCore/", env!("CARGO_PKG_VERSION")); @@ -29,8 +29,22 @@ pub const BLACKLISTED_MODS: [&str; 3] = [ // Titanfall2 game IDs on Origin/EA-App pub const TITANFALL2_ORIGIN_IDS: [&str; 2] = ["Origin.OFR.50.0001452", "Origin.OFR.50.0001456"]; +// Titanfall2 Steam App ID +pub const TITANFALL2_STEAM_ID: &str = "1237970"; + +// Order in which the sections for release notes should be displayed +pub const SECTION_ORDER: [&str; 9] = [ + "feat", "fix", "docs", "style", "refactor", "build", "test", "chore", "other", +]; + // GitHub API endpoints for launcher/mods PRs pub const PULLS_API_ENDPOINT_LAUNCHER: &str = "https://api.github.com/repos/R2Northstar/NorthstarLauncher/pulls"; pub const PULLS_API_ENDPOINT_MODS: &str = "https://api.github.com/repos/R2Northstar/NorthstarMods/pulls"; + +// Statistics (players and servers counts) refresh delay +pub const REFRESH_DELAY: Duration = Duration::from_secs(5 * 60); + +// Flightcore repo name and org name on GitHub +pub const FLIGHTCORE_REPO_NAME: &str = "R2NorthstarTools/FlightCore"; diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs index 942f0db2..b336ab5c 100644 --- a/src-tauri/src/github/mod.rs +++ b/src-tauri/src/github/mod.rs @@ -1,2 +1,183 @@ pub mod pull_requests; pub mod release_notes; + +use app::constants::{APP_USER_AGENT, FLIGHTCORE_REPO_NAME, SECTION_ORDER}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub struct Tag { + name: String, +} + +/// Wrapper type needed for frontend +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub struct TagWrapper { + label: String, + value: Tag, +} + +#[derive(Debug, Deserialize)] +struct CommitInfo { + sha: String, + commit: Commit, +} + +#[derive(Debug, Deserialize)] +struct Commit { + message: String, +} + +#[derive(Debug, Deserialize)] +struct Comparison { + commits: Vec<CommitInfo>, +} + +/// Get a list of tags on the FlightCore repo +#[tauri::command] +pub fn get_list_of_tags() -> Result<Vec<TagWrapper>, String> { + // Set the repository name. + + // Create a `reqwest` client with a user agent. + let client = reqwest::blocking::Client::builder() + .user_agent(APP_USER_AGENT) + .build() + .unwrap(); + + // Fetch the list of tags for the repository as a `Vec<Tag>`. + let tags_url = format!("https://api.github.com/repos/{}/tags", FLIGHTCORE_REPO_NAME); + let tags: Vec<Tag> = client.get(tags_url).send().unwrap().json().unwrap(); + + // Map each `Tag` element to a `TagWrapper` element with the desired label and `Tag` value. + let tag_wrappers: Vec<TagWrapper> = tags + .into_iter() + .map(|tag| TagWrapper { + label: tag.name.clone(), + value: tag, + }) + .collect(); + + Ok(tag_wrappers) +} + +/// Use GitHub API to compare two tags of the same repo against each other and get the resulting changes +#[tauri::command] +pub fn compare_tags(first_tag: Tag, second_tag: Tag) -> Result<String, String> { + // Fetch the list of commits between the two tags. + + // Create a `reqwest` client with a user agent. + let client = reqwest::blocking::Client::builder() + .user_agent(APP_USER_AGENT) + .build() + .unwrap(); + + let repo = "R2NorthstarTools/FlightCore"; + + let mut full_patch_notes = "".to_string(); + + let mut patch_notes: Vec<String> = [].to_vec(); + println!("{}", repo); + // let repo = "R2Northstar/NorthstarLauncher"; + let comparison_url = format!( + "https://api.github.com/repos/{}/compare/{}...{}", + repo, first_tag.name, second_tag.name + ); + + let comparison: Comparison = client.get(comparison_url).send().unwrap().json().unwrap(); + let commits = comparison.commits; + + // Display the list of commits. + println!( + "Commits between {} and {}:", + first_tag.name, second_tag.name + ); + + // Iterate over all commits in the diff + for commit in commits { + println!( + " * {} : {}", + commit.sha, + commit.commit.message.split('\n').next().unwrap() + ); + patch_notes.push( + commit + .commit + .message + .split('\n') + .next() + .unwrap() + .to_string(), + ); + } + + full_patch_notes += &generate_flightcore_release_notes(patch_notes); + + Ok(full_patch_notes.to_string()) +} + +/// Generate release notes in the format used for FlightCore +fn generate_flightcore_release_notes(commits: Vec<String>) -> String { + let grouped_commits = group_commits_by_type(commits); + let mut release_notes = String::new(); + + // Go over commit types and generate notes + for commit_type in SECTION_ORDER { + if let Some(commit_list) = grouped_commits.get(commit_type) { + if !commit_list.is_empty() { + let section_title = match commit_type { + "feat" => "**Features:**", + "fix" => "**Bug Fixes:**", + "docs" => "**Documentation:**", + "style" => "**Styles:**", + "refactor" => "**Code Refactoring:**", + "build" => "**Build:**", + "test" => "**Tests:**", + "chore" => "**Chores:**", + _ => "**Other:**", + }; + + release_notes.push_str(&format!("{}\n", section_title)); + + for commit_message in commit_list { + release_notes.push_str(&format!("- {}\n", commit_message)); + } + + release_notes.push('\n'); + } + } + } + + release_notes +} + +/// Group semantic commit messages by type +/// Commmit messages that are not formatted accordingly are marked as "other" +fn group_commits_by_type(commits: Vec<String>) -> HashMap<String, Vec<String>> { + let mut grouped_commits: HashMap<String, Vec<String>> = HashMap::new(); + let mut other_commits: Vec<String> = vec![]; + + for commit in commits { + let commit_parts: Vec<&str> = commit.splitn(2, ':').collect(); + if commit_parts.len() == 2 { + let commit_type = commit_parts[0].to_lowercase(); + let commit_description = commit_parts[1].trim().to_string(); + + // Check if known commit type + if SECTION_ORDER.contains(&commit_type.as_str()) { + let commit_list = grouped_commits.entry(commit_type.to_string()).or_default(); + commit_list.push(commit_description); + } else { + // otherwise add to list of "other" + other_commits.push(commit.to_string()); + } + } else { + other_commits.push(commit.to_string()); + } + } + grouped_commits.insert("other".to_string(), other_commits); + + grouped_commits +} diff --git a/src-tauri/src/github/pull_requests.rs b/src-tauri/src/github/pull_requests.rs index 586a4fb3..96ac623f 100644 --- a/src-tauri/src/github/pull_requests.rs +++ b/src-tauri/src/github/pull_requests.rs @@ -60,15 +60,15 @@ struct ArtifactsResponse { #[derive(Serialize, Deserialize, Debug, Clone, TS)] #[ts(export)] pub enum PullRequestType { - MODS, - LAUNCHER, + Mods, + Launcher, } /// Parse pull requests from specified URL pub async fn get_pull_requests(url: String) -> Result<Vec<PullsApiResponseElement>, String> { let json_response = match fetch_github_releases_api(&url).await { Ok(result) => result, - Err(err) => return Err(err.to_string()), + Err(err) => return Err(err), }; let pulls_response: Vec<PullsApiResponseElement> = match serde_json::from_str(&json_response) { @@ -85,8 +85,8 @@ pub async fn get_pull_requests_wrapper( install_type: PullRequestType, ) -> Result<Vec<PullsApiResponseElement>, String> { let api_pr_url = match install_type { - PullRequestType::MODS => PULLS_API_ENDPOINT_MODS, - PullRequestType::LAUNCHER => PULLS_API_ENDPOINT_LAUNCHER, + PullRequestType::Mods => PULLS_API_ENDPOINT_MODS, + PullRequestType::Launcher => PULLS_API_ENDPOINT_LAUNCHER, }; get_pull_requests(api_pr_url.to_string()).await @@ -142,7 +142,8 @@ fn get_mods_download_link(pull_request: PullsApiResponseElement) -> Result<Strin } /// Gets `nightly.link` artifact download link of a launcher PR -async fn get_launcher_download_link( +#[tauri::command] +pub async fn get_launcher_download_link( pull_request: PullsApiResponseElement, ) -> Result<String, String> { // Iterate over the first 10 pages of @@ -207,7 +208,7 @@ fn add_batch_file(game_install_path: &str) { match file.write_all(batch_file_content.as_bytes()) { Err(why) => panic!("couldn't write to {}: {}", display, why), - Ok(_) => println!("successfully wrote to {}", display), + Ok(_) => log::info!("successfully wrote to {}", display), } } @@ -258,7 +259,7 @@ pub async fn apply_launcher_pr( for file_name in files_to_copy { let source_file_path = format!("{}/{}", extract_directory, file_name); let destination_file_path = format!("{}/{}", game_install_path, file_name); - match std::fs::copy(&source_file_path, &destination_file_path) { + match std::fs::copy(source_file_path, destination_file_path) { Ok(_result) => (), Err(err) => { return Err(format!( @@ -280,7 +281,7 @@ pub async fn apply_launcher_pr( } } - println!("All done with installing launcher PR"); + log::info!("All done with installing launcher PR"); Ok(()) } @@ -308,9 +309,9 @@ pub async fn apply_mods_pr( // Delete previously managed folder if std::fs::remove_dir_all(profile_folder.clone()).is_err() { if std::path::Path::new(&profile_folder).exists() { - println!("Failed removing previous dir"); + log::error!("Failed removing previous dir"); } else { - println!("Failed removing folder that doesn't exist. Probably cause first run"); + log::warn!("Failed removing folder that doesn't exist. Probably cause first run"); } }; @@ -330,6 +331,6 @@ pub async fn apply_mods_pr( // Add batch file to launch right profile add_batch_file(game_install_path); - println!("All done with installing mods PR"); + log::info!("All done with installing mods PR"); Ok(()) } diff --git a/src-tauri/src/github/release_notes.rs b/src-tauri/src/github/release_notes.rs index b59358ec..b45442e0 100644 --- a/src-tauri/src/github/release_notes.rs +++ b/src-tauri/src/github/release_notes.rs @@ -20,7 +20,7 @@ pub struct FlightCoreVersion { // Fetches repo release API and returns response as string pub async fn fetch_github_releases_api(url: &str) -> Result<String, String> { - println!("Fetching releases notes from GitHub API"); + log::info!("Fetching releases notes from GitHub API"); let client = reqwest::Client::new(); let res = client @@ -40,13 +40,13 @@ pub async fn fetch_github_releases_api(url: &str) -> Result<String, String> { #[tauri::command] pub async fn get_newest_flightcore_version() -> Result<FlightCoreVersion, String> { // Get newest version number from GitHub API - println!("Checking GitHub API"); + log::info!("Checking GitHub API"); let url = "https://api.github.com/repos/R2NorthstarTools/FlightCore/releases/latest"; let res = fetch_github_releases_api(url).await?; let flightcore_version: FlightCoreVersion = serde_json::from_str(&res).expect("JSON was not well-formatted"); - println!("Done checking GitHub API"); + log::info!("Done checking GitHub API"); Ok(flightcore_version) } @@ -98,7 +98,7 @@ pub async fn get_northstar_release_notes() -> Result<Vec<ReleaseInfo>, String> { let release_info_vector: Vec<ReleaseInfo> = serde_json::from_str(&res).expect("JSON was not well-formatted"); - println!("Done checking GitHub API"); + log::info!("Done checking GitHub API"); - return Ok(release_info_vector); + Ok(release_info_vector) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 12c839fe..8ec7e4b1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,7 +1,6 @@ -use std::env; +use std::{cell::RefCell, env, fs, path::Path, time::Duration, time::Instant}; use anyhow::{anyhow, Context, Result}; -use sentry::{add_breadcrumb, Breadcrumb, Level}; mod northstar; @@ -15,11 +14,14 @@ use platform_specific::linux; use serde::{Deserialize, Serialize}; use sysinfo::SystemExt; +use tokio::time::sleep; use ts_rs::TS; use zip::ZipArchive; use northstar::get_northstar_version_number; +use crate::constants::TITANFALL2_STEAM_ID; + #[derive(Serialize, Deserialize, Debug, Clone)] pub enum InstallType { STEAM, @@ -50,10 +52,26 @@ pub struct NorthstarServer { pub player_count: i32, } +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub enum InstallState { + DOWNLOADING, + EXTRACTING, + DONE, +} + +#[derive(Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +struct InstallProgress { + current_downloaded: u64, + total_size: u64, + state: InstallState, +} + /// Check version number of a mod -pub fn check_mod_version_number(path_to_mod_folder: String) -> Result<String, anyhow::Error> { +pub fn check_mod_version_number(path_to_mod_folder: &str) -> Result<String, anyhow::Error> { // println!("{}", format!("{}/mod.json", path_to_mod_folder)); - let data = std::fs::read_to_string(format!("{}/mod.json", path_to_mod_folder))?; + let data = std::fs::read_to_string(format!("{path_to_mod_folder}/mod.json"))?; let parsed_json: serde_json::Value = serde_json::from_str(&data)?; // println!("{}", parsed_json); let mod_version_number = match parsed_json.get("Version").and_then(|value| value.as_str()) { @@ -61,7 +79,7 @@ pub fn check_mod_version_number(path_to_mod_folder: String) -> Result<String, an None => return Err(anyhow!("No version number found")), }; - println!("{}", mod_version_number); + log::info!("{}", mod_version_number); Ok(mod_version_number.to_string()) } @@ -81,8 +99,7 @@ pub fn linux_checks_librs() -> Result<(), String> { return Err(format!( "GLIBC is not version {} or greater", min_required_ldd_version - ) - .to_string()); + )); }; // All checks passed @@ -94,7 +111,7 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { // Attempt parsing Steam library directly match steamlocate::SteamDir::locate() { Some(mut steamdir) => { - let titanfall2_steamid = 1237970; + let titanfall2_steamid = TITANFALL2_STEAM_ID.parse().unwrap(); match steamdir.app(&titanfall2_steamid) { Some(app) => { // println!("{:#?}", app); @@ -104,10 +121,10 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { }; return Ok(game_install); } - None => println!("Couldn't locate Titanfall2"), + None => log::info!("Couldn't locate Titanfall2 Steam instal"), } } - None => println!("Couldn't locate Steam on this computer!"), + None => log::info!("Couldn't locate Steam on this computer!"), } // (On Windows only) try parsing Windows registry for Origin install path @@ -115,13 +132,13 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { match windows::origin_install_location_detection() { Ok(game_path) => { let game_install = GameInstall { - game_path: game_path, + game_path, install_type: InstallType::ORIGIN, }; return Ok(game_install); } Err(err) => { - println!("{}", err); + log::info!("{}", err); } }; @@ -130,13 +147,13 @@ pub fn find_game_install_location() -> Result<GameInstall, String> { /// Checks whether the provided path is a valid Titanfall2 gamepath by checking against a certain set of criteria pub fn check_is_valid_game_path(game_install_path: &str) -> Result<(), String> { - let path_to_titanfall2_exe = format!("{}/Titanfall2.exe", game_install_path); + let path_to_titanfall2_exe = format!("{game_install_path}/Titanfall2.exe"); let is_correct_game_path = std::path::Path::new(&path_to_titanfall2_exe).exists(); - println!("Titanfall2.exe exists in path? {}", is_correct_game_path); + log::info!("Titanfall2.exe exists in path? {}", is_correct_game_path); // Exit early if wrong game path if !is_correct_game_path { - return Err(format!("Incorrect game path \"{}\"", game_install_path)); // Return error cause wrong game path + return Err(format!("Incorrect game path \"{game_install_path}\"")); // Return error cause wrong game path } Ok(()) } @@ -159,12 +176,12 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> { ); if (*f.name()).ends_with('/') { - println!("Create directory {}", f.name()); + log::info!("Create directory {}", f.name()); std::fs::create_dir_all(target.join(f.name())) .context("Unable to create directory")?; continue; } else if let Some(p) = out.parent() { - std::fs::create_dir_all(&p).context("Unable to create directory")?; + std::fs::create_dir_all(p).context("Unable to create directory")?; } let mut outfile = std::fs::OpenOptions::new() @@ -173,7 +190,7 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> { .truncate(true) .open(&out)?; - println!("Write file {}", out.display()); + log::info!("Write file {}", out.display()); std::io::copy(&mut f, &mut outfile).context("Unable to write to file")?; } @@ -186,33 +203,83 @@ fn extract(zip_file: std::fs::File, target: &std::path::Path) -> Result<()> { ///Install N* from the provided mod /// ///Checks cache, else downloads the latest version -async fn do_install(nmod: &thermite::model::ModVersion, game_path: &std::path::Path) -> Result<()> { +async fn do_install( + window: tauri::Window, + nmod: &thermite::model::ModVersion, + game_path: &std::path::Path, +) -> Result<()> { let filename = format!("northstar-{}.zip", nmod.version); let download_directory = format!("{}/___flightcore-temp-download-dir/", game_path.display()); std::fs::create_dir_all(download_directory.clone())?; - let download_path = format!("{}/{}", download_directory.clone(), filename); - println!("{}", download_path); - - let nfile = match thermite::core::manage::download_file(&nmod.url, download_path) { + let download_path = format!("{}/{}", download_directory, filename); + log::info!("Download path: {download_path}"); + + let last_emit = RefCell::new(Instant::now()); // Keep track of the last time a signal was emitted + let nfile = match thermite::core::manage::download_file_with_progress( + &nmod.url, + download_path, + |delta, current, total| { + if delta != 0 { + // Only emit a signal once every 100ms + // This way we don't bombard the frontend with events on fast download speeds + let time_since_last_emit = Instant::now().duration_since(*last_emit.borrow()); + if time_since_last_emit >= Duration::from_millis(100) { + window + .emit( + "northstar-install-download-progress", + InstallProgress { + current_downloaded: current, + total_size: total, + state: InstallState::DOWNLOADING, + }, + ) + .unwrap(); + *last_emit.borrow_mut() = Instant::now(); + } + } + }, + ) { Ok(res) => res, Err(err) => return Err(anyhow!("Failed downloading Northstar {}", err)), }; - println!("Extracting Northstar..."); + window + .emit( + "northstar-install-download-progress", + InstallProgress { + current_downloaded: 0, + total_size: 0, + state: InstallState::EXTRACTING, + }, + ) + .unwrap(); + + log::info!("Extracting Northstar..."); extract(nfile, game_path)?; // Delete old copy - println!("Delete temp folder again"); + log::info!("Delete temp folder again"); std::fs::remove_dir_all(download_directory).unwrap(); - println!("Done!"); + log::info!("Done installing Northstar!"); + window + .emit( + "northstar-install-download-progress", + InstallProgress { + current_downloaded: 0, + total_size: 0, + state: InstallState::DONE, + }, + ) + .unwrap(); Ok(()) } pub async fn install_northstar( + window: tauri::Window, game_path: &str, northstar_package_name: Option<String>, ) -> Result<String, String> { @@ -234,20 +301,30 @@ pub async fn install_northstar( .ok_or_else(|| panic!("Couldn't find Northstar on thunderstore???")) .unwrap(); - // Breadcrumb for sentry to debug crash - add_breadcrumb(Breadcrumb { - // category: Some("auth".into()), - message: Some(format!("Install path \"{}\"", game_path)), - level: Level::Info, - ..Default::default() - }); + log::info!("Install path \"{}\"", game_path); - do_install( + match do_install( + window, nmod.versions.get(&nmod.latest).unwrap(), std::path::Path::new(game_path), ) .await - .unwrap(); + { + Ok(_) => (), + Err(err) => { + if game_path + .to_lowercase() + .contains(&r#"C:\Program Files\"#.to_lowercase()) + // default is `C:\Program Files\EA Games\Titanfall2` + { + return Err( + "Cannot install to default EA App install path, please move Titanfall2 to a different install location.".to_string(), + ); + } else { + return Err(err.to_string()); + } + } + } Ok(nmod.latest.clone()) } @@ -258,7 +335,7 @@ pub fn get_host_os() -> String { } pub fn launch_northstar( - game_install: GameInstall, + game_install: &GameInstall, bypass_checks: Option<bool>, ) -> Result<String, String> { dbg!(game_install.clone()); @@ -278,15 +355,12 @@ pub fn launch_northstar( )); } - let bypass_checks = match bypass_checks { - Some(bypass_checks) => bypass_checks, - None => false, - }; + let bypass_checks = bypass_checks.unwrap_or(false); // Only check guards if bypassing checks is not enabled if !bypass_checks { // Some safety checks before, should have more in the future - if get_northstar_version_number(game_install.game_path.clone()).is_err() { + if get_northstar_version_number(&game_install.game_path).is_err() { return Err(anyhow!("Not all checks were met").to_string()); } @@ -314,7 +388,7 @@ pub fn launch_northstar( { let ns_exe_path = format!("{}/NorthstarLauncher.exe", game_install.game_path); let _output = std::process::Command::new("C:\\Windows\\System32\\cmd.exe") - .args(&["/C", "start", "", &ns_exe_path]) + .args(["/C", "start", "", &ns_exe_path]) .spawn() .expect("failed to execute process"); return Ok("Launched game".to_string()); @@ -327,31 +401,109 @@ pub fn launch_northstar( )) } -pub fn check_origin_running() -> bool { - let s = sysinfo::System::new_all(); - for _process in s.processes_by_name("Origin.exe") { - // check here if this is your process - // dbg!(process); - // There's at least one Origin process, so we can launch - return true; +/// Prepare Northstar and Launch through Steam using the Browser Protocol +pub fn launch_northstar_steam( + game_install: &GameInstall, + _bypass_checks: Option<bool>, +) -> Result<String, String> { + if !matches!(game_install.install_type, InstallType::STEAM) { + return Err("Titanfall2 was not installed via Steam".to_string()); } - // Alternatively, check for EA Desktop - for _process in s.processes_by_name("EADesktop.exe") { - // There's at least one EADesktop process, so we can launch - return true; + + match steamlocate::SteamDir::locate() { + Some(mut steamdir) => { + if get_host_os() != "windows" { + let titanfall2_steamid: u32 = TITANFALL2_STEAM_ID.parse().unwrap(); + match steamdir.compat_tool(&titanfall2_steamid) { + Some(compat) => { + if !compat + .name + .clone() + .unwrap() + .to_ascii_lowercase() + .contains("northstarproton") + { + return Err( + "Titanfall2 was not configured to use NorthstarProton".to_string() + ); + } + } + None => { + return Err( + "Titanfall2 was not configured to use a compatibility tool".to_string() + ); + } + } + } + } + None => { + return Err("Couldn't access Titanfall2 directory".to_string()); + } } - false + + // Switch to Titanfall2 directory to set everything up + if std::env::set_current_dir(game_install.game_path.clone()).is_err() { + // We failed to get to Titanfall2 directory + return Err("Couldn't access Titanfall2 directory".to_string()); + } + + let run_northstar = "run_northstar.txt"; + let run_northstar_bak = "run_northstar.txt.bak"; + + if Path::new(run_northstar).exists() { + // rename should ovewrite existing files + fs::rename(run_northstar, run_northstar_bak).unwrap(); + } + + // Passing arguments gives users a prompt, so we use run_northstar.txt + fs::write(run_northstar, b"1").unwrap(); + + let retval = match open::that(format!("steam://run/{}/", TITANFALL2_STEAM_ID)) { + Ok(()) => Ok("Started game".to_string()), + Err(_err) => Err("Failed to launch Titanfall 2 via Steam".to_string()), + }; + + let is_err = retval.is_err(); + + // Handle the rest in the backround + tauri::async_runtime::spawn(async move { + // Starting the EA app and Titanfall might take a good minute or three + let mut wait_countdown = 60 * 3; + while wait_countdown > 0 && !check_northstar_running() && !is_err { + sleep(Duration::from_millis(1000)).await; + wait_countdown -= 1; + } + + // Northstar may be running, but it may not have loaded the file yet + sleep(Duration::from_millis(2000)).await; + + // intentionally ignore Result + let _ = fs::remove_file(run_northstar); + + if Path::new(run_northstar_bak).exists() { + fs::rename(run_northstar_bak, run_northstar).unwrap(); + } + }); + + retval +} + +pub fn check_origin_running() -> bool { + let s = sysinfo::System::new_all(); + let x = s.processes_by_name("Origin.exe").next().is_some() + || s.processes_by_name("EADesktop.exe").next().is_some(); + x } /// Checks if Northstar process is running pub fn check_northstar_running() -> bool { let s = sysinfo::System::new_all(); - for _process in s.processes_by_name("NorthstarLauncher.exe") { - // check here if this is your process - // dbg!(process); - return true; - } - false + let x = s + .processes_by_name("NorthstarLauncher.exe") + .next() + .is_some() + || s.processes_by_name("Titanfall2.exe").next().is_some(); + x } /// Helps with converting release candidate numbers which are different on Thunderstore @@ -364,7 +516,7 @@ pub fn convert_release_candidate_number(version_number: String) -> String { } /// Returns a serde json object of the parsed `enabledmods.json` file -pub fn get_enabled_mods(game_install: GameInstall) -> Result<serde_json::value::Value, String> { +pub fn get_enabled_mods(game_install: &GameInstall) -> Result<serde_json::value::Value, String> { let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); // Check for JSON file @@ -381,7 +533,7 @@ pub fn get_enabled_mods(game_install: GameInstall) -> Result<serde_json::value:: // Parse JSON let res: serde_json::Value = match serde_json::from_str(&data) { Ok(result) => result, - Err(err) => return Err(format!("Failed to read JSON due to: {}", err.to_string())), + Err(err) => return Err(format!("Failed to read JSON due to: {}", err)), }; // Return parsed data diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 50e439c8..3aa17459 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,15 +10,18 @@ use std::{ }; use app::{ - constants::{APP_USER_AGENT, MASTER_SERVER_URL, SERVER_BROWSER_ENDPOINT}, + constants::{APP_USER_AGENT, MASTER_SERVER_URL, REFRESH_DELAY, SERVER_BROWSER_ENDPOINT}, *, }; mod github; -use github::pull_requests::{apply_launcher_pr, apply_mods_pr, get_pull_requests_wrapper}; +use github::pull_requests::{ + apply_launcher_pr, apply_mods_pr, get_launcher_download_link, get_pull_requests_wrapper, +}; use github::release_notes::{ check_is_flightcore_outdated, get_newest_flightcore_version, get_northstar_release_notes, }; +use github::{compare_tags, get_list_of_tags}; mod repair_and_verify; use repair_and_verify::{ @@ -38,13 +41,20 @@ mod thunderstore; use thunderstore::query_thunderstore_packages_api; use tauri::{Manager, Runtime}; -use tauri_plugin_store::PluginBuilder; use tokio::time::sleep; #[derive(Default)] struct Counter(Arc<Mutex<i32>>); fn main() { + // Setup logger + let mut log_builder = pretty_env_logger::formatted_builder(); + log_builder.parse_filters("info"); + let logger = sentry_log::SentryLogger::with_dest(log_builder.build()); + + log::set_boxed_logger(Box::new(logger)).unwrap(); + log::set_max_level(log::LevelFilter::Info); + // Only enable Sentry crash logs on release #[cfg(not(debug_assertions))] let _guard = sentry::init(( @@ -57,7 +67,7 @@ fn main() { )); tauri::Builder::default() - .plugin(PluginBuilder::default().build()) + .plugin(tauri_plugin_store::Builder::default().build()) .setup(|app| { let app_handle = app.app_handle(); tauri::async_runtime::spawn(async move { @@ -86,6 +96,17 @@ fn main() { } }); + // Emit updated player and server count to GUI + let app_handle = app.app_handle(); + tauri::async_runtime::spawn(async move { + loop { + sleep(REFRESH_DELAY).await; + app_handle + .emit_all("northstar-statistics", get_server_player_count().await) + .unwrap(); + } + }); + Ok(()) }) .manage(Counter(Default::default())) @@ -100,6 +121,7 @@ fn main() { install_northstar_caller, update_northstar_caller, launch_northstar_caller, + launch_northstar_steam_caller, check_is_flightcore_outdated_caller, get_log_list, verify_game_files, @@ -117,9 +139,12 @@ fn main() { delete_thunderstore_mod, open_repair_window, query_thunderstore_packages_api, + get_list_of_tags, + compare_tags, get_pull_requests_wrapper, apply_launcher_pr, apply_mods_pr, + get_launcher_download_link, close_application, ]) .run(tauri::generate_context!()) @@ -142,7 +167,7 @@ fn force_panic() { #[tauri::command] /// Returns true if built in debug mode async fn is_debug_mode() -> bool { - return cfg!(debug_assertions); + cfg!(debug_assertions) } #[tauri::command] @@ -177,7 +202,7 @@ async fn get_flightcore_version_number() -> String { #[tauri::command] async fn get_northstar_version_number_caller(game_path: String) -> Result<String, String> { - match get_northstar_version_number(game_path) { + match get_northstar_version_number(&game_path) { Ok(version_number) => Ok(version_number), Err(err) => Err(err.to_string()), } @@ -209,10 +234,10 @@ async fn check_is_northstar_outdated( .expect("Couldn't find Northstar on thunderstore???"); // .ok_or_else(|| anyhow!("Couldn't find Northstar on thunderstore???"))?; - let version_number = match get_northstar_version_number(game_path) { + let version_number = match get_northstar_version_number(&game_path) { Ok(version_number) => version_number, Err(err) => { - println!("{}", err); + log::warn!("{}", err); // If we fail to get new version just assume we are up-to-date return Err(err.to_string()); } @@ -222,10 +247,10 @@ async fn check_is_northstar_outdated( let version_number = convert_release_candidate_number(version_number); if version_number != nmod.latest { - println!("Installed Northstar version outdated"); + log::info!("Installed Northstar version outdated"); Ok(true) } else { - println!("Installed Northstar version up-to-date"); + log::info!("Installed Northstar version up-to-date"); Ok(false) } } @@ -244,7 +269,7 @@ async fn verify_install_location(game_path: String) -> bool { match check_is_valid_game_path(&game_path) { Ok(()) => true, Err(err) => { - println!("{}", err); + log::warn!("{}", err); false } } @@ -259,15 +284,16 @@ async fn get_host_os_caller() -> String { #[tauri::command] /// Installs Northstar to the given path async fn install_northstar_caller( + window: tauri::Window, game_path: String, northstar_package_name: Option<String>, ) -> Result<bool, String> { - println!("Running"); - match install_northstar(&game_path, northstar_package_name).await { + log::info!("Running"); + match install_northstar(window, &game_path, northstar_package_name).await { Ok(_) => Ok(true), Err(err) => { - println!("{}", err); - Err(err.to_string()) + log::error!("{}", err); + Err(err) } } } @@ -275,17 +301,18 @@ async fn install_northstar_caller( #[tauri::command] /// Update Northstar install in the given path async fn update_northstar_caller( + window: tauri::Window, game_path: String, northstar_package_name: Option<String>, ) -> Result<bool, String> { - println!("Updating"); + log::info!("Updating Northstar"); // Simply re-run install with up-to-date version for upate - match install_northstar(&game_path, northstar_package_name).await { + match install_northstar(window, &game_path, northstar_package_name).await { Ok(_) => Ok(true), Err(err) => { - println!("{}", err); - Err(err.to_string()) + log::error!("{}", err); + Err(err) } } } @@ -296,7 +323,16 @@ async fn launch_northstar_caller( game_install: GameInstall, bypass_checks: Option<bool>, ) -> Result<String, String> { - launch_northstar(game_install, bypass_checks) + launch_northstar(&game_install, bypass_checks) +} + +#[tauri::command] +/// Launches Northstar +async fn launch_northstar_steam_caller( + game_install: GameInstall, + bypass_checks: Option<bool>, +) -> Result<String, String> { + launch_northstar_steam(&game_install, bypass_checks) } #[tauri::command] @@ -305,11 +341,11 @@ 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) { + fc_download_mod_and_install(&game_install, &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); + log::info!("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(()) @@ -323,7 +359,7 @@ async fn clean_up_download_folder_caller( game_install: GameInstall, force: bool, ) -> Result<(), String> { - match clean_up_download_folder(game_install, force) { + match clean_up_download_folder(&game_install, force) { Ok(()) => Ok(()), Err(err) => Err(err.to_string()), } @@ -353,7 +389,8 @@ async fn get_server_player_count() -> Result<(i32, usize), String> { // Sum up player count let total_player_count: i32 = ns_servers.iter().map(|server| server.player_count).sum(); - dbg!((total_player_count, server_count)); + log::info!("total_player_count: {}", total_player_count); + log::info!("server_count: {}", server_count); Ok((total_player_count, server_count)) } diff --git a/src-tauri/src/mod_management/mod.rs b/src-tauri/src/mod_management/mod.rs index d2b5040b..728e72c0 100644 --- a/src-tauri/src/mod_management/mod.rs +++ b/src-tauri/src/mod_management/mod.rs @@ -12,8 +12,6 @@ use std::path::PathBuf; use app::get_enabled_mods; use app::GameInstall; -use json5; - #[derive(Debug, Clone)] struct ParsedThunderstoreModString { author_name: String, @@ -25,7 +23,7 @@ impl std::str::FromStr for ParsedThunderstoreModString { type Err = (); fn from_str(s: &str) -> Result<Self, Self::Err> { - let mut parts = s.split("-"); + let mut parts = s.split('-'); let author_name = parts.next().unwrap().to_string(); let mod_name = parts.next().unwrap().to_string(); @@ -56,9 +54,9 @@ pub struct ModJson { } /// Gets all currently installed and enabled/disabled mods to rebuild `enabledmods.json` -pub fn rebuild_enabled_mods_json(game_install: GameInstall) -> Result<(), String> { +pub fn rebuild_enabled_mods_json(game_install: &GameInstall) -> Result<(), String> { let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); - let mods_and_properties = get_installed_mods_and_properties(game_install)?; + let mods_and_properties = get_installed_mods_and_properties(game_install.clone())?; // Create new mapping let mut my_map = serde_json::Map::new(); @@ -91,28 +89,27 @@ pub fn set_mod_enabled_status( let enabledmods_json_path = format!("{}/R2Northstar/enabledmods.json", game_install.game_path); // Parse JSON - let mut res: serde_json::Value = match get_enabled_mods(game_install.clone()) { + let mut res: serde_json::Value = match get_enabled_mods(&game_install) { Ok(res) => res, Err(err) => { - println!("Couldn't parse `enabledmod.json`: {}", err); - println!("Rebuilding file."); + log::warn!("Couldn't parse `enabledmod.json`: {}", err); + log::warn!("Rebuilding file."); - rebuild_enabled_mods_json(game_install.clone())?; + rebuild_enabled_mods_json(&game_install)?; // Then try again - let res = get_enabled_mods(game_install.clone())?; - res + get_enabled_mods(&game_install)? } }; // Check if key exists if res.get(mod_name.clone()).is_none() { // If it doesn't exist, rebuild `enabledmod.json` - println!("Value not found in `enabledmod.json`. Rebuilding file"); - rebuild_enabled_mods_json(game_install.clone())?; + log::info!("Value not found in `enabledmod.json`. Rebuilding file"); + rebuild_enabled_mods_json(&game_install)?; // Then try again - res = get_enabled_mods(game_install)?; + res = get_enabled_mods(&game_install)?; } // Update value @@ -129,7 +126,7 @@ pub fn set_mod_enabled_status( } /// Parses `manifest.json` for Thunderstore mod string -fn parse_for_thunderstore_mod_string(nsmod_path: String) -> Result<String, anyhow::Error> { +fn parse_for_thunderstore_mod_string(nsmod_path: &str) -> Result<String, anyhow::Error> { let manifest_json_path = format!("{}/manifest.json", nsmod_path); let ts_author_txt_path = format!("{}/thunderstore_author.txt", nsmod_path); @@ -152,7 +149,7 @@ fn parse_for_thunderstore_mod_string(nsmod_path: String) -> Result<String, anyho } /// Parse `mods` folder for installed mods. -fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, anyhow::Error> { +fn parse_installed_mods(game_install: &GameInstall) -> Result<Vec<NorthstarMod>, anyhow::Error> { let ns_mods_folder = format!("{}/R2Northstar/mods/", game_install.game_path); let paths = match std::fs::read_dir(ns_mods_folder) { @@ -189,7 +186,7 @@ fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, let parsed_mod_json: ModJson = match json5::from_str(&data) { Ok(parsed_json) => parsed_json, Err(err) => { - println!("Failed parsing {} with {}", mod_json_path, err.to_string()); + log::warn!("Failed parsing {} with {}", mod_json_path, err.to_string()); continue; } }; @@ -198,7 +195,7 @@ fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, // Attempt legacy method for getting Thunderstore string first Some(ts_mod_string) => Some(ts_mod_string), // Legacy method failed - None => match parse_for_thunderstore_mod_string(directory_str) { + None => match parse_for_thunderstore_mod_string(&directory_str) { Ok(thunderstore_mod_string) => Some(thunderstore_mod_string), Err(_err) => None, }, @@ -209,7 +206,7 @@ fn parse_installed_mods(game_install: GameInstall) -> Result<Vec<NorthstarMod>, let ns_mod = NorthstarMod { name: parsed_mod_json.name, version: parsed_mod_json.version, - thunderstore_mod_string: thunderstore_mod_string, + thunderstore_mod_string, enabled: false, // Placeholder directory: mod_directory, }; @@ -229,13 +226,13 @@ pub fn get_installed_mods_and_properties( game_install: GameInstall, ) -> Result<Vec<NorthstarMod>, String> { // Get actually installed mods - let found_installed_mods = match parse_installed_mods(game_install.clone()) { + let found_installed_mods = match parse_installed_mods(&game_install) { Ok(res) => res, Err(err) => return Err(err.to_string()), }; // Get enabled mods as JSON - let enabled_mods: serde_json::Value = match get_enabled_mods(game_install) { + let enabled_mods: serde_json::Value = match get_enabled_mods(&game_install) { Ok(enabled_mods) => enabled_mods, Err(_) => serde_json::from_str("{}").unwrap(), // `enabledmods.json` not found, create empty object }; @@ -256,7 +253,7 @@ pub fn get_installed_mods_and_properties( Ok(installed_mods) } -async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result<String, String> { +async fn get_ns_mod_download_url(thunderstore_mod_string: &str) -> 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().unwrap().to_vec(); @@ -276,7 +273,7 @@ async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result<Stri for ns_mod in index { // Iterate over all versions of a given mod - for (_key, ns_mod) in &ns_mod.versions { + for ns_mod in ns_mod.versions.values() { if ns_mod.url.contains(&ts_mod_string_url) { dbg!(ns_mod.clone()); return Ok(ns_mod.url.clone()); @@ -288,21 +285,19 @@ async fn get_ns_mod_download_url(thunderstore_mod_string: String) -> Result<Stri } /// 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()); +async fn get_mod_dependencies(thunderstore_mod_string: &str) -> Result<Vec<String>, anyhow::Error> { + dbg!(thunderstore_mod_string); // TODO: This will crash the thread if not internet connection exist. `match` should be used instead let index = thermite::api::get_package_index().unwrap().to_vec(); // String replace works but more care should be taken in the future - let ts_mod_string_url = thunderstore_mod_string.replace("-", "/"); + 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 { + for ns_mod in ns_mod.versions.values() { if ns_mod.url.contains(&ts_mod_string_url) { dbg!(ns_mod.clone()); return Ok(ns_mod.deps.clone()); @@ -317,8 +312,8 @@ async fn get_mod_dependencies( /// 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, + game_install: &GameInstall, + thunderstore_mod_string: &str, ) -> Result<(), String> { // Get mods and download directories let download_directory = format!( @@ -328,11 +323,11 @@ pub async fn fc_download_mod_and_install( let mods_directory = format!("{}/R2Northstar/mods/", game_install.game_path); // Early return on empty string - if thunderstore_mod_string.len() == 0 { + if thunderstore_mod_string.is_empty() { return Err("Passed empty string".to_string()); } - let deps = match get_mod_dependencies(thunderstore_mod_string.clone()).await { + let deps = match get_mod_dependencies(thunderstore_mod_string).await { Ok(deps) => deps, Err(err) => return Err(err.to_string()), }; @@ -340,13 +335,13 @@ pub async fn fc_download_mod_and_install( // Recursively install dependencies for dep in deps { - match fc_download_mod_and_install(game_install.clone(), dep).await { + match fc_download_mod_and_install(game_install, &dep).await { Ok(()) => (), Err(err) => { - if err.to_string() == "Cannot install Northstar as a mod!" { + if err == "Cannot install Northstar as a mod!" { continue; // For Northstar as a dependency, we just skip it } else { - return Err(err.to_string()); + return Err(err); } } }; @@ -361,7 +356,7 @@ pub async fn fc_download_mod_and_install( } // Get download URL for the specified mod - let download_url = get_ns_mod_download_url(thunderstore_mod_string.clone()).await?; + let download_url = get_ns_mod_download_url(thunderstore_mod_string).await?; // Create download directory match std::fs::create_dir_all(download_directory.clone()) { @@ -369,20 +364,19 @@ pub async fn fc_download_mod_and_install( 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 + "{}/___flightcore-temp-download-dir/{thunderstore_mod_string}.zip", + game_install.game_path ); // Download the mod - let f = match thermite::core::manage::download_file(&download_url, path.clone()) { + let f = match thermite::core::manage::download_file(download_url, path.clone()) { Ok(f) => f, Err(e) => return Err(e.to_string()), }; // Get Thunderstore mod author - let author = thunderstore_mod_string.split("-").next().unwrap(); + let author = thunderstore_mod_string.split('-').next().unwrap(); // Extract the mod to the mods directory match thermite::core::manage::install_mod(author, &f, std::path::Path::new(&mods_directory)) { @@ -397,7 +391,7 @@ pub async fn fc_download_mod_and_install( } /// Deletes a given Northstar mod folder -fn delete_mod_folder(ns_mod_directory: String) -> Result<(), String> { +fn delete_mod_folder(ns_mod_directory: &str) -> Result<(), String> { let ns_mod_dir_path = std::path::Path::new(&ns_mod_directory); // Safety check: Check whether `mod.json` exists and exit early if not @@ -408,7 +402,7 @@ fn delete_mod_folder(ns_mod_directory: String) -> Result<(), String> { return Err(format!("mod.json does not exist in {}", ns_mod_directory)); } - match std::fs::remove_dir_all(&ns_mod_directory) { + match std::fs::remove_dir_all(ns_mod_directory) { Ok(()) => Ok(()), Err(err) => Err(format!("Failed deleting mod: {err}")), } @@ -432,7 +426,7 @@ pub fn delete_northstar_mod(game_install: GameInstall, nsmod_name: String) -> Re // Installed mod matches specified mod if installed_ns_mod.name == nsmod_name { // Delete folder - return delete_mod_folder(installed_ns_mod.directory); + return delete_mod_folder(&installed_ns_mod.directory); } } @@ -483,7 +477,7 @@ pub fn delete_thunderstore_mod( } } - if !(mod_folders_to_remove.len() > 0) { + if mod_folders_to_remove.is_empty() { return Err(format!( "No mods removed as no Northstar mods matching {thunderstore_mod_string} were found to be installed." )); @@ -491,7 +485,7 @@ pub fn delete_thunderstore_mod( // Delete given folders for mod_folder in mod_folders_to_remove { - delete_mod_folder(mod_folder)?; + delete_mod_folder(&mod_folder)?; } Ok(()) diff --git a/src-tauri/src/northstar/mod.rs b/src-tauri/src/northstar/mod.rs index a043632c..7bd0b0a3 100644 --- a/src-tauri/src/northstar/mod.rs +++ b/src-tauri/src/northstar/mod.rs @@ -5,25 +5,23 @@ use crate::{check_mod_version_number, constants::CORE_MODS}; use anyhow::anyhow; /// Returns the current Northstar version number as a string -pub fn get_northstar_version_number(game_path: String) -> Result<String, anyhow::Error> { - println!("{}", game_path); - // println!("{:?}", install_type); +pub fn get_northstar_version_number(game_path: &str) -> Result<String, anyhow::Error> { + log::info!("{}", game_path); // TODO: // Check if NorthstarLauncher.exe exists and check its version number let profile_folder = "R2Northstar"; - let initial_version_number = match check_mod_version_number(format!( - "{}/{}/mods/{}", - game_path, profile_folder, CORE_MODS[0] + let initial_version_number = match check_mod_version_number(&format!( + "{game_path}/{profile_folder}/mods/{}", + CORE_MODS[0] )) { Ok(version_number) => version_number, Err(err) => return Err(err), }; for core_mod in CORE_MODS { - let current_version_number = match check_mod_version_number(format!( - "{}/{}/mods/{}", - game_path, profile_folder, core_mod + let current_version_number = match check_mod_version_number(&format!( + "{game_path}/{profile_folder}/mods/{core_mod}", )) { Ok(version_number) => version_number, Err(err) => return Err(err), @@ -33,7 +31,7 @@ pub fn get_northstar_version_number(game_path: String) -> Result<String, anyhow: return Err(anyhow!("Found version number mismatch")); } } - println!("All mods same version"); + log::info!("All mods same version"); Ok(initial_version_number) } diff --git a/src-tauri/src/platform_specific/linux.rs b/src-tauri/src/platform_specific/linux.rs index eb9fbea6..4b9964e9 100644 --- a/src-tauri/src/platform_specific/linux.rs +++ b/src-tauri/src/platform_specific/linux.rs @@ -14,10 +14,10 @@ pub fn check_glibc_v() -> f32 { let lddvl: Vec<&str> = lddva.split('\n').collect(); let lddvlo = &lddvl[0]; let reg = Regex::new(r"(2.\d{2}$)").unwrap(); - for caps in reg.captures_iter(lddvlo) { + if let Some(caps) = reg.captures_iter(lddvlo).next() { return caps.get(1).unwrap().as_str().parse::<f32>().unwrap(); // theres prolly a better way ijdk how tho } - return 0.0; // this shouldnt ever be reached but it has to be here + 0.0 // this shouldnt ever be reached but it has to be here } /* diff --git a/src-tauri/src/platform_specific/windows.rs b/src-tauri/src/platform_specific/windows.rs index 21772b91..004beb6e 100644 --- a/src-tauri/src/platform_specific/windows.rs +++ b/src-tauri/src/platform_specific/windows.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Result}; use crate::{check_is_valid_game_path, constants::TITANFALL2_ORIGIN_IDS}; -/// Runs a powershell command and parses output to get Titanfall2 install location on Origin +/// Gets Titanfall2 install location on Origin pub fn origin_install_location_detection() -> Result<String, anyhow::Error> { // Iterate over known Titanfall2 Origin IDs for origin_id in TITANFALL2_ORIGIN_IDS { @@ -18,14 +18,14 @@ pub fn origin_install_location_detection() -> Result<String, anyhow::Error> { return Ok(game_path_str.to_string()); } Err(err) => { - println!("{}", err.to_string()); + log::warn!("{}", err); continue; // Not a valid game path } } } } Err(err) => { - println!("Couldn't find {origin_id}: {err}") + log::warn!("Couldn't find {origin_id}: {err}") } } } diff --git a/src-tauri/src/repair_and_verify/mod.rs b/src-tauri/src/repair_and_verify/mod.rs index 9d020f15..ecf5e1ec 100644 --- a/src-tauri/src/repair_and_verify/mod.rs +++ b/src-tauri/src/repair_and_verify/mod.rs @@ -15,9 +15,9 @@ pub fn verify_game_files(game_install: GameInstall) -> Result<String, String> { #[tauri::command] pub fn disable_all_but_core(game_install: GameInstall) -> Result<(), String> { // Rebuild `enabledmods.json` first to ensure all mods are added - rebuild_enabled_mods_json(game_install.clone())?; + rebuild_enabled_mods_json(&game_install)?; - let current_mods = get_enabled_mods(game_install.clone())?; + let current_mods = get_enabled_mods(&game_install)?; // Disable all mods, set core mods to enabled for (key, _value) in current_mods.as_object().unwrap() { @@ -37,7 +37,7 @@ pub fn disable_all_but_core(game_install: GameInstall) -> Result<(), String> { /// 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, + game_install: &GameInstall, force: bool, ) -> Result<(), anyhow::Error> { // Get download directory @@ -51,7 +51,7 @@ pub fn clean_up_download_folder( // dbg!(download_dir_contents); let mut count = 0; - download_dir_contents.inspect(|_| count += 1).for_each(drop); + download_dir_contents.for_each(|_| count += 1); if count > 0 && !force { return Err(anyhow!("Folder not empty, not deleting")); @@ -84,7 +84,7 @@ pub fn get_log_list(game_install: GameInstall) -> Result<Vec<std::path::PathBuf> } } - if log_files.len() > 0 { + if !log_files.is_empty() { Ok(log_files) } else { Err("No logs found".to_string()) diff --git a/src-tauri/src/thunderstore/mod.rs b/src-tauri/src/thunderstore/mod.rs index 9151ba7c..e9eb30d7 100644 --- a/src-tauri/src/thunderstore/mod.rs +++ b/src-tauri/src/thunderstore/mod.rs @@ -43,7 +43,7 @@ pub struct ThunderstoreModVersion { /// Queries Thunderstore packages API #[tauri::command] pub async fn query_thunderstore_packages_api() -> Result<Vec<ThunderstoreMod>, String> { - println!("Fetching Thunderstore API"); + log::info!("Fetching Thunderstore API"); // Fetches let url = "https://northstar.thunderstore.io/api/v1/package/"; @@ -66,7 +66,7 @@ pub async fn query_thunderstore_packages_api() -> Result<Vec<ThunderstoreMod>, S }; // Remove some mods from listing - let to_remove_set: HashSet<&str> = BLACKLISTED_MODS.iter().map(|s| s.as_ref()).collect(); + let to_remove_set: HashSet<&str> = BLACKLISTED_MODS.iter().copied().collect(); let filtered_packages = parsed_json .into_iter() .filter(|package| !to_remove_set.contains(&package.full_name.as_ref())) |