diff options
author | GeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com> | 2023-03-19 21:19:38 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-19 20:19:38 +0000 |
commit | 6c0f600cc6dcb22b3990283aa5d3d7593da6bbb5 (patch) | |
tree | 45fb30229c437bc70a1abdceb020400a9ff8dbb8 | |
parent | 72070a9f6809f679ddd5874edfe454a8d8559092 (diff) | |
download | FlightCore-6c0f600cc6dcb22b3990283aa5d3d7593da6bbb5.tar.gz FlightCore-6c0f600cc6dcb22b3990283aa5d3d7593da6bbb5.zip |
refactor: Fix PR downloads permission issue (#217)
* fix: Download PRs into Titanfall2 folder
Avoids any folder write permission issues
* fix: Download Mod PRs into Titanfall2 folder
Avoids any folder write permission issues
* build: Add libraries for zip extract
* refactor: Download mods zip into memory
and extract it from there directly into Titanfall2 dir
* fix: Stop creating unneeded temp download folder
* refactor: Download launcher zip into memory
and extract it from there into a temp folder
then only copy out necessary files
and delete the rest
* docs: Add comment
* refactor: Remove unused functions
* fix: Smoothly handle misformatted zip file
Usually caused by the downloaded file not actually being a zip
Catches the error and displays error message if it occurs.
* feat: Exit early if download was unsuccessful
* chore: Remove leftover commented out code
* fix: Write mod files into correct subdirectory
Was writing them into parent instead by accident which meant that
Northstar wouldn't actually find it.
* refactor: Define profile path once
Instead of recreating the string everytime
-rw-r--r-- | src-tauri/Cargo.lock | 12 | ||||
-rw-r--r-- | src-tauri/Cargo.toml | 2 | ||||
-rw-r--r-- | src-tauri/src/github/pull_requests.rs | 263 |
3 files changed, 90 insertions, 187 deletions
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7ecb04df..d1779cf0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -109,6 +109,7 @@ dependencies = [ "tokio", "ts-rs", "zip", + "zip-extract", ] [[package]] @@ -5037,6 +5038,17 @@ dependencies = [ ] [[package]] +name = "zip-extract" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb654964c003959ed64cbd0d7b329bcdcbd9690facd50c8617748d3622543972" +dependencies = [ + "log", + "thiserror", + "zip", +] + +[[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index fafad444..a6142cea 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -54,6 +54,8 @@ chrono = "0.4.23" ts-rs = "6.1" # const formatting const_format = "0.2.30" +# Extracting zip files easily +zip-extract = "0.1.2" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/github/pull_requests.rs b/src-tauri/src/github/pull_requests.rs index 2b7be30b..586a4fb3 100644 --- a/src-tauri/src/github/pull_requests.rs +++ b/src-tauri/src/github/pull_requests.rs @@ -4,7 +4,6 @@ use anyhow::anyhow; use app::check_is_valid_game_path; use app::constants::{APP_USER_AGENT, PULLS_API_ENDPOINT_LAUNCHER, PULLS_API_ENDPOINT_MODS}; use serde::{Deserialize, Serialize}; -use std::fs; use std::fs::File; use std::io; use std::io::prelude::*; @@ -93,61 +92,6 @@ pub async fn get_pull_requests_wrapper( get_pull_requests(api_pr_url.to_string()).await } -fn unzip(zip_file_name: &str) -> String { - let fname = std::path::Path::new(zip_file_name); - let file = fs::File::open(fname).unwrap(); - - let mut archive = zip::ZipArchive::new(file).unwrap(); - - let mut folder_name = "".to_string(); - - for i in 0..archive.len() { - let mut file = archive.by_index(i).unwrap(); - let outpath = match file.enclosed_name() { - Some(path) => path.to_owned(), - None => continue, - }; - - { - let comment = file.comment(); - if !comment.is_empty() { - println!("File {} comment: {}", i, comment); - } - } - - if i == 0 { - // Sanity check that it's a folder - assert!((*file.name()).ends_with('/')); - - folder_name = format!("{}", outpath.display()); - println!("{}", folder_name); - } - - if (*file.name()).ends_with('/') { - fs::create_dir_all(&outpath).unwrap(); - } else { - if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(p).unwrap(); - } - } - let mut outfile = fs::File::create(&outpath).unwrap(); - io::copy(&mut file, &mut outfile).unwrap(); - } - - // Get and Set permissions - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - - if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap(); - } - } - } - folder_name -} - pub async fn check_github_api(url: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> { let client = reqwest::Client::new(); let res = client @@ -165,99 +109,20 @@ pub async fn check_github_api(url: &str) -> Result<serde_json::Value, Box<dyn st Ok(json) } -/// Downloads a file from given URL -async fn download_zip(download_url: String, location: String) -> Result<(), anyhow::Error> { - let client = reqwest::Client::new(); - let resp = client - .get(download_url) - .header(reqwest::header::USER_AGENT, APP_USER_AGENT) - .send() - .await?; - - // Error out earlier if non-successful response - if !resp.status().is_success() { - // Return error cause wrong game path - return Err(anyhow!( - "Couldn't download zip. Received error code \"{}\"", - resp.status() - )); - } - - let mut out = fs::File::create(format!("{}/ns-dev-test-helper-temp-pr-files.zip", location)) - .expect("failed to create file"); - let bytes = resp.bytes().await?; - let mut cursor = std::io::Cursor::new(bytes); - std::io::copy(&mut cursor, &mut out)?; - Ok(()) -} - -fn unzip_launcher_zip(zip_file_name: &str) -> String { - let outfolder_name = "ns-dev-test-helper-temp-pr-files"; - let fname = std::path::Path::new(zip_file_name); - let file = fs::File::open(fname).unwrap(); - - let mut archive = zip::ZipArchive::new(file).unwrap(); - - fs::create_dir_all(outfolder_name).unwrap(); - - for i in 0..archive.len() { - let mut file = archive.by_index(i).unwrap(); - let outpath = match file.enclosed_name() { - Some(path) => path.to_owned(), - None => continue, - }; - - { - let comment = file.comment(); - if !comment.is_empty() { - println!("File {} comment: {}", i, comment); - } - } - - // Only extract two hardcoded files - if *file.name() == *"NorthstarLauncher.exe" || *file.name() == *"Northstar.dll" { - println!( - "File {} extracted to \"{}\" ({} bytes)", - i, - outpath.display(), - file.size() - ); - if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(p).unwrap(); - } - } - let mut outfile = - fs::File::create(format!("{}/{}", outfolder_name, outpath.display())).unwrap(); - std::io::copy(&mut file, &mut outfile).unwrap(); - } +/// Downloads a file from given URL into an array in memory +async fn download_zip_into_memory(download_url: String) -> Result<Vec<u8>, anyhow::Error> { + let client = reqwest::Client::builder() + .user_agent(APP_USER_AGENT) + .build()?; - // Get and Set permissions - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; + let response = client.get(download_url).send().await?; - if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap(); - } - } + if !response.status().is_success() { + return Err(anyhow!("Request unsuccessful: {}", response.status())); } - outfolder_name.to_string() -} -/// Recursively copies files from one directory to another -fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> { - fs::create_dir_all(&dst)?; - for entry in fs::read_dir(src)? { - let entry = entry?; - let ty = entry.file_type()?; - if ty.is_dir() { - copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; - } else { - fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; - } - } - Ok(()) + let bytes = response.bytes().await?; + Ok(bytes.to_vec()) } /// Gets GitHub download link of a mods PR @@ -356,28 +221,64 @@ pub async fn apply_launcher_pr( check_is_valid_game_path(game_install_path)?; // get download link - let download_url = get_launcher_download_link(pull_request).await?; + let download_url = get_launcher_download_link(pull_request.clone()).await?; - // download - match download_zip(download_url, ".".to_string()).await { - Ok(_) => (), + let archive = match download_zip_into_memory(download_url).await { + Ok(archive) => archive, Err(err) => return Err(err.to_string()), }; - // extract - let zip_extract_folder_name = unzip_launcher_zip("ns-dev-test-helper-temp-pr-files.zip"); - fs::remove_file("ns-dev-test-helper-temp-pr-files.zip").unwrap(); - - // Copy downloaded folder to game install folder - match copy_dir_all(zip_extract_folder_name.clone(), game_install_path) { + let extract_directory = format!( + "{}/___flightcore-temp-download-dir/launcher-pr-{}", + game_install_path, pull_request.number + ); + match std::fs::create_dir_all(extract_directory.clone()) { Ok(_) => (), Err(err) => { - return Err(format!("Failed copying files: {}", err)); + return Err(format!( + "Failed creating temporary download directory: {}", + err + )) + } + }; + + let target_dir = std::path::PathBuf::from(extract_directory.clone()); // Doesn't need to exist + match zip_extract::extract(io::Cursor::new(archive), &target_dir, true) { + Ok(()) => (), + Err(err) => { + return Err(format!("Failed unzip: {}", err)); } + }; + + // Copy only necessary files from temp dir + // Copy: + // - NorthstarLauncher.exe + // - Northstar.dll + let files_to_copy = vec!["NorthstarLauncher.exe", "Northstar.dll"]; + 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) { + Ok(_result) => (), + Err(err) => { + return Err(format!( + "Failed to copy necessary file {} from temp dir: {}", + file_name, err + )) + } + }; } - // Delete old unzipped - fs::remove_dir_all(zip_extract_folder_name).unwrap(); + // delete extract directory + match std::fs::remove_dir_all(&extract_directory) { + Ok(()) => (), + Err(err) => { + return Err(format!( + "Failed to delete temporary download directory: {}", + err + )) + } + } println!("All done with installing launcher PR"); Ok(()) @@ -397,47 +298,35 @@ pub async fn apply_mods_pr( Err(err) => return Err(err.to_string()), }; - match download_zip(download_url, ".".to_string()).await { - Ok(()) => (), + let archive = match download_zip_into_memory(download_url).await { + Ok(archive) => archive, Err(err) => return Err(err.to_string()), }; - // Extract folder and delete zip - let zip_extract_folder_name = unzip("ns-dev-test-helper-temp-pr-files.zip"); - fs::remove_file("ns-dev-test-helper-temp-pr-files.zip").unwrap(); + let profile_folder = format!("{}/R2Northstar-PR-test-managed-folder", game_install_path); // Delete previously managed folder - if std::fs::remove_dir_all(format!( - "{}/R2Northstar-PR-test-managed-folder", - game_install_path - )) - .is_err() - { - if std::path::Path::new(&format!( - "{}/R2Northstar-PR-test-managed-folder", - game_install_path - )) - .exists() - { + if std::fs::remove_dir_all(profile_folder.clone()).is_err() { + if std::path::Path::new(&profile_folder).exists() { println!("Failed removing previous dir"); } else { println!("Failed removing folder that doesn't exist. Probably cause first run"); } }; - // Copy downloaded folder to game install folder - copy_dir_all( - zip_extract_folder_name.clone(), - format!( - "{}/R2Northstar-PR-test-managed-folder/mods", - game_install_path - ), - ) - .unwrap(); - - // Delete old copy - std::fs::remove_dir_all(zip_extract_folder_name).unwrap(); + // Create profile folder + match std::fs::create_dir_all(profile_folder.clone()) { + Ok(()) => (), + Err(err) => return Err(err.to_string()), + } + let target_dir = std::path::PathBuf::from(format!("{}/mods", profile_folder)); // Doesn't need to exist + match zip_extract::extract(io::Cursor::new(archive), &target_dir, true) { + Ok(()) => (), + Err(err) => { + return Err(format!("Failed unzip: {}", err)); + } + }; // Add batch file to launch right profile add_batch_file(game_install_path); |