Skip to content

Commit 2213c96

Browse files
Improved search algorithm
1 parent d109353 commit 2213c96

File tree

10 files changed

+153
-89
lines changed

10 files changed

+153
-89
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
This changelog is formatted based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
44

5+
## [3.6.0] - 30.10.2021
6+
7+
- Added a check for mod loader compatibility in `upgrade_modrinth()` and `upgrade_github()`
8+
- Improved the GitHub Releases version and mod loader checking algorithm
9+
- `download_release()` and `download_version()` are now `download_asset()` and `download_version_file()` repectively
10+
- If multiple compatible assets were found, a selector is shown to let the user pick the latest version
11+
- The progress indicators updates now show the name of the asset being downloaded rather than the name of the `Version` or `Release`
12+
- Added function `remove_minor_version()` which is extracted from `get_latest_mc_versions()`
13+
-
14+
515
## [3.5.1] - 28.10.2021
616

717
- Migrated from `native-dialog` to [`rfd`](https://crates.io/crates/rfd)

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ferium"
3-
version = "3.5.1"
3+
version = "3.6.0"
44
edition = "2021"
55
authors = ["theRookieCoder <[email protected]>"]
66
description = "Ferium is an easy to use manager for Minecraft mods on Modrinth and Github Releases"

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ Ferium is an open source and easy to use package manager for Minecraft mods on [
99
## Feature requests
1010

1111
If you want to have a feature added, check the [project](https://github.com/theRookieCoder/ferium/projects/1) to see if the feature is already added/planned. If not, you can [create a new issue](https://github.com/theRookieCoder/ferium/issues/new)
12+
13+
## Building or working on Ferium
14+
15+
You can build the project and install it to `~/bin` by running `make install`. If you are working on the project, you can use `make install-dev`. If you want to obtain executables for a specific OS, you can run `make build-<OS>` and replace `<OS>` with `mac`, `win`, or `linux`

src/labrinth/calls.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use bytes::Bytes;
66
use reqwest::{Client, Response};
77

88
/// Return the contents of `version`'s JAR file as bytes
9-
pub async fn download_version(client: &Client, version: Version) -> FResult<Bytes> {
10-
Ok(request(client, version.files[0].url.clone())
9+
pub async fn download_version_file(client: &Client, version_file: &VersionFile) -> FResult<Bytes> {
10+
Ok(request(client, version_file.url.clone())
1111
.await?
1212
.bytes()
1313
.await?)

src/main.rs

Lines changed: 108 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use reqwest::Client;
99
use std::{
1010
fs::{create_dir_all, remove_file, OpenOptions},
1111
io::Write,
12+
path::PathBuf,
1213
};
1314
use util::{
1415
cli::SubCommand,
@@ -27,7 +28,7 @@ async fn main() -> FResult<()> {
2728
Ok(_) => println!("✓"),
2829
Err(_) => {
2930
return Err(FError::Quit {
30-
message: "Ferium requires an internet connection to work".into(),
31+
message: "× Ferium requires an internet connection to work".into(),
3132
})
3233
}
3334
}
@@ -80,6 +81,15 @@ async fn main() -> FResult<()> {
8081
Ok(())
8182
}
8283

84+
/// Fetch a mod file's path based on a `name` and `config`uration
85+
fn get_mod_file_path(config: &json::Config, name: &str) -> PathBuf {
86+
let mut mod_file_path = config
87+
.output_dir
88+
.join(format!("{}-{}-{}", name, config.loader, config.version));
89+
mod_file_path.set_extension("jar");
90+
mod_file_path
91+
}
92+
8393
/// Check if `config`'s mods and repos are empty, and if so return an error
8494
fn check_empty_config(config: &json::Config) -> FResult<()> {
8595
if config.repos.is_empty() && config.mod_slugs.is_empty() {
@@ -199,8 +209,7 @@ async fn remove(client: &Client, config: &mut json::Config) -> FResult<()> {
199209
let name = &items[item_to_remove];
200210

201211
// Remove the mod from downloaded mods
202-
let mut mod_file_path = config.output_dir.join(name);
203-
mod_file_path.set_extension("jar");
212+
let mod_file_path = get_mod_file_path(config, name);
204213
let _ = remove_file(mod_file_path);
205214

206215
// Store its name in a string
@@ -212,8 +221,7 @@ async fn remove(client: &Client, config: &mut json::Config) -> FResult<()> {
212221
let name = &items[item_to_remove];
213222

214223
// Remove the mod from downloaded mods
215-
let mut mod_file_path = config.output_dir.join(name);
216-
mod_file_path.set_extension("jar");
224+
let mod_file_path = get_mod_file_path(config, name);
217225
let _ = remove_file(mod_file_path);
218226

219227
// Store its name in a string
@@ -231,7 +239,7 @@ async fn remove(client: &Client, config: &mut json::Config) -> FResult<()> {
231239
if !items_removed.is_empty() {
232240
// Remove trailing ", "
233241
items_removed.truncate(items_removed.len() - 2);
234-
println!("Removed {} from config", items_removed);
242+
println!("Removed {}", items_removed);
235243
}
236244

237245
Ok(())
@@ -244,18 +252,18 @@ async fn add_repo_github(
244252
repo_name: String,
245253
config: &mut json::Config,
246254
) -> FResult<()> {
255+
eprint!("Adding repo {}/{}... ", owner, repo_name);
256+
247257
// Check if repo has already been added
248258
if config.repos.contains(&json::Repo {
249259
owner: owner.clone(),
250260
name: repo_name.clone(),
251261
}) {
252262
return Err(FError::Quit {
253-
message: "Repo already added to config!".into(),
263+
message: "× Repsitory already added to config!".into(),
254264
});
255265
}
256266

257-
eprint!("Adding repo {}/{}... ", owner, repo_name);
258-
259267
// Get repository metadata
260268
let repo = get_repository(client, &owner, &repo_name).await?;
261269

@@ -267,7 +275,7 @@ async fn add_repo_github(
267275
// Check if the releases contain JAR files (a mod file)
268276
'outer: for release in releases {
269277
for asset in &release.assets {
270-
if asset.name.contains(".jar") {
278+
if asset.name.contains("jar") {
271279
// If JAR release is found, set flag to true and break
272280
contains_jar_asset = true;
273281
break 'outer;
@@ -284,7 +292,7 @@ async fn add_repo_github(
284292
println!("✓")
285293
} else {
286294
return Err(FError::Quit {
287-
message: "Repository does not release mods!".into(),
295+
message: "× Repository does not release mods!".into(),
288296
});
289297
}
290298

@@ -297,15 +305,15 @@ async fn add_mod_modrinth(
297305
mod_id: String,
298306
config: &mut json::Config,
299307
) -> FResult<()> {
308+
eprint!("Adding mod ID {}... ", mod_id);
309+
300310
// Check if mod has already been added
301311
if config.mod_slugs.contains(&mod_id) {
302312
return Err(FError::Quit {
303-
message: "Mod already added to config!".into(),
313+
message: "× Mod already added to config!".into(),
304314
});
305315
}
306316

307-
eprint!("Adding mod {}... ", mod_id);
308-
309317
// Check if mod exists
310318
match get_mod(client, &mod_id).await {
311319
Ok(mod_) => {
@@ -316,7 +324,7 @@ async fn add_mod_modrinth(
316324
Err(_) => {
317325
// Else return an error
318326
return Err(FError::Quit {
319-
message: format!("Mod with ID {} does not exist!", mod_id),
327+
message: format!("× Mod with ID `{}` does not exist!", mod_id),
320328
});
321329
}
322330
};
@@ -332,14 +340,16 @@ async fn list(client: &Client, config: &json::Config) -> FResult<()> {
332340

333341
// Print mod data formatted
334342
println!(
335-
"- {}
336-
\r {}
343+
"- {} (Modrinth)
344+
\r {}\n
345+
\r Link: https://modrinth.com/mod/{}
337346
\r Downloads: {}
338347
\r Client side: {}
339348
\r Server side: {}
340349
\r License: {}\n",
341350
mod_.title,
342351
mod_.description,
352+
mod_.slug,
343353
mod_.downloads,
344354
mod_.client_side,
345355
mod_.server_side,
@@ -350,15 +360,30 @@ async fn list(client: &Client, config: &json::Config) -> FResult<()> {
350360
for repo_name in &config.repos {
351361
// Get repository metadata
352362
let repo = get_repository(client, &repo_name.owner, &repo_name.name).await?;
363+
let releases = get_releases(client, &repo).await?;
364+
let mut downloads = 0;
365+
366+
// Calculate number of downloads
367+
for release in releases {
368+
for asset in release.assets {
369+
downloads += asset.download_count;
370+
}
371+
}
353372

354373
// Print repository data formatted
355374
println!(
356-
"- {}
357-
\r {}
358-
\r Stars: {}
359-
\r Developer: {}
360-
\r License: {}\n",
361-
repo.name, repo.description, repo.stargazers_count, repo.owner.login, repo.license.name,
375+
"- {} (GitHub)
376+
\r {}\n
377+
\r Link: {}
378+
\r Downloads: {}
379+
\r Developer: {}
380+
\r License: {}\n",
381+
repo.name,
382+
repo.description,
383+
repo.html_url,
384+
downloads,
385+
repo.owner.login,
386+
repo.license.name,
362387
)
363388
}
364389

@@ -370,44 +395,71 @@ async fn upgrade_github(client: &Client, config: &json::Config) -> FResult<()> {
370395
for repo_name in &config.repos {
371396
println!("Downloading {}", repo_name.name);
372397
eprint!(" [1] Getting release information... ");
373-
// Get mod's repository
398+
374399
let repository = get_repository(client, &repo_name.owner, &repo_name.name).await?;
375-
// Get releases
376400
let releases = get_releases(client, &repository).await?;
377401

378-
let mut latest_release: Option<&octorok::structs::Release> = None;
402+
// A vector of assets that are compatible
403+
let mut asset_candidates: Vec<&octorok::structs::Asset> = Vec::new();
404+
// Whether the mod specifies the mod loader in its Assets' names
405+
let mut specifies_loader = false;
379406

380-
// Try to get the latest compatible release
407+
// Try to get the latest compatible assets
381408
for release in &releases {
382-
if release.name.contains(&config.version) {
383-
latest_release = Some(release);
409+
// If a release with compatible assets has been found, stop searching older releases
410+
if !asset_candidates.is_empty() {
384411
break;
385412
}
386-
}
387413

388-
let latest_release = match latest_release {
389-
// If a compatible release was found, install it
390-
Some(release) => {
391-
println!("✓");
392-
release
393-
}
394-
// If not, default to the latest one
395-
None => {
396-
println!(
397-
"✓ Warning! Did not find release with version in name. Defaulting to latest"
398-
);
399-
&releases[0]
414+
for asset in &release.assets {
415+
// If the asset specifies the mod loader, set the `specifies_loader` flag to true
416+
if asset.name.to_lowercase().contains("fabric")
417+
|| asset.name.to_lowercase().contains("forge")
418+
{
419+
specifies_loader = true;
420+
}
421+
422+
if asset
423+
.name
424+
// Check that the asset supports the user's specified version
425+
.contains(&wrappers::remove_minor_version(&config.version)?)
426+
// Check that the asset is a JAR file
427+
&& asset.name.contains("jar")
428+
// If the asset specifies a mod loader, check for it, if not don't check and return true
429+
&& !(specifies_loader && !asset.name.to_lowercase().contains(&config.loader))
430+
{
431+
// Specify this asset as a compatible asset
432+
asset_candidates.push(asset);
433+
}
400434
}
435+
}
436+
437+
// If 1 compatible asset was found, use it
438+
let asset_to_download = if asset_candidates.len() == 1 {
439+
println!("✓");
440+
asset_candidates[0]
441+
// If none were found, throw an error
442+
} else if asset_candidates.len() == 0 {
443+
return Err(FError::Quit {
444+
message: "× Could not find a compatible asset to download".into(),
445+
});
446+
// If more than 1 was found, let the user select which one to use
447+
} else {
448+
println!("✓");
449+
println!("Select the asset to downloaded:");
450+
let selection = Select::with_theme(&ColorfulTheme::default())
451+
.items(&asset_candidates)
452+
.interact()?;
453+
asset_candidates[selection]
401454
};
402455

403-
eprint!(" [2] Downloading {}... ", latest_release.name);
456+
eprint!(" [2] Downloading {}... ", asset_to_download.name);
404457

405-
// Compute mod file's output path
406-
let mut mod_file_path = config.output_dir.join(&repository.name);
407-
mod_file_path.set_extension("jar");
458+
// Compute output mod file's path
459+
let mod_file_path = get_mod_file_path(config, &repository.name);
408460

409461
// Get file contents
410-
let contents = download_release(client, latest_release).await?;
462+
let contents = download_asset(client, asset_to_download).await?;
411463

412464
// Open the mod JAR file
413465
let mut mod_file = OpenOptions::new()
@@ -438,9 +490,11 @@ async fn upgrade_modrinth(client: &Client, config: &json::Config) -> FResult<()>
438490

439491
let mut latest_version: Option<labrinth::structs::Version> = None;
440492

441-
// Check if a version compatible with the game version specified in the config is available
493+
// Check if a version compatible with the game version and mod loader specified in the config is available
442494
for version in versions {
443-
if version.game_versions.contains(&config.version) {
495+
if version.game_versions.contains(&config.version)
496+
&& version.loaders.contains(&config.loader)
497+
{
444498
latest_version = Some(version);
445499
break;
446500
}
@@ -452,8 +506,8 @@ async fn upgrade_modrinth(client: &Client, config: &json::Config) -> FResult<()>
452506
None => {
453507
return Err(FError::Quit {
454508
message: format!(
455-
"No version of {} is compatible for Minecraft {}",
456-
mod_.title, config.version,
509+
"× No version of {} is compatible for {} {}",
510+
mod_.title, config.loader, config.version,
457511
),
458512
});
459513
}
@@ -463,11 +517,11 @@ async fn upgrade_modrinth(client: &Client, config: &json::Config) -> FResult<()>
463517

464518
eprint!(" [2] Downloading {}... ", latest_version.name);
465519

466-
let mut mod_file_path = config.output_dir.join(mod_.title);
467-
mod_file_path.set_extension("jar");
520+
// Compute output mod file's path
521+
let mod_file_path = get_mod_file_path(config, &mod_.title);
468522

469523
// Get file contents
470-
let contents = download_version(client, latest_version).await?;
524+
let contents = download_version_file(client, &latest_version.files[0]).await?;
471525

472526
// Open mod JAR file
473527
let mut mod_file = OpenOptions::new()

0 commit comments

Comments
 (0)