Skip to content

Commit 9929937

Browse files
Implement scanning of mods
1 parent 7816162 commit 9929937

File tree

10 files changed

+179
-51
lines changed

10 files changed

+179
-51
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog for Ferium
22

3+
## `v4.7.0`
4+
### 11.06.2024
5+
6+
- Features
7+
- Scan a directory (the profile's output directory by default) to automatically add the mods
8+
- Uses a maximum of 4 network requests! Unfortunately file hashing and searching for the file on the server take some time so it's not instant, especially with a large number of mods.
9+
10+
- Internal Changes
11+
- Move code for displaying successes and failures to the `add` module
12+
313
## `v4.6.0`
414
### 10.06.2024
515

Cargo.lock

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
[package]
22

33
name = "ferium"
4-
version = "4.6.0"
4+
version = "4.7.0"
55
repository = "https://github.com/gorilla-devs/ferium"
66
description = "Fast CLI program for managing Minecraft mods and modpacks from Modrinth, CurseForge, and Github Releases"
77
authors = [
88
## Code
99
"Ilesh Thiada (theRookieCoder) <[email protected]>", # AUR, Scoop, Homebrew, winget
1010
"atamakahere (atamakahere-git)",
11+
"Tuxinal",
1112

1213
## Package Management
1314
"KyleUltimateS", # AUR
@@ -58,7 +59,7 @@ octocrab = "0.38"
5859
fs_extra = "1.3"
5960
ferinth = "2.11"
6061
colored = "2.1"
61-
libium = "1.28"
62+
libium = "1.29"
6263
anyhow = "1.0"
6364
furse = "1.5"
6465
size = "0.4"

src/add.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use std::collections::HashMap;
2+
3+
use colored::Colorize as _;
4+
use itertools::Itertools as _;
5+
use libium::add::Error;
6+
7+
pub fn display_successes_failures(successes: &[String], failures: Vec<(String, Error)>) -> bool {
8+
if !successes.is_empty() {
9+
println!(
10+
"{} {}",
11+
"Successfully added".green(),
12+
successes.iter().map(|s| s.bold()).format(", ")
13+
);
14+
}
15+
16+
let mut grouped_errors = HashMap::new();
17+
18+
for (id, error) in failures {
19+
grouped_errors
20+
.entry(error.to_string())
21+
.or_insert_with(Vec::new)
22+
.push(id);
23+
}
24+
25+
let pad_len = grouped_errors
26+
.keys()
27+
.map(String::len)
28+
.max()
29+
.unwrap_or(0)
30+
.clamp(0, 50);
31+
32+
let mut exit_error = false;
33+
for (err, ids) in grouped_errors {
34+
println!(
35+
"{:pad_len$}: {}",
36+
// Change already added into a warning
37+
if err == libium::add::Error::AlreadyAdded.to_string() {
38+
err.yellow()
39+
} else {
40+
exit_error = true;
41+
err.red()
42+
},
43+
ids.iter().map(|s| s.italic()).format(", ")
44+
);
45+
}
46+
47+
exit_error
48+
}

src/cli.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![deny(missing_docs)]
22

3-
use clap::{Parser, Subcommand, ValueHint};
3+
use clap::{Parser, Subcommand, ValueEnum, ValueHint};
44
use clap_complete::Shell;
55
use libium::config::structs::ModLoader;
66
use std::path::PathBuf;
@@ -55,6 +55,23 @@ pub enum SubCommands {
5555
#[clap(long, short = 'M', alias = "dont-check-mod-loader")]
5656
ignore_mod_loader: bool,
5757
},
58+
/// Scan the profile's output directory (or the specified directory) for mods and add them to the profile
59+
Scan {
60+
/// The platform you prefer mods to be added from.
61+
/// If a mod isn't available from this platform, the other platform will still be used.
62+
#[clap(long, short, default_value_t)]
63+
platform: Platform,
64+
/// The directory to scan mods from.
65+
/// Defaults to the profile's output directory.
66+
#[clap(long, short,
67+
visible_aliases = ["dir", "folder"],
68+
aliases = ["output_directory", "out_dir"]
69+
)]
70+
directory: Option<PathBuf>,
71+
/// Temporarily ignore game version and mod loader checks and add the mods anyway
72+
#[clap(long, short, visible_alias = "override")]
73+
force: bool,
74+
},
5875
/// Print shell auto completions for the specified shell
5976
Complete {
6077
/// The shell to generate auto completions for
@@ -224,3 +241,21 @@ pub enum ModpackSubCommands {
224241
#[clap(visible_aliases = ["download", "install"])]
225242
Upgrade,
226243
}
244+
245+
#[derive(Clone, Copy, Default, ValueEnum)]
246+
pub enum Platform {
247+
#[default]
248+
#[clap(alias = "mr")]
249+
Modrinth,
250+
#[clap(alias = "cf")]
251+
Curseforge,
252+
}
253+
254+
impl std::fmt::Display for Platform {
255+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256+
match self {
257+
Self::Modrinth => write!(f, "modrinth"),
258+
Self::Curseforge => write!(f, "curseforge"),
259+
}
260+
}
261+
}

src/main.rs

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
clippy::too_many_lines
1919
)]
2020

21+
mod add;
2122
mod cli;
2223
mod download;
2324
mod subcommands;
@@ -41,7 +42,6 @@ use libium::{
4142
};
4243
use once_cell::sync::Lazy;
4344
use std::{
44-
collections::HashMap,
4545
env::{var, var_os},
4646
process::ExitCode,
4747
};
@@ -170,6 +170,63 @@ async fn actual_main(mut cli_app: Ferium) -> Result<()> {
170170
SubCommands::Complete { .. } | SubCommands::Profiles | SubCommands::Modpacks => {
171171
unreachable!();
172172
}
173+
SubCommands::Scan {
174+
platform,
175+
directory,
176+
force,
177+
} => {
178+
let profile = get_active_profile(&mut config)?;
179+
180+
let spinner = indicatif::ProgressBar::new_spinner().with_message("Reading files");
181+
spinner.enable_steady_tick(std::time::Duration::from_millis(100));
182+
183+
let ids = libium::scan(
184+
&modrinth,
185+
&curseforge,
186+
directory.as_ref().unwrap_or(&profile.output_dir),
187+
|| {
188+
spinner.set_message("Querying servers");
189+
},
190+
)
191+
.await?;
192+
193+
spinner.set_message("Adding mods");
194+
195+
let mut send_ids = Vec::new();
196+
for id in ids {
197+
use libium::config::structs::ModIdentifier;
198+
match id {
199+
(filename, None, None) => {
200+
println!("{} {}", "Unknown file:".yellow(), filename.dimmed());
201+
}
202+
(_, Some(mr_id), None) => send_ids.push(ModIdentifier::ModrinthProject(mr_id)),
203+
(_, None, Some(cf_id)) => {
204+
send_ids.push(ModIdentifier::CurseForgeProject(cf_id));
205+
}
206+
(_, Some(mr_id), Some(cf_id)) => match platform {
207+
cli::Platform::Modrinth => {
208+
send_ids.push(ModIdentifier::ModrinthProject(mr_id));
209+
}
210+
cli::Platform::Curseforge => {
211+
send_ids.push(ModIdentifier::CurseForgeProject(cf_id));
212+
}
213+
},
214+
}
215+
}
216+
217+
let (successes, failures) = libium::add(
218+
libium::APIs::new(&modrinth, &curseforge, &github.build()?),
219+
profile,
220+
send_ids,
221+
!force,
222+
true,
223+
true,
224+
)
225+
.await?;
226+
spinner.finish_and_clear();
227+
228+
add_error = add::display_successes_failures(&successes, failures);
229+
}
173230
SubCommands::Add {
174231
identifiers,
175232
force,
@@ -182,53 +239,20 @@ async fn actual_main(mut cli_app: Ferium) -> Result<()> {
182239
bail!("Only use the ignore flags when adding a single mod!")
183240
}
184241

185-
let (successes, failures) = libium::add::add(
242+
let (successes, failures) = libium::add(
186243
libium::APIs::new(&modrinth, &curseforge, &github.build()?),
187244
profile,
188-
identifiers,
245+
identifiers
246+
.into_iter()
247+
.map(libium::add::parse_id)
248+
.collect_vec(),
189249
!force,
190250
!ignore_game_version,
191251
!ignore_mod_loader,
192252
)
193253
.await?;
194254

195-
if !successes.is_empty() {
196-
println!(
197-
"{} {}",
198-
"Successfully added".green(),
199-
successes.iter().map(|s| s.bold()).format(", ")
200-
);
201-
}
202-
203-
let mut grouped_errors = HashMap::new();
204-
205-
for (id, error) in failures {
206-
grouped_errors
207-
.entry(error.to_string())
208-
.or_insert_with(Vec::new)
209-
.push(id);
210-
}
211-
212-
let pad_len = grouped_errors
213-
.keys()
214-
.map(String::len)
215-
.max()
216-
.unwrap_or(0)
217-
.clamp(0, 50);
218-
219-
for (err, ids) in grouped_errors {
220-
println!(
221-
"{:pad_len$}: {}",
222-
// Change already added into a warning
223-
if err == libium::add::Error::AlreadyAdded.to_string() {
224-
err.yellow()
225-
} else {
226-
add_error |= true;
227-
err.red()
228-
},
229-
ids.iter().map(|s| s.italic()).format(", ")
230-
);
231-
}
255+
add_error = add::display_successes_failures(&successes, failures);
232256
}
233257
SubCommands::List { verbose, markdown } => {
234258
let profile = get_active_profile(&mut config)?;

tests/integration_tests.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,14 @@ fn add_all() -> Result {
147147
)
148148
}
149149

150+
#[test]
151+
fn scan_dir() -> Result {
152+
run_command(
153+
vec!["scan", "--directory", "./tests/test_mods"],
154+
Some("empty_profile"),
155+
)
156+
}
157+
150158
#[test]
151159
fn modpack_add_modrinth() -> Result {
152160
// Add Fabulously Optimised

tests/test_mods/Incendium.jar

4.81 MB
Binary file not shown.

tests/test_mods/Sodium.jar

1.26 MB
Binary file not shown.

tests/test_mods/Starlight.jar

124 KB
Binary file not shown.

0 commit comments

Comments
 (0)