Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev: Packages #429

Draft
wants to merge 42 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9c0e479
feat: Initial support for reading NS packages dir
GeckoEidechse Jul 16, 2023
008e31e
Merge branch 'main' into feat/read-packages-dir
GeckoEidechse Jul 17, 2023
babb502
chore: Code cleanup
GeckoEidechse Jul 17, 2023
58b8dce
Merge branch 'main' into feat/read-packages-dir
GeckoEidechse Jul 17, 2023
7e301bd
feat: Add ability to delete a Thunderstore package
GeckoEidechse Jul 17, 2023
0fc68de
fix: Formatting
GeckoEidechse Jul 17, 2023
0cd98b6
Merge branch 'main' into feat/read-packages-dir
GeckoEidechse Jul 18, 2023
6d0d813
feat: Initial support for installing packages
GeckoEidechse Jul 18, 2023
24090dd
feat: Give wrong version number on legacy TS mods
GeckoEidechse Jul 18, 2023
7df3de9
Merge remote-tracking branch 'origin/feat/delete-package' into dev/pa…
GeckoEidechse Jul 18, 2023
fb8cfbf
Merge remote-tracking branch 'origin/feat/install-packages' into dev/…
GeckoEidechse Jul 18, 2023
5fc6669
Merge remote-tracking branch 'origin/feat/upgrade-to-packges' into de…
GeckoEidechse Jul 18, 2023
9f9c555
feat: Support deleting older versions of package
GeckoEidechse Jul 18, 2023
8a1c8d3
fix: Typo in comment
GeckoEidechse Jul 18, 2023
ea74685
fix: Add missing semi colon
GeckoEidechse Jul 18, 2023
6958580
feat: Delete packages installed in legacy mode
GeckoEidechse Jul 18, 2023
349a3cb
fix: Remove unnecessary reference
GeckoEidechse Jul 18, 2023
a2b80ef
Merge branch 'main' into feat/read-packages-dir
GeckoEidechse Jul 19, 2023
f8d321a
Merge remote-tracking branch 'origin/feat/read-packages-dir' into dev…
GeckoEidechse Jul 19, 2023
794817d
Merge branch 'main' into dev/packages
GeckoEidechse Jul 19, 2023
48bfce1
feat: Add ability to delete a Thunderstore package
GeckoEidechse Jul 19, 2023
0d06d88
fix: Remove debug prints
GeckoEidechse Jul 19, 2023
28c2627
Merge branch 'feat/delete-package' into dev/packages
GeckoEidechse Jul 19, 2023
56ac4a1
Merge branch 'main' into dev/packages
GeckoEidechse Jul 19, 2023
e0aab46
fix: Typo in comment
GeckoEidechse Jul 19, 2023
5f3dcde
Merge branch 'main' into feat/install-packages
GeckoEidechse Jul 19, 2023
e3c68e1
fix: Typo in Cargo.toml
GeckoEidechse Jul 19, 2023
8ae71ef
feat: Delete old version of package on update
GeckoEidechse Jul 19, 2023
c16ff2e
chore: Sync with other branch
GeckoEidechse Jul 19, 2023
d335e74
feat: Add sanity check for package install
GeckoEidechse Jul 20, 2023
ef088ee
Merge branch 'feat/install-packages' into dev/packages
GeckoEidechse Jul 20, 2023
c5bffe6
fix: Remove accidentally duplicated code
GeckoEidechse Jul 20, 2023
06cdb5d
Bump libthermite to 0.7.0-alpha.1
GeckoEidechse Jul 20, 2023
68892a4
fix: Formatting
GeckoEidechse Jul 20, 2023
d7fdbf8
fix: Bump libthermite to 0.7.0-beta
GeckoEidechse Jul 21, 2023
448c6f3
docs: Add comment explaining "legacy package"
GeckoEidechse Jul 21, 2023
e4f995b
docs: Add comment explaining why we skip a folder
GeckoEidechse Jul 21, 2023
17d6282
Merge branch 'main' into dev/packages
GeckoEidechse Jul 21, 2023
5e988a6
style: Remove trailing whitespace
GeckoEidechse Jul 21, 2023
4fbb3e2
Merge branch 'feat/install-packages' into dev/packages
GeckoEidechse Jul 21, 2023
09f8834
Merge branch 'main' into dev/packages
GeckoEidechse Jul 21, 2023
6b49af5
Merge branch 'main' into dev/packages
GeckoEidechse Jul 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ steamlocate = "1.2"
# Error messages
anyhow = "1.0"
# libthermite for Northstar/mod install handling
libthermite = { version = "0.6.5", features = ["proton"] }
libthermite = { version = "0.7.0-alpha", features = ["proton"] }
# zip stuff
zip = "0.6.2"
# Regex
Expand Down
49 changes: 48 additions & 1 deletion src-tauri/src/mod_management/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub fn parse_installed_mods(
}
};
// Get Thunderstore mod string if it exists
let thunderstore_mod_string = match parsed_mod_json.thunderstore_mod_string {
let mut thunderstore_mod_string = match parsed_mod_json.thunderstore_mod_string {
// Attempt legacy method for getting Thunderstore string first
Some(ts_mod_string) => Some(ts_mod_string),
// Legacy method failed
Expand All @@ -100,6 +100,17 @@ pub fn parse_installed_mods(
// Get directory path
let mod_directory = directory.to_str().unwrap().to_string();

// This is a stupid way to show a legacy installed mod as outdated by simply giving back a wrong version number
if thunderstore_mod_string.is_some() {
// Parse the string
let mut parsed_string: ParsedThunderstoreModString =
thunderstore_mod_string.clone().unwrap().parse().unwrap();
// Set version number to `0.0.0`
parsed_string.version = "0.0.0".to_string();
// And store new string back in original variable
thunderstore_mod_string = Some(parsed_string.to_string())
}
Comment on lines +103 to +112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps just move this into the match case above ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't I have to duplicate the code then? Cause the match matches for like two cases

  1. checking if it's in mods.json
  2. checking if it there's a manifest.json and a thunderstore_author.txt


let ns_mod = NorthstarMod {
name: parsed_mod_json.name,
version: parsed_mod_json.version,
Expand All @@ -115,6 +126,42 @@ pub fn parse_installed_mods(
Ok(mods)
}

/// Deletes all legacy packages that match in author and mod name
/// regardless of version
pub fn delete_legacy_package_install(
thunderstore_mod_string: &str,
game_install: &GameInstall,
) -> Result<(), String> {
let thunderstore_mod_string: ParsedThunderstoreModString =
thunderstore_mod_string.parse().unwrap();
let found_installed_legacy_mods = match parse_installed_mods(game_install) {
Ok(res) => res,
Err(err) => return Err(err.to_string()),
};

for legacy_mod in found_installed_legacy_mods {
if legacy_mod.thunderstore_mod_string.is_none() {
continue; // Not a thunderstore mod
}

let current_mod_ts_string: ParsedThunderstoreModString = legacy_mod
.clone()
.thunderstore_mod_string
.unwrap()
.parse()
.unwrap();

if thunderstore_mod_string.author_name == current_mod_ts_string.author_name
&& thunderstore_mod_string.mod_name == current_mod_ts_string.mod_name
{
// They match, delete
delete_mod_folder(&legacy_mod.directory)?;
}
}
Copy link
Contributor

@catornot catornot Jul 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a iterator would also be better here although you can probably keep it and just move all these if cases into filters.


Ok(())
}

/// Deletes all NorthstarMods related to a Thunderstore mod
pub fn delete_thunderstore_mod(
game_install: GameInstall,
Expand Down
127 changes: 120 additions & 7 deletions src-tauri/src/mod_management/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use crate::constants::{BLACKLISTED_MODS, CORE_MODS};
use async_recursion::async_recursion;
use thermite::prelude::ThermiteError;

use crate::NorthstarMod;
use anyhow::{anyhow, Result};
Expand Down Expand Up @@ -407,6 +408,93 @@ async fn get_mod_dependencies(thunderstore_mod_string: &str) -> Result<Vec<Strin
Ok(Vec::<String>::new())
}

/// Deletes all versions of Thunderstore package except the specified one
fn delete_older_versions(
thunderstore_mod_string: &str,
game_install: &GameInstall,
) -> Result<(), String> {
let thunderstore_mod_string: ParsedThunderstoreModString =
thunderstore_mod_string.parse().unwrap();
log::info!(
"Deleting other versions of {}",
thunderstore_mod_string.to_string()
);
let packages_folder = format!("{}/R2Northstar/packages", game_install.game_path);

// Get folders in packages dir
let paths = match std::fs::read_dir(&packages_folder) {
Ok(paths) => paths,
Err(_err) => return Err(format!("Failed to read directory {}", &packages_folder)),
};

let mut directories: Vec<PathBuf> = Vec::new();

// Get list of folders in `mods` directory
for path in paths {
let my_path = path.unwrap().path();

let md = std::fs::metadata(my_path.clone()).unwrap();
if md.is_dir() {
directories.push(my_path);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for loop found, use a iterator instead in this case. ( I hate for loops ).


for directory in directories {
let folder_name = directory.file_name().unwrap().to_str().unwrap();
let ts_mod_string_from_folder: ParsedThunderstoreModString = match folder_name.parse() {
Ok(res) => res,
Err(err) => {
log::warn!("{err}");
continue;
}
};
// Check which match `AUTHOR-MOD` and do NOT match `AUTHOR-MOD-VERSION`
if ts_mod_string_from_folder.author_name == thunderstore_mod_string.author_name
&& ts_mod_string_from_folder.mod_name == thunderstore_mod_string.mod_name
&& ts_mod_string_from_folder.version != thunderstore_mod_string.version
{
delete_package_folder(&directory.display().to_string())?;
}
}
Copy link
Contributor

@catornot catornot Jul 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a iterator would also be better here although you can probably keep it and just move all these if cases into filters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah so I kinda merged #426 before seeing your comments on here. Will do some refactoring to change update to your feedback though ^^


Ok(())
}

/// Checks whether some mod is correctly formatted
/// Currently checks whether
/// - Some `mod.json` exists under `mods/*/mod.json`
fn fc_sanity_check(input: &&fs::File) -> bool {
let mut archive = match zip::read::ZipArchive::new(*input) {
Ok(archive) => archive,
Err(_) => return false,
};

let mut has_mods = false;
let mut mod_json_exists = false;

// Checks for `mods/*/mod.json`
for i in 0..archive.len() {
let file = match archive.by_index(i) {
Ok(file) => file,
Err(_) => continue,
};
let file_path = file.mangled_name();
if file_path.starts_with("mods/") {
has_mods = true;
if let Some(name) = file_path.file_name() {
if name == "mod.json" {
let parent_path = file_path.parent().unwrap();
if parent_path.parent().unwrap().to_str().unwrap() == "mods" {
mod_json_exists = true;
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is literately the perfect use for a iterator.
no mutability :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh, I'm dumb, can I have an example? ^^

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smth like this
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=93be605e794e1939f0deeaeebb767575

it might not compile but it shouldn't be hard to fix stuff in it I think.

}

has_mods && mod_json_exists
}

// 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.
Expand All @@ -421,7 +509,6 @@ pub async fn fc_download_mod_and_install(
"{}/___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.is_empty() {
Expand Down Expand Up @@ -486,19 +573,45 @@ pub async fn fc_download_mod_and_install(
Err(err) => return Err(err.to_string()),
};

// Get Thunderstore mod author
let author = thunderstore_mod_string.split('-').next().unwrap();
// Get directory to install to made up of packages directory and Thunderstore mod string
let install_directory = format!(
"{}/R2Northstar/packages/{}",
game_install.game_path, thunderstore_mod_string
);

// Extract the mod to the mods directory
match thermite::core::manage::install_mod(
author,
match thermite::core::manage::install_with_sanity(
temp_file.file(),
std::path::Path::new(&mods_directory),
std::path::Path::new(&install_directory),
fc_sanity_check,
) {
Ok(_) => (),
Err(err) => {
log::warn!("libthermite couldn't install mod {thunderstore_mod_string} due to {err:?}",);
return Err(err.to_string());
return match err {
ThermiteError::SanityError => Err(
"Mod failed sanity check during install. It's probably not correctly formatted"
.to_string(),
),
_ => Err(err.to_string()),
};
}
};

// Successful package install
match legacy::delete_legacy_package_install(thunderstore_mod_string, game_install) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using a if let here

Ok(()) => (),
Err(err) => {
// Catch error but ignore
log::warn!("Failed deleting legacy versions due to: {}", err);
}
};

match delete_older_versions(thunderstore_mod_string, game_install) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and here

Ok(()) => (),
Err(err) => {
// Catch error but ignore
log::warn!("Failed deleting older versions due to: {}", err);
}
};

Expand Down