From edc2cfb392d4476b71d51658396dd5e4e0e6aaf5 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Thu, 29 Feb 2024 01:10:08 +0530 Subject: [PATCH 01/11] Refactor add::modrinth --- src/add.rs | 82 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/src/add.rs b/src/add.rs index c0e8186..b0ce816 100644 --- a/src/add.rs +++ b/src/add.rs @@ -208,7 +208,40 @@ pub async fn github( Ok(repo.name) } -use ferinth::structures::project::{DonationLink, ProjectType}; +use ferinth::structures::project::{DonationLink, Project, ProjectType}; + +fn project_exist(profile: &Profile, project: &Project) -> bool { + profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == project.title.to_lowercase() + || ModIdentifierRef::ModrinthProject(&project.id) == mod_.identifier.as_ref() + }) +} + +fn project_is_mod(project: &Project) -> bool { + project.project_type == ProjectType::Mod +} + +fn check_mod_loader_fabric_backwards_compatible( + profile: &Profile, + project: &Project, + check_mod_loader: bool, +) -> bool { + mod_loader_check(profile.get_loader(check_mod_loader), &project.loaders) + || (profile.mod_loader == ModLoader::Quilt + && mod_loader_check(Some(ModLoader::Fabric), &project.loaders)) +} + +fn project_comatible( + profile: &Profile, + project: &Project, + check_game_version: bool, + check_mod_loader: bool, +) -> bool { + game_version_check( + profile.get_version(check_game_version), + &project.game_versions, + ) && check_mod_loader_fabric_backwards_compatible(profile, project, check_mod_loader) +} /// Check if the project of `project_id` exists, is a mod, and is compatible with `profile`. /// If so, add it to the `profile`. @@ -224,39 +257,26 @@ pub async fn modrinth( ) -> Result<(String, Vec)> { let project = modrinth.get_project(project_id).await?; - // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == project.title.to_lowercase() - || ModIdentifierRef::ModrinthProject(&project.id) == mod_.identifier.as_ref() - }) { - Err(Error::AlreadyAdded) - - // Check if the project is a mod - } else if project.project_type != ProjectType::Mod { - Err(Error::NotAMod) + if project_exist(profile, &project) { + return Err(Error::AlreadyAdded); + } - // Check if the project is compatible - } else if !perform_checks // Short circuit if the checks should not be performed - || (game_version_check( - profile.get_version(check_game_version), - &project.game_versions, - ) && (mod_loader_check(profile.get_loader(check_mod_loader), &project.loaders) | ( - // Fabric backwards compatibility in Quilt - profile.mod_loader == ModLoader::Quilt && mod_loader_check(Some(ModLoader::Fabric), &project.loaders) - ))) - { - // Add it to the profile - profile.mods.push(Mod { - name: project.title.trim().to_string(), - identifier: ModIdentifier::ModrinthProject(project.id), - check_game_version, - check_mod_loader, - }); + if !project_is_mod(&project) { + return Err(Error::NotAMod); + } - Ok((project.title, project.donation_urls)) - } else { - Err(Error::Incompatible) + if !project_comatible(profile, &project, check_game_version, check_mod_loader) { + return Err(Error::Incompatible); } + + profile.mods.push(Mod { + name: project.title.trim().to_string(), + identifier: ModIdentifier::ModrinthProject(project.id), + check_game_version, + check_mod_loader, + }); + + Ok((project.title, project.donation_urls)) } /// Check if the mod of `project_id` exists, is a mod, and is compatible with `profile`. From 2918d292e52abc18aec124dba914b1ed74311298 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Thu, 29 Feb 2024 01:26:46 +0530 Subject: [PATCH 02/11] Refactor split add module into multi-parts --- src/add.rs | 332 ------------------------------------------ src/add/curseforge.rs | 56 +++++++ src/add/github.rs | 58 ++++++++ src/add/mod.rs | 153 +++++++++++++++++++ src/add/modrinth.rs | 76 ++++++++++ 5 files changed, 343 insertions(+), 332 deletions(-) delete mode 100644 src/add.rs create mode 100644 src/add/curseforge.rs create mode 100644 src/add/github.rs create mode 100644 src/add/mod.rs create mode 100644 src/add/modrinth.rs diff --git a/src/add.rs b/src/add.rs deleted file mode 100644 index b0ce816..0000000 --- a/src/add.rs +++ /dev/null @@ -1,332 +0,0 @@ -use crate::{ - config::structs::{Mod, ModIdentifier, ModIdentifierRef, ModLoader, Profile}, - upgrade::{ - check::{game_version_check, mod_loader_check}, - mod_downloadable, - }, -}; -use reqwest::StatusCode; - -#[derive(thiserror::Error, Debug)] -#[error("{}: {}", self, .0)] -pub enum Error { - #[error( - "The developer of this project has denied third party applications from downloading it" - )] - /// The user can manually download the mod and place it in the `user` folder of the output directory to mitigate this. - /// However, they will have to manually update the mod. - DistributionDenied, - #[error("The project has already been added")] - AlreadyAdded, - #[error("The project does not exist")] - DoesNotExist, - #[error("The project is not compatible")] - Incompatible, - #[error("The project is not a mod")] - NotAMod, - #[error("Invalid identifier")] - InvalidIdentifier, - GitHubError(octocrab::Error), - ModrinthError(ferinth::Error), - CurseForgeError(furse::Error), -} -type Result = std::result::Result; - -impl From for Error { - fn from(err: furse::Error) -> Self { - if let furse::Error::ReqwestError(source) = &err { - if Some(StatusCode::NOT_FOUND) == source.status() { - Self::DoesNotExist - } else { - Self::CurseForgeError(err) - } - } else { - Self::CurseForgeError(err) - } - } -} - -impl From for Error { - fn from(err: ferinth::Error) -> Self { - if let ferinth::Error::ReqwestError(source) = &err { - if Some(StatusCode::NOT_FOUND) == source.status() { - Self::DoesNotExist - } else { - Self::ModrinthError(err) - } - } else { - Self::ModrinthError(err) - } - } -} - -impl From for Error { - fn from(err: octocrab::Error) -> Self { - if let octocrab::Error::GitHub { source, .. } = &err { - if &source.message == "Not Found" { - return Self::DoesNotExist; - } - } - Self::GitHubError(err) - } -} - -pub async fn add_multiple( - modrinth: &ferinth::Ferinth, - curseforge: &furse::Furse, - github: &octocrab::Octocrab, - profile: &mut Profile, - identifiers: Vec, -) -> (Vec, Vec<(String, Error)>) { - let mut success_names = Vec::new(); - let mut failures = Vec::new(); - - for identifier in identifiers { - match add_single( - modrinth, - curseforge, - github, - profile, - &identifier, - true, - true, - true, - ) - .await - { - Ok(name) => success_names.push(name), - Err(err) => failures.push(( - identifier, - if matches!(err, Error::ModrinthError(ferinth::Error::InvalidIDorSlug)) { - Error::InvalidIdentifier - } else { - err - }, - )), - } - } - (success_names, failures) -} - -#[allow(clippy::too_many_arguments)] -pub async fn add_single( - modrinth: &ferinth::Ferinth, - curseforge: &furse::Furse, - github: &octocrab::Octocrab, - profile: &mut Profile, - identifier: &str, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result { - if let Ok(project_id) = identifier.parse() { - self::curseforge( - curseforge, - project_id, - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await - } else if identifier.matches('/').count() == 1 { - let split = identifier.split('/').collect::>(); - - self::github( - &github.repos(split[0], split[1]), - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await - } else { - self::modrinth( - modrinth, - identifier, - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await - .map(|o| o.0) - } -} - -/// Check if the repo of `repo_handler` exists, releases mods, and is compatible with `profile`. -/// If so, add it to the `profile`. -/// -/// Returns the name of the repository to display to the user -pub async fn github( - repo_handler: &octocrab::repos::RepoHandler<'_>, - profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result { - let repo = repo_handler.get().await?; - let repo_name = (repo.owner.clone().unwrap().login, repo.name.clone()); - - // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == repo.name.to_lowercase() - || ModIdentifierRef::GitHubRepository(&repo_name) == mod_.identifier.as_ref() - }) { - return Err(Error::AlreadyAdded); - } - - if perform_checks { - let releases = repo_handler.releases().list().send().await?.items; - - // Check if jar files are released - if !releases - .iter() - .flat_map(|r| &r.assets) - .any(|a| a.name.ends_with(".jar")) - { - return Err(Error::NotAMod); - } - - // Check if the repo is compatible - mod_downloadable::get_latest_compatible_asset( - &releases, - profile.get_version(check_game_version), - profile.get_loader(check_game_version), - ) - .ok_or(Error::Incompatible)?; - } - - // Add it to the profile - profile.mods.push(Mod { - name: repo.name.trim().to_string(), - identifier: ModIdentifier::GitHubRepository(repo_name), - check_game_version, - check_mod_loader, - }); - - Ok(repo.name) -} - -use ferinth::structures::project::{DonationLink, Project, ProjectType}; - -fn project_exist(profile: &Profile, project: &Project) -> bool { - profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == project.title.to_lowercase() - || ModIdentifierRef::ModrinthProject(&project.id) == mod_.identifier.as_ref() - }) -} - -fn project_is_mod(project: &Project) -> bool { - project.project_type == ProjectType::Mod -} - -fn check_mod_loader_fabric_backwards_compatible( - profile: &Profile, - project: &Project, - check_mod_loader: bool, -) -> bool { - mod_loader_check(profile.get_loader(check_mod_loader), &project.loaders) - || (profile.mod_loader == ModLoader::Quilt - && mod_loader_check(Some(ModLoader::Fabric), &project.loaders)) -} - -fn project_comatible( - profile: &Profile, - project: &Project, - check_game_version: bool, - check_mod_loader: bool, -) -> bool { - game_version_check( - profile.get_version(check_game_version), - &project.game_versions, - ) && check_mod_loader_fabric_backwards_compatible(profile, project, check_mod_loader) -} - -/// Check if the project of `project_id` exists, is a mod, and is compatible with `profile`. -/// If so, add it to the `profile`. -/// -/// Returns the project name and donation URLs to display to the user -pub async fn modrinth( - modrinth: &ferinth::Ferinth, - project_id: &str, - profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result<(String, Vec)> { - let project = modrinth.get_project(project_id).await?; - - if project_exist(profile, &project) { - return Err(Error::AlreadyAdded); - } - - if !project_is_mod(&project) { - return Err(Error::NotAMod); - } - - if !project_comatible(profile, &project, check_game_version, check_mod_loader) { - return Err(Error::Incompatible); - } - - profile.mods.push(Mod { - name: project.title.trim().to_string(), - identifier: ModIdentifier::ModrinthProject(project.id), - check_game_version, - check_mod_loader, - }); - - Ok((project.title, project.donation_urls)) -} - -/// Check if the mod of `project_id` exists, is a mod, and is compatible with `profile`. -/// If so, add it to the `profile`. -/// -/// Returns the mod name to display to the user -pub async fn curseforge( - curseforge: &furse::Furse, - project_id: i32, - profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, -) -> Result { - let project = curseforge.get_mod(project_id).await?; - - // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == project.name.to_lowercase() - || ModIdentifier::CurseForgeProject(project.id) == mod_.identifier - }) { - Err(Error::AlreadyAdded) - - // Check if it can be downloaded by third-parties - } else if Some(false) == project.allow_mod_distribution { - Err(Error::DistributionDenied) - - // Check if the project is a Minecraft mod - } else if !project.links.website_url.as_str().contains("mc-mods") { - Err(Error::NotAMod) - - // Check if the project is compatible - } else { - if perform_checks { - mod_downloadable::get_latest_compatible_file( - curseforge.get_mod_files(project.id).await?, - profile.get_version(check_game_version), - profile.get_loader(check_game_version), - ) - .ok_or(Error::Incompatible)?; - } - - // Add it to the profile - profile.mods.push(Mod { - name: project.name.trim().to_string(), - identifier: ModIdentifier::CurseForgeProject(project.id), - check_game_version, - check_mod_loader, - }); - - Ok(project.name) - } -} diff --git a/src/add/curseforge.rs b/src/add/curseforge.rs new file mode 100644 index 0000000..96c4474 --- /dev/null +++ b/src/add/curseforge.rs @@ -0,0 +1,56 @@ +use crate::{ + config::structs::{Mod, ModIdentifier, Profile}, + upgrade::mod_downloadable, +}; + +/// Check if the mod of `project_id` exists, is a mod, and is compatible with `profile`. +/// If so, add it to the `profile`. +/// +/// Returns the mod name to display to the user +pub async fn curseforge( + curseforge: &furse::Furse, + project_id: i32, + profile: &mut Profile, + perform_checks: bool, + check_game_version: bool, + check_mod_loader: bool, +) -> super::Result { + let project = curseforge.get_mod(project_id).await?; + + // Check if project has already been added + if profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == project.name.to_lowercase() + || ModIdentifier::CurseForgeProject(project.id) == mod_.identifier + }) { + Err(super::Error::AlreadyAdded) + + // Check if it can be downloaded by third-parties + } else if Some(false) == project.allow_mod_distribution { + Err(super::Error::DistributionDenied) + + // Check if the project is a Minecraft mod + } else if !project.links.website_url.as_str().contains("mc-mods") { + Err(super::Error::NotAMod) + + // Check if the project is compatible + } else { + if perform_checks { + mod_downloadable::get_latest_compatible_file( + curseforge.get_mod_files(project.id).await?, + profile.get_version(check_game_version), + profile.get_loader(check_game_version), + ) + .ok_or(super::Error::Incompatible)?; + } + + // Add it to the profile + profile.mods.push(Mod { + name: project.name.trim().to_string(), + identifier: ModIdentifier::CurseForgeProject(project.id), + check_game_version, + check_mod_loader, + }); + + Ok(project.name) + } +} diff --git a/src/add/github.rs b/src/add/github.rs new file mode 100644 index 0000000..4ded201 --- /dev/null +++ b/src/add/github.rs @@ -0,0 +1,58 @@ +use crate::{ + config::structs::{Mod, ModIdentifier, ModIdentifierRef, Profile}, + upgrade::mod_downloadable, +}; + +/// Check if the repo of `repo_handler` exists, releases mods, and is compatible with `profile`. +/// If so, add it to the `profile`. +/// +/// Returns the name of the repository to display to the user +pub async fn github( + repo_handler: &octocrab::repos::RepoHandler<'_>, + profile: &mut Profile, + perform_checks: bool, + check_game_version: bool, + check_mod_loader: bool, +) -> super::Result { + let repo = repo_handler.get().await?; + let repo_name = (repo.owner.clone().unwrap().login, repo.name.clone()); + + // Check if project has already been added + if profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == repo.name.to_lowercase() + || ModIdentifierRef::GitHubRepository(&repo_name) == mod_.identifier.as_ref() + }) { + return Err(super::Error::AlreadyAdded); + } + + if perform_checks { + let releases = repo_handler.releases().list().send().await?.items; + + // Check if jar files are released + if !releases + .iter() + .flat_map(|r| &r.assets) + .any(|a| a.name.ends_with(".jar")) + { + return Err(super::Error::NotAMod); + } + + // Check if the repo is compatible + mod_downloadable::get_latest_compatible_asset( + &releases, + profile.get_version(check_game_version), + profile.get_loader(check_game_version), + ) + .ok_or(super::Error::Incompatible)?; + } + + // Add it to the profile + profile.mods.push(Mod { + name: repo.name.trim().to_string(), + identifier: ModIdentifier::GitHubRepository(repo_name), + check_game_version, + check_mod_loader, + }); + + Ok(repo.name) +} diff --git a/src/add/mod.rs b/src/add/mod.rs new file mode 100644 index 0000000..3298a9f --- /dev/null +++ b/src/add/mod.rs @@ -0,0 +1,153 @@ +use crate::config::structs::Profile; +use reqwest::StatusCode; + +pub mod curseforge; +pub mod github; +pub mod modrinth; + +#[derive(thiserror::Error, Debug)] +#[error("{}: {}", self, .0)] +pub enum Error { + #[error( + "The developer of this project has denied third party applications from downloading it" + )] + /// The user can manually download the mod and place it in the `user` folder of the output directory to mitigate this. + /// However, they will have to manually update the mod. + DistributionDenied, + #[error("The project has already been added")] + AlreadyAdded, + #[error("The project does not exist")] + DoesNotExist, + #[error("The project is not compatible")] + Incompatible, + #[error("The project is not a mod")] + NotAMod, + #[error("Invalid identifier")] + InvalidIdentifier, + GitHubError(octocrab::Error), + ModrinthError(ferinth::Error), + CurseForgeError(furse::Error), +} + +pub type Result = std::result::Result; + +impl From for Error { + fn from(err: furse::Error) -> Self { + if let furse::Error::ReqwestError(source) = &err { + if Some(StatusCode::NOT_FOUND) == source.status() { + Self::DoesNotExist + } else { + Self::CurseForgeError(err) + } + } else { + Self::CurseForgeError(err) + } + } +} + +impl From for Error { + fn from(err: ferinth::Error) -> Self { + if let ferinth::Error::ReqwestError(source) = &err { + if Some(StatusCode::NOT_FOUND) == source.status() { + Self::DoesNotExist + } else { + Self::ModrinthError(err) + } + } else { + Self::ModrinthError(err) + } + } +} + +impl From for Error { + fn from(err: octocrab::Error) -> Self { + if let octocrab::Error::GitHub { source, .. } = &err { + if &source.message == "Not Found" { + return Self::DoesNotExist; + } + } + Self::GitHubError(err) + } +} + +pub async fn add_multiple( + modrinth: &ferinth::Ferinth, + curseforge: &furse::Furse, + github: &octocrab::Octocrab, + profile: &mut Profile, + identifiers: Vec, +) -> (Vec, Vec<(String, Error)>) { + let mut success_names = Vec::new(); + let mut failures = Vec::new(); + + for identifier in identifiers { + match add_single( + modrinth, + curseforge, + github, + profile, + &identifier, + true, + true, + true, + ) + .await + { + Ok(name) => success_names.push(name), + Err(err) => failures.push(( + identifier, + if matches!(err, Error::ModrinthError(ferinth::Error::InvalidIDorSlug)) { + Error::InvalidIdentifier + } else { + err + }, + )), + } + } + (success_names, failures) +} + +#[allow(clippy::too_many_arguments)] +pub async fn add_single( + modrinth: &ferinth::Ferinth, + curseforge: &furse::Furse, + github: &octocrab::Octocrab, + profile: &mut Profile, + identifier: &str, + perform_checks: bool, + check_game_version: bool, + check_mod_loader: bool, +) -> Result { + if let Ok(project_id) = identifier.parse() { + curseforge::curseforge( + curseforge, + project_id, + profile, + perform_checks, + check_game_version, + check_mod_loader, + ) + .await + } else if identifier.matches('/').count() == 1 { + let split = identifier.split('/').collect::>(); + github::github( + &github.repos(split[0], split[1]), + profile, + perform_checks, + check_game_version, + check_mod_loader, + ) + .await + } else { + modrinth::modrinth( + modrinth, + identifier, + profile, + perform_checks, + check_game_version, + check_mod_loader, + ) + .await + .map(|o| o.0) + } +} diff --git a/src/add/modrinth.rs b/src/add/modrinth.rs new file mode 100644 index 0000000..9c1a854 --- /dev/null +++ b/src/add/modrinth.rs @@ -0,0 +1,76 @@ +use ferinth::structures::project::{DonationLink, Project, ProjectType}; + +use crate::{ + config::structs::{Mod, ModIdentifier, ModIdentifierRef, ModLoader, Profile}, + upgrade::check::{game_version_check, mod_loader_check}, +}; + +fn project_exist(profile: &Profile, project: &Project) -> bool { + profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == project.title.to_lowercase() + || ModIdentifierRef::ModrinthProject(&project.id) == mod_.identifier.as_ref() + }) +} + +fn project_is_mod(project: &Project) -> bool { + project.project_type == ProjectType::Mod +} + +fn check_mod_loader_fabric_backwards_compatible( + profile: &Profile, + project: &Project, + check_mod_loader: bool, +) -> bool { + mod_loader_check(profile.get_loader(check_mod_loader), &project.loaders) + || (profile.mod_loader == ModLoader::Quilt + && mod_loader_check(Some(ModLoader::Fabric), &project.loaders)) +} + +fn project_comatible( + profile: &Profile, + project: &Project, + check_game_version: bool, + check_mod_loader: bool, +) -> bool { + game_version_check( + profile.get_version(check_game_version), + &project.game_versions, + ) && check_mod_loader_fabric_backwards_compatible(profile, project, check_mod_loader) +} + +/// Check if the project of `project_id` exists, is a mod, and is compatible with `profile`. +/// If so, add it to the `profile`. +/// +/// Returns the project name and donation URLs to display to the user +pub async fn modrinth( + modrinth: &ferinth::Ferinth, + project_id: &str, + profile: &mut Profile, + perform_checks: bool, + check_game_version: bool, + check_mod_loader: bool, +) -> super::Result<(String, Vec)> { + let project = modrinth.get_project(project_id).await?; + + if project_exist(profile, &project) { + return Err(super::Error::AlreadyAdded); + } + + if !project_is_mod(&project) { + return Err(super::Error::NotAMod); + } + + if perform_checks && !project_comatible(profile, &project, check_game_version, check_mod_loader) + { + return Err(super::Error::Incompatible); + } + + profile.mods.push(Mod { + name: project.title.trim().to_string(), + identifier: ModIdentifier::ModrinthProject(project.id), + check_game_version, + check_mod_loader, + }); + + Ok((project.title, project.donation_urls)) +} From 50f50579e2877148b4713c1d9a92ee35f7d23ad4 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Thu, 29 Feb 2024 01:59:19 +0530 Subject: [PATCH 03/11] Refactor add::curseforge --- src/add/curseforge.rs | 80 ++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/src/add/curseforge.rs b/src/add/curseforge.rs index 96c4474..5b4a61a 100644 --- a/src/add/curseforge.rs +++ b/src/add/curseforge.rs @@ -3,6 +3,35 @@ use crate::{ upgrade::mod_downloadable, }; +fn project_exist(profile: &Profile, project: &furse::structures::mod_structs::Mod) -> bool { + profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == project.name.to_lowercase() + || ModIdentifier::CurseForgeProject(project.id) == mod_.identifier + }) +} + +fn distrubution_denied(project: &furse::structures::mod_structs::Mod) -> bool { + project.allow_mod_distribution.map_or(false, |b| !b) +} + +fn is_minecraft_mod(project: &furse::structures::mod_structs::Mod) -> bool { + project.links.website_url.as_str().contains("mc-mods") +} + +async fn is_project_compatible( + curseforge: &furse::Furse, + project: &furse::structures::mod_structs::Mod, + profile: &Profile, + check_game_version: bool, +) -> super::Result { + Ok(mod_downloadable::get_latest_compatible_file( + curseforge.get_mod_files(project.id).await?, + profile.get_version(check_game_version), + profile.get_loader(check_game_version), + ) + .is_some()) +} + /// Check if the mod of `project_id` exists, is a mod, and is compatible with `profile`. /// If so, add it to the `profile`. /// @@ -18,39 +47,34 @@ pub async fn curseforge( let project = curseforge.get_mod(project_id).await?; // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == project.name.to_lowercase() - || ModIdentifier::CurseForgeProject(project.id) == mod_.identifier - }) { - Err(super::Error::AlreadyAdded) + if project_exist(profile, &project) { + return Err(super::Error::AlreadyAdded); + } // Check if it can be downloaded by third-parties - } else if Some(false) == project.allow_mod_distribution { - Err(super::Error::DistributionDenied) + if distrubution_denied(&project) { + return Err(super::Error::DistributionDenied); + } // Check if the project is a Minecraft mod - } else if !project.links.website_url.as_str().contains("mc-mods") { - Err(super::Error::NotAMod) + if !is_minecraft_mod(&project) { + return Err(super::Error::NotAMod); + } // Check if the project is compatible - } else { - if perform_checks { - mod_downloadable::get_latest_compatible_file( - curseforge.get_mod_files(project.id).await?, - profile.get_version(check_game_version), - profile.get_loader(check_game_version), - ) - .ok_or(super::Error::Incompatible)?; - } - - // Add it to the profile - profile.mods.push(Mod { - name: project.name.trim().to_string(), - identifier: ModIdentifier::CurseForgeProject(project.id), - check_game_version, - check_mod_loader, - }); - - Ok(project.name) + if perform_checks + && !is_project_compatible(curseforge, &project, profile, check_game_version).await? + { + return Err(super::Error::Incompatible); } + + // Add it to the profile + profile.mods.push(Mod { + name: project.name.trim().to_string(), + identifier: ModIdentifier::CurseForgeProject(project.id), + check_game_version, + check_mod_loader, + }); + + Ok(project.name) } From fb38cfc390c6043ac4bf4a16e8f69061f9b9bfd8 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Thu, 29 Feb 2024 02:19:22 +0530 Subject: [PATCH 04/11] Refactor add::github --- src/add/github.rs | 57 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/add/github.rs b/src/add/github.rs index 4ded201..85de1e6 100644 --- a/src/add/github.rs +++ b/src/add/github.rs @@ -1,8 +1,37 @@ +use octocrab::models::{repos::Release, Repository}; + use crate::{ config::structs::{Mod, ModIdentifier, ModIdentifierRef, Profile}, upgrade::mod_downloadable, }; +fn project_exist(profile: &Profile, repo: &Repository, repo_name: &(String, String)) -> bool { + profile.mods.iter().any(|mod_| { + mod_.name.to_lowercase() == repo.name.to_lowercase() + || ModIdentifierRef::GitHubRepository(&repo_name) == mod_.identifier.as_ref() + }) +} + +fn contains_jar_asset(releases: &[Release]) -> bool { + releases + .iter() + .flat_map(|r| &r.assets) + .any(|a| a.name.ends_with(".jar")) +} + +async fn is_project_compatible( + profile: &Profile, + releases: &[Release], + check_game_version: bool, +) -> super::Result { + Ok(mod_downloadable::get_latest_compatible_asset( + &releases, + profile.get_version(check_game_version), + profile.get_loader(check_game_version), + ) + .is_some()) +} + /// Check if the repo of `repo_handler` exists, releases mods, and is compatible with `profile`. /// If so, add it to the `profile`. /// @@ -15,13 +44,16 @@ pub async fn github( check_mod_loader: bool, ) -> super::Result { let repo = repo_handler.get().await?; - let repo_name = (repo.owner.clone().unwrap().login, repo.name.clone()); + let repo_name = ( + repo.owner + .clone() + .expect("Owner name not found in git repo") + .login, + repo.name.clone(), + ); // Check if project has already been added - if profile.mods.iter().any(|mod_| { - mod_.name.to_lowercase() == repo.name.to_lowercase() - || ModIdentifierRef::GitHubRepository(&repo_name) == mod_.identifier.as_ref() - }) { + if project_exist(profile, &repo, &repo_name) { return Err(super::Error::AlreadyAdded); } @@ -29,21 +61,14 @@ pub async fn github( let releases = repo_handler.releases().list().send().await?.items; // Check if jar files are released - if !releases - .iter() - .flat_map(|r| &r.assets) - .any(|a| a.name.ends_with(".jar")) - { + if !contains_jar_asset(&releases) { return Err(super::Error::NotAMod); } // Check if the repo is compatible - mod_downloadable::get_latest_compatible_asset( - &releases, - profile.get_version(check_game_version), - profile.get_loader(check_game_version), - ) - .ok_or(super::Error::Incompatible)?; + if !is_project_compatible(profile, &releases, check_game_version).await? { + return Err(super::Error::Incompatible); + } } // Add it to the profile From 49a9651f35733b7952d072b2836a6adc21014bcb Mon Sep 17 00:00:00 2001 From: atamakahere Date: Thu, 29 Feb 2024 02:20:07 +0530 Subject: [PATCH 05/11] Refactor clippy fixes --- src/add/github.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/add/github.rs b/src/add/github.rs index 85de1e6..467106c 100644 --- a/src/add/github.rs +++ b/src/add/github.rs @@ -8,7 +8,7 @@ use crate::{ fn project_exist(profile: &Profile, repo: &Repository, repo_name: &(String, String)) -> bool { profile.mods.iter().any(|mod_| { mod_.name.to_lowercase() == repo.name.to_lowercase() - || ModIdentifierRef::GitHubRepository(&repo_name) == mod_.identifier.as_ref() + || ModIdentifierRef::GitHubRepository(repo_name) == mod_.identifier.as_ref() }) } @@ -25,7 +25,7 @@ async fn is_project_compatible( check_game_version: bool, ) -> super::Result { Ok(mod_downloadable::get_latest_compatible_asset( - &releases, + releases, profile.get_version(check_game_version), profile.get_loader(check_game_version), ) From 8c08920174f2a79eeeb91527c10fac5aa6945504 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Fri, 1 Mar 2024 20:34:59 +0530 Subject: [PATCH 06/11] Refactor! condensed multiple bools to Checks struct --- src/add/curseforge.rs | 21 +++--- src/add/github.rs | 18 ++--- src/add/mod.rs | 156 ++++++++++++++++++++++++++++++++++-------- src/add/modrinth.rs | 31 ++++----- src/config/structs.rs | 13 ++++ 5 files changed, 171 insertions(+), 68 deletions(-) diff --git a/src/add/curseforge.rs b/src/add/curseforge.rs index 5b4a61a..be4498b 100644 --- a/src/add/curseforge.rs +++ b/src/add/curseforge.rs @@ -3,6 +3,8 @@ use crate::{ upgrade::mod_downloadable, }; +use super::Checks; + fn project_exist(profile: &Profile, project: &furse::structures::mod_structs::Mod) -> bool { profile.mods.iter().any(|mod_| { mod_.name.to_lowercase() == project.name.to_lowercase() @@ -40,9 +42,7 @@ pub async fn curseforge( curseforge: &furse::Furse, project_id: i32, profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, + checks: &Checks, ) -> super::Result { let project = curseforge.get_mod(project_id).await?; @@ -62,19 +62,18 @@ pub async fn curseforge( } // Check if the project is compatible - if perform_checks - && !is_project_compatible(curseforge, &project, profile, check_game_version).await? + if checks.perform_checks() + && !is_project_compatible(curseforge, &project, profile, checks.game_version()).await? { return Err(super::Error::Incompatible); } // Add it to the profile - profile.mods.push(Mod { - name: project.name.trim().to_string(), - identifier: ModIdentifier::CurseForgeProject(project.id), - check_game_version, - check_mod_loader, - }); + profile.mods.push(Mod::new( + project.name.trim(), + ModIdentifier::CurseForgeProject(project.id), + checks, + )); Ok(project.name) } diff --git a/src/add/github.rs b/src/add/github.rs index 467106c..3bf860e 100644 --- a/src/add/github.rs +++ b/src/add/github.rs @@ -5,6 +5,8 @@ use crate::{ upgrade::mod_downloadable, }; +use super::Checks; + fn project_exist(profile: &Profile, repo: &Repository, repo_name: &(String, String)) -> bool { profile.mods.iter().any(|mod_| { mod_.name.to_lowercase() == repo.name.to_lowercase() @@ -40,8 +42,7 @@ pub async fn github( repo_handler: &octocrab::repos::RepoHandler<'_>, profile: &mut Profile, perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, + checks: &Checks, ) -> super::Result { let repo = repo_handler.get().await?; let repo_name = ( @@ -66,18 +67,17 @@ pub async fn github( } // Check if the repo is compatible - if !is_project_compatible(profile, &releases, check_game_version).await? { + if !is_project_compatible(profile, &releases, checks.game_version()).await? { return Err(super::Error::Incompatible); } } // Add it to the profile - profile.mods.push(Mod { - name: repo.name.trim().to_string(), - identifier: ModIdentifier::GitHubRepository(repo_name), - check_game_version, - check_mod_loader, - }); + profile.mods.push(Mod::new( + repo.name.trim(), + ModIdentifier::GitHubRepository(repo_name), + checks, + )); Ok(repo.name) } diff --git a/src/add/mod.rs b/src/add/mod.rs index 3298a9f..c9cf1ff 100644 --- a/src/add/mod.rs +++ b/src/add/mod.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; + use crate::config::structs::Profile; use reqwest::StatusCode; @@ -31,6 +33,88 @@ pub enum Error { pub type Result = std::result::Result; +/// Single sturct to condense check flags for game version, mod loader and to-check +/// Saves space, reduce complexity in fn args and is fast +/// +/// Bit mappings (LTR: [7,6,5,4,3,2,1,0]): +/// 0: flag for "perform checks" +/// 1: flag for "game version" +/// 2: flag for "mod loader" +#[derive(Default)] +pub struct Checks(Cell); + +impl Checks { + /// Generates new [Checks] will all values set to [true] + pub fn new_all_set() -> Self { + Self(Cell::new(0b00000111)) + } + + /// Generates [Checks] from given predicate + pub fn from(checks: bool, game_version: bool, mod_loader: bool) -> Self { + let ret = Self::default(); + if checks { + ret.set_perform_check(); + } + if game_version { + ret.set_game_version(); + } + if mod_loader { + ret.set_mod_loader(); + } + ret + } + + /// Set "perform_checks" bit to true + pub fn set_perform_check(&self) { + self.0.set(self.0.get() | 1 << 0); + } + + /// Set "game_version" bit to true + pub fn set_game_version(&self) { + self.0.set(self.0.get() | 1 << 1); + } + + /// Set "mod_loader" bit to true + pub fn set_mod_loader(&self) { + self.0.set(self.0.get() | 1 << 2); + } + + /// Set "perform_checks" bit to false + pub fn unset_perform_check(&self) { + self.0.set(self.0.get() & 1 << 0); + } + + /// Set "game_version" bit to false + pub fn unset_game_version(&self) { + self.0.set(self.0.get() & 1 << 1); + } + + /// Set "mod_loader" bit to true + pub fn unset_mod_loader(&self) { + self.0.set(self.0.get() & 1 << 2); + } + + /// Return "perform_checks" bit status + pub fn perform_checks(&self) -> bool { + self.0.get() & 1 != 0 + } + + /// Return "game_version" bit status + pub fn game_version(&self) -> bool { + self.0.get() & (1 << 1) != 0 + } + + /// Return "mod_loader" bit status + pub fn mod_loader(&self) -> bool { + self.0.get() & (1 << 2) != 0 + } + + /// Reset all bits to 0 (all flags to false) + pub fn reset(&self) { + self.0.set(0); + } +} + impl From for Error { fn from(err: furse::Error) -> Self { if let furse::Error::ReqwestError(source) = &err { @@ -87,9 +171,7 @@ pub async fn add_multiple( github, profile, &identifier, - true, - true, - true, + &Checks::new_all_set(), ) .await { @@ -107,47 +189,63 @@ pub async fn add_multiple( (success_names, failures) } -#[allow(clippy::too_many_arguments)] pub async fn add_single( modrinth: &ferinth::Ferinth, curseforge: &furse::Furse, github: &octocrab::Octocrab, profile: &mut Profile, identifier: &str, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, + checks: &Checks, ) -> Result { if let Ok(project_id) = identifier.parse() { - curseforge::curseforge( - curseforge, - project_id, - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await + curseforge::curseforge(curseforge, project_id, profile, checks).await } else if identifier.matches('/').count() == 1 { let split = identifier.split('/').collect::>(); github::github( &github.repos(split[0], split[1]), profile, - perform_checks, - check_game_version, - check_mod_loader, + checks.perform_checks(), + checks, ) .await } else { - modrinth::modrinth( - modrinth, - identifier, - profile, - perform_checks, - check_game_version, - check_mod_loader, - ) - .await - .map(|o| o.0) + modrinth::modrinth(modrinth, identifier, profile, checks) + .await + .map(|o| o.0) + } +} + +#[cfg(test)] +mod test { + use super::Checks; + + #[test] + fn check_bit_set_unset() { + let check = Checks::default(); + + // seting bits + check.set_perform_check(); + check.set_mod_loader(); + check.set_game_version(); + + assert!(check.perform_checks() && check.game_version() && check.mod_loader()); + + // Unset after set + check.unset_perform_check(); + check.unset_mod_loader(); + check.unset_game_version(); + + assert!(!(check.perform_checks() && check.game_version() && check.mod_loader())); + + // Unset after Unset + check.unset_mod_loader(); + + assert!(!check.mod_loader()); + + // set after set + check.set_game_version(); + check.set_game_version(); + + assert!(check.game_version()); } } diff --git a/src/add/modrinth.rs b/src/add/modrinth.rs index 9c1a854..d5a159f 100644 --- a/src/add/modrinth.rs +++ b/src/add/modrinth.rs @@ -5,6 +5,8 @@ use crate::{ upgrade::check::{game_version_check, mod_loader_check}, }; +use super::Checks; + fn project_exist(profile: &Profile, project: &Project) -> bool { profile.mods.iter().any(|mod_| { mod_.name.to_lowercase() == project.title.to_lowercase() @@ -26,16 +28,11 @@ fn check_mod_loader_fabric_backwards_compatible( && mod_loader_check(Some(ModLoader::Fabric), &project.loaders)) } -fn project_comatible( - profile: &Profile, - project: &Project, - check_game_version: bool, - check_mod_loader: bool, -) -> bool { +fn project_comatible(profile: &Profile, project: &Project, checks: &Checks) -> bool { game_version_check( - profile.get_version(check_game_version), + profile.get_version(checks.game_version()), &project.game_versions, - ) && check_mod_loader_fabric_backwards_compatible(profile, project, check_mod_loader) + ) && check_mod_loader_fabric_backwards_compatible(profile, project, checks.mod_loader()) } /// Check if the project of `project_id` exists, is a mod, and is compatible with `profile`. @@ -46,9 +43,7 @@ pub async fn modrinth( modrinth: &ferinth::Ferinth, project_id: &str, profile: &mut Profile, - perform_checks: bool, - check_game_version: bool, - check_mod_loader: bool, + checks: &Checks, ) -> super::Result<(String, Vec)> { let project = modrinth.get_project(project_id).await?; @@ -60,17 +55,15 @@ pub async fn modrinth( return Err(super::Error::NotAMod); } - if perform_checks && !project_comatible(profile, &project, check_game_version, check_mod_loader) - { + if checks.perform_checks() && !project_comatible(profile, &project, checks) { return Err(super::Error::Incompatible); } - profile.mods.push(Mod { - name: project.title.trim().to_string(), - identifier: ModIdentifier::ModrinthProject(project.id), - check_game_version, - check_mod_loader, - }); + profile.mods.push(Mod::new( + project.title.trim(), + ModIdentifier::ModrinthProject(project.id), + checks, + )); Ok((project.title, project.donation_urls)) } diff --git a/src/config/structs.rs b/src/config/structs.rs index 9155058..43688e2 100644 --- a/src/config/structs.rs +++ b/src/config/structs.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use std::{path::PathBuf, str::FromStr}; +use crate::add::Checks; + #[derive(Deserialize, Serialize, Debug, Default, Clone)] pub struct Config { /// The index of the active profile @@ -90,6 +92,17 @@ pub struct Mod { pub check_mod_loader: bool, } +impl Mod { + pub fn new(name: &str, identifier: ModIdentifier, checks: &Checks) -> Self { + Self { + name: name.into(), + identifier, + check_game_version: checks.game_version(), + check_mod_loader: checks.mod_loader(), + } + } +} + fn is_true(b: &bool) -> bool { *b } From 097c5830abfd840bb71acb7d60e2b1e0e08c8544 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Sat, 2 Mar 2024 00:04:10 +0530 Subject: [PATCH 07/11] Fix for last commit --- src/add/github.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/add/github.rs b/src/add/github.rs index 3bf860e..81fa719 100644 --- a/src/add/github.rs +++ b/src/add/github.rs @@ -41,7 +41,6 @@ async fn is_project_compatible( pub async fn github( repo_handler: &octocrab::repos::RepoHandler<'_>, profile: &mut Profile, - perform_checks: bool, checks: &Checks, ) -> super::Result { let repo = repo_handler.get().await?; @@ -58,7 +57,7 @@ pub async fn github( return Err(super::Error::AlreadyAdded); } - if perform_checks { + if checks.perform_checks() { let releases = repo_handler.releases().list().send().await?.items; // Check if jar files are released From f58eac5f27e25153ccbc0370765c6136f4c18c41 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Sat, 2 Mar 2024 00:04:41 +0530 Subject: [PATCH 08/11] Refactor(WIP)! Condense different mod providers in to one API --- src/add/mod.rs | 114 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 40 deletions(-) diff --git a/src/add/mod.rs b/src/add/mod.rs index c9cf1ff..1f4234a 100644 --- a/src/add/mod.rs +++ b/src/add/mod.rs @@ -115,6 +115,56 @@ impl Checks { } } +pub struct ModProvider<'p> { + modrinth: &'p ferinth::Ferinth, + curseforge: &'p furse::Furse, + github: &'p octocrab::Octocrab, + checks: &'p Checks, + profile: &'p mut Profile, +} + +impl<'p> ModProvider<'p> { + pub fn new( + modrinth: &'p ferinth::Ferinth, + curseforge: &'p furse::Furse, + github: &'p octocrab::Octocrab, + checks: &'p Checks, + profile: &'p mut Profile, + ) -> Self { + Self { + modrinth, + curseforge, + github, + checks, + profile, + } + } + + pub async fn add(&mut self, identifier: &str) -> Result { + if let Ok(project_id) = identifier.parse() { + self.curseforge(project_id).await + } else if identifier.matches('/').count() == 1 { + self.github(identifier).await + } else { + self.modrinth(identifier).await + } + } + + pub async fn curseforge(&mut self, project_id: i32) -> Result { + curseforge::curseforge(self.curseforge, project_id, self.profile, self.checks).await + } + pub async fn github(&mut self, identifier: &str) -> Result { + let split = identifier.split('/').collect::>(); + let repo_handler = self.github.repos(split[0], split[1]); + github::github(&repo_handler, self.profile, self.checks).await + } + pub async fn modrinth(&mut self, identifier: &str) -> Result { + modrinth::modrinth(self.modrinth, identifier, self.profile, self.checks) + .await + .map(|o| o.0) + } +} + impl From for Error { fn from(err: furse::Error) -> Self { if let furse::Error::ReqwestError(source) = &err { @@ -154,37 +204,28 @@ impl From for Error { } } -pub async fn add_multiple( - modrinth: &ferinth::Ferinth, - curseforge: &furse::Furse, - github: &octocrab::Octocrab, - profile: &mut Profile, +pub async fn add_multiple<'p>( + mod_provider: &mut ModProvider<'p>, identifiers: Vec, ) -> (Vec, Vec<(String, Error)>) { let mut success_names = Vec::new(); let mut failures = Vec::new(); for identifier in identifiers { - match add_single( - modrinth, - curseforge, - github, - profile, - &identifier, - &Checks::new_all_set(), - ) - .await - { - Ok(name) => success_names.push(name), - Err(err) => failures.push(( - identifier, - if matches!(err, Error::ModrinthError(ferinth::Error::InvalidIDorSlug)) { - Error::InvalidIdentifier - } else { - err - }, - )), - } + mod_provider + .add(&identifier) + .await + .map(|name| success_names.push(name)) + .map_err(|err| { + let ret_err = + if matches!(err, Error::ModrinthError(ferinth::Error::InvalidIDorSlug)) { + Error::InvalidIdentifier + } else { + err + }; + failures.push((identifier, ret_err)) + }) + .ok(); } (success_names, failures) } @@ -197,22 +238,9 @@ pub async fn add_single( identifier: &str, checks: &Checks, ) -> Result { - if let Ok(project_id) = identifier.parse() { - curseforge::curseforge(curseforge, project_id, profile, checks).await - } else if identifier.matches('/').count() == 1 { - let split = identifier.split('/').collect::>(); - github::github( - &github.repos(split[0], split[1]), - profile, - checks.perform_checks(), - checks, - ) + ModProvider::new(modrinth, curseforge, github, checks, profile) + .add(identifier) .await - } else { - modrinth::modrinth(modrinth, identifier, profile, checks) - .await - .map(|o| o.0) - } } #[cfg(test)] @@ -247,5 +275,11 @@ mod test { check.set_game_version(); assert!(check.game_version()); + + let check = Checks::from(true, false, true); + + assert!(check.perform_checks()); + assert!(!check.game_version()); + assert!(check.mod_loader()); } } From 0f702d7f0cc51ebd7b27cd183da1d587282184c7 Mon Sep 17 00:00:00 2001 From: atamakahere Date: Sat, 2 Mar 2024 12:17:02 +0530 Subject: [PATCH 09/11] Refactor! Use bitflags for checks --- Cargo.toml | 1 + src/add/curseforge.rs | 10 +++- src/add/github.rs | 5 +- src/add/mod.rs | 128 ++---------------------------------------- src/add/modrinth.rs | 10 +++- src/config/structs.rs | 4 +- 6 files changed, 27 insertions(+), 131 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22509fc..87fdfaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,3 +49,4 @@ thiserror = "1.0" ferinth = "2.10" furse = "1.5" home = "0.5" +bitflags = "2.4.2" diff --git a/src/add/curseforge.rs b/src/add/curseforge.rs index be4498b..cc09c97 100644 --- a/src/add/curseforge.rs +++ b/src/add/curseforge.rs @@ -62,8 +62,14 @@ pub async fn curseforge( } // Check if the project is compatible - if checks.perform_checks() - && !is_project_compatible(curseforge, &project, profile, checks.game_version()).await? + if checks.contains(Checks::ENABLED) + && !is_project_compatible( + curseforge, + &project, + profile, + checks.contains(Checks::GAME_VERSION), + ) + .await? { return Err(super::Error::Incompatible); } diff --git a/src/add/github.rs b/src/add/github.rs index 81fa719..b3d87b0 100644 --- a/src/add/github.rs +++ b/src/add/github.rs @@ -57,7 +57,7 @@ pub async fn github( return Err(super::Error::AlreadyAdded); } - if checks.perform_checks() { + if checks.contains(Checks::ENABLED) { let releases = repo_handler.releases().list().send().await?.items; // Check if jar files are released @@ -66,7 +66,8 @@ pub async fn github( } // Check if the repo is compatible - if !is_project_compatible(profile, &releases, checks.game_version()).await? { + if !is_project_compatible(profile, &releases, checks.contains(Checks::GAME_VERSION)).await? + { return Err(super::Error::Incompatible); } } diff --git a/src/add/mod.rs b/src/add/mod.rs index 1f4234a..861f6dc 100644 --- a/src/add/mod.rs +++ b/src/add/mod.rs @@ -1,6 +1,5 @@ -use std::cell::Cell; - use crate::config::structs::Profile; +use bitflags::bitflags; use reqwest::StatusCode; pub mod curseforge; @@ -33,85 +32,11 @@ pub enum Error { pub type Result = std::result::Result; -/// Single sturct to condense check flags for game version, mod loader and to-check -/// Saves space, reduce complexity in fn args and is fast -/// -/// Bit mappings (LTR: [7,6,5,4,3,2,1,0]): -/// 0: flag for "perform checks" -/// 1: flag for "game version" -/// 2: flag for "mod loader" -#[derive(Default)] -pub struct Checks(Cell); - -impl Checks { - /// Generates new [Checks] will all values set to [true] - pub fn new_all_set() -> Self { - Self(Cell::new(0b00000111)) - } - - /// Generates [Checks] from given predicate - pub fn from(checks: bool, game_version: bool, mod_loader: bool) -> Self { - let ret = Self::default(); - if checks { - ret.set_perform_check(); - } - if game_version { - ret.set_game_version(); - } - if mod_loader { - ret.set_mod_loader(); - } - ret - } - - /// Set "perform_checks" bit to true - pub fn set_perform_check(&self) { - self.0.set(self.0.get() | 1 << 0); - } - - /// Set "game_version" bit to true - pub fn set_game_version(&self) { - self.0.set(self.0.get() | 1 << 1); - } - - /// Set "mod_loader" bit to true - pub fn set_mod_loader(&self) { - self.0.set(self.0.get() | 1 << 2); - } - - /// Set "perform_checks" bit to false - pub fn unset_perform_check(&self) { - self.0.set(self.0.get() & 1 << 0); - } - - /// Set "game_version" bit to false - pub fn unset_game_version(&self) { - self.0.set(self.0.get() & 1 << 1); - } - - /// Set "mod_loader" bit to true - pub fn unset_mod_loader(&self) { - self.0.set(self.0.get() & 1 << 2); - } - - /// Return "perform_checks" bit status - pub fn perform_checks(&self) -> bool { - self.0.get() & 1 != 0 - } - - /// Return "game_version" bit status - pub fn game_version(&self) -> bool { - self.0.get() & (1 << 1) != 0 - } - - /// Return "mod_loader" bit status - pub fn mod_loader(&self) -> bool { - self.0.get() & (1 << 2) != 0 - } - - /// Reset all bits to 0 (all flags to false) - pub fn reset(&self) { - self.0.set(0); +bitflags! { + pub struct Checks: u8 { + const ENABLED = 0b00000001; + const GAME_VERSION = 0b00000010; + const MOD_LOADER = 0b00000100; } } @@ -242,44 +167,3 @@ pub async fn add_single( .add(identifier) .await } - -#[cfg(test)] -mod test { - use super::Checks; - - #[test] - fn check_bit_set_unset() { - let check = Checks::default(); - - // seting bits - check.set_perform_check(); - check.set_mod_loader(); - check.set_game_version(); - - assert!(check.perform_checks() && check.game_version() && check.mod_loader()); - - // Unset after set - check.unset_perform_check(); - check.unset_mod_loader(); - check.unset_game_version(); - - assert!(!(check.perform_checks() && check.game_version() && check.mod_loader())); - - // Unset after Unset - check.unset_mod_loader(); - - assert!(!check.mod_loader()); - - // set after set - check.set_game_version(); - check.set_game_version(); - - assert!(check.game_version()); - - let check = Checks::from(true, false, true); - - assert!(check.perform_checks()); - assert!(!check.game_version()); - assert!(check.mod_loader()); - } -} diff --git a/src/add/modrinth.rs b/src/add/modrinth.rs index d5a159f..cb41f81 100644 --- a/src/add/modrinth.rs +++ b/src/add/modrinth.rs @@ -30,9 +30,13 @@ fn check_mod_loader_fabric_backwards_compatible( fn project_comatible(profile: &Profile, project: &Project, checks: &Checks) -> bool { game_version_check( - profile.get_version(checks.game_version()), + profile.get_version(checks.contains(Checks::GAME_VERSION)), &project.game_versions, - ) && check_mod_loader_fabric_backwards_compatible(profile, project, checks.mod_loader()) + ) && check_mod_loader_fabric_backwards_compatible( + profile, + project, + checks.contains(Checks::MOD_LOADER), + ) } /// Check if the project of `project_id` exists, is a mod, and is compatible with `profile`. @@ -55,7 +59,7 @@ pub async fn modrinth( return Err(super::Error::NotAMod); } - if checks.perform_checks() && !project_comatible(profile, &project, checks) { + if checks.contains(Checks::ENABLED) && !project_comatible(profile, &project, checks) { return Err(super::Error::Incompatible); } diff --git a/src/config/structs.rs b/src/config/structs.rs index 43688e2..13bea7c 100644 --- a/src/config/structs.rs +++ b/src/config/structs.rs @@ -97,8 +97,8 @@ impl Mod { Self { name: name.into(), identifier, - check_game_version: checks.game_version(), - check_mod_loader: checks.mod_loader(), + check_game_version: checks.contains(Checks::GAME_VERSION), + check_mod_loader: checks.contains(Checks::MOD_LOADER), } } } From b268b1523595567574b474ccf36d2183ad863c1b Mon Sep 17 00:00:00 2001 From: atamakahere Date: Sat, 2 Mar 2024 12:28:47 +0530 Subject: [PATCH 10/11] Refactor add deprication note for add_single --- src/add/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/add/mod.rs b/src/add/mod.rs index 861f6dc..1446d4b 100644 --- a/src/add/mod.rs +++ b/src/add/mod.rs @@ -155,6 +155,7 @@ pub async fn add_multiple<'p>( (success_names, failures) } +#[deprecated(note = "use ModProvide::add() instead")] pub async fn add_single( modrinth: &ferinth::Ferinth, curseforge: &furse::Furse, From 6b9ecad4d787ff202d96356eaaa97684825de1df Mon Sep 17 00:00:00 2001 From: atamakahere Date: Sat, 2 Mar 2024 13:01:16 +0530 Subject: [PATCH 11/11] Docs add docs for ModProvider and Checks --- src/add/mod.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/add/mod.rs b/src/add/mod.rs index 1446d4b..f13471c 100644 --- a/src/add/mod.rs +++ b/src/add/mod.rs @@ -33,13 +33,43 @@ pub enum Error { pub type Result = std::result::Result; bitflags! { + /// Represents a set of boolean flags used for checking conditions while adding Mods using + /// [ModProvider] + /// + /// The `Checks` struct is a bitflag representation of different checks that can be performed. + /// + /// # Examples + /// + /// ``` + /// use libium::Checks; + /// + /// // Create a set of checks + /// let checks = Checks::ENABLED | Checks::GAME_VERSION; + /// // or + /// let mut checks = Checks::empty(); + /// checks.insert(Checks::ENABLED); + /// + /// + /// // Check if a specific flag is set + /// if checks.contains(Checks::ENABLED) { + /// println!("The feature is enabled."); + /// } + /// + /// // Add additional checks + /// let updated_checks = checks | Checks::MOD_LOADER; + /// ``` pub struct Checks: u8 { + /// Should we perform checks? const ENABLED = 0b00000001; + /// Should we check game version? const GAME_VERSION = 0b00000010; + /// Should we check mod loader? const MOD_LOADER = 0b00000100; } } +/// Collects all mod providers (i.e Modrinth, Cursefore and github wrappers) and abstracts away the +/// add method pub struct ModProvider<'p> { modrinth: &'p ferinth::Ferinth, curseforge: &'p furse::Furse, @@ -49,6 +79,40 @@ pub struct ModProvider<'p> { } impl<'p> ModProvider<'p> { + /// Creates a new instance of `ModManager` with the provided API wrappers, checks, and profile. + /// + /// This function constructs a new `ModManager` instance, which serves as a utility for adding mods. + /// It takes API wrappers for Modrinth, CurseForge, and GitHub, along with references to checks + /// and a mutable reference to a profile. These components are used internally for + /// for adding mods, performing checks, and managing profiles. + /// + /// # Arguments + /// + /// * `modrinth` - A reference to the Modrinth API wrapper (`ferinth::Ferinth`). + /// * `curseforge` - A reference to the CurseForge API wrapper (`furse::Furse`). + /// * `github` - A reference to the GitHub API wrapper (`octocrab::Octocrab`). + /// * `checks` - Checks to perform while adding mods + /// * `profile` - The profile to make changes in + /// + /// # Returns + /// + /// A new instance of `ModProvider` configured with the provided components. + /// + /// # Example + /// + /// ``` + /// // Create API wrappers + /// let modrinth = Ferinth::new(); + /// let curseforge = Furse::new(); + /// let github = Octocrab::builder().build(); + /// + /// // Create checks and profile + /// let checks = Checks::empty(); + /// let mut profile = Profile::new(); + /// + /// // Create a new ModProvider instance + /// let mod_provider = ModProvider::new(&modrinth, &curseforge, &github, &checks, &mut profile); + /// ``` pub fn new( modrinth: &'p ferinth::Ferinth, curseforge: &'p furse::Furse, @@ -65,6 +129,28 @@ impl<'p> ModProvider<'p> { } } + /// Add a mod to the profile based on the identifier. + /// The identifier can be: + /// - A numeric ID representing a project on CurseForge. + /// - A GitHub repository identifier in the form "username/repository". + /// - Any other string, which is assumed to be a mod ID on Modrinth. + /// + /// # Arguments + /// + /// * `identifier` - A string representing the identifier of the mod. + /// + /// # Returns + /// + /// A Result containing a String representing the added mod's information, + /// or an error if the addition failed. + /// + /// # Examples + /// + /// ``` + /// let mod_provider = ModProvider::new(&modrinth, &curseforge, &github, &checks, &mut profile); + /// let result = manager.add("123456"); + /// assert!(result.is_ok()); + /// ``` pub async fn add(&mut self, identifier: &str) -> Result { if let Ok(project_id) = identifier.parse() { self.curseforge(project_id).await @@ -75,14 +161,19 @@ impl<'p> ModProvider<'p> { } } + /// Fetches mod information from CurseForge using the provided project ID. pub async fn curseforge(&mut self, project_id: i32) -> Result { curseforge::curseforge(self.curseforge, project_id, self.profile, self.checks).await } + + /// Fetches mod information from GitHub using the provided repository identifier. pub async fn github(&mut self, identifier: &str) -> Result { let split = identifier.split('/').collect::>(); let repo_handler = self.github.repos(split[0], split[1]); github::github(&repo_handler, self.profile, self.checks).await } + + /// Fetches mod information from Modrinth using the provided identifier. pub async fn modrinth(&mut self, identifier: &str) -> Result { modrinth::modrinth(self.modrinth, identifier, self.profile, self.checks) .await @@ -129,6 +220,32 @@ impl From for Error { } } +/// Adds multiple mods to the profile using the provided `ModProvider` and a list of identifiers. +/// +/// # Arguments +/// +/// * `mod_provider` - A mutable reference to a `ModProvider` instance used for adding mods. +/// * `identifiers` - A vector of strings representing mod identifiers to be added. +/// +/// # Returns +/// +/// A tuple containing two vectors: +/// - The names of the mods successfully added to the profile. +/// - Tuples of identifiers of mods that failed to be added along with the corresponding errors. +/// +/// # Examples +/// +/// ``` +/// async fn example(mod_provider: &mut ModProvider<'_>, identifiers: Vec) { +/// let (success_names, failures) = ModProvider::add_multiple(mod_provider, identifiers).await; +/// +/// println!("Successfully added mods: {:?}", success_names); +/// println!("Failed to add mods:"); +/// for (identifier, error) in failures { +/// println!("Identifier: {}, Error: {:?}", identifier, error); +/// } +/// } +/// ``` pub async fn add_multiple<'p>( mod_provider: &mut ModProvider<'p>, identifiers: Vec,