Skip to content

Commit a303015

Browse files
Parallelised network requests
1 parent 68fba7c commit a303015

File tree

9 files changed

+263
-156
lines changed

9 files changed

+263
-156
lines changed

CHANGELOG.md

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

3+
## `v3.28.0`
4+
### 08.05.2022
5+
6+
Upgrading and verbose listing of mods is now _**SUPER**_ fast compared to before (14-20 times) due to multi threading
7+
8+
- Added multi threading for getting latest mod versions and downloading mods
9+
- Added `--threads` options to limit the maximum number of additional threads
10+
- Used `Arc` in many locations to use the APIs without having to _actually_ clone them
11+
- Added `mutex_ext` to (somewhat unsafely) recover from a poison error and lock a mutex
12+
- If a CurseForge request fails during version determination with a status code, then the request is tried again
13+
- Requests are sent so fast the CF API gives 500 internal server errors sometimes
14+
315
## `v3.27.0`
416
### 07.05.2022
517

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.27.0"
3+
version = "3.28.0"
44
edition = "2021"
55
authors = ["Ilesh Thiada (theRookieCoder) <[email protected]>", "薛詠謙 (KyleUltimate)", "Daniel Hauck (SolidTux)"]
66
description = "Ferium is a CLI program for managing Minecraft mods from Modrinth, CurseForge, and Github Releases"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ Simply specify the mods you use through the CLI and in just one command, you can
1515
- Upgrade all your mods in one command, `ferium upgrade`
1616
- Ferium checks that the version being downloaded is the latest one compatible with the chosen mod loader and Minecraft version
1717
- Create multiple profiles and configure different mod loaders, Minecraft versions, output directories, and mods for each
18-
- Configure overrides for mods that are not specified as compatible, but still work
18+
- Configure overrides for mods that are not specified as compatible but still work
19+
- Multi-threading for network intensive subcommands
20+
- You can configure the maximum number of additional threads using the `--threads` options
1921

2022
## Installation
2123

src/cli.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use std::path::PathBuf;
88
pub struct Ferium {
99
#[clap(subcommand)]
1010
pub subcommand: SubCommands,
11+
#[clap(long, short)]
12+
#[clap(help("The limit for additional threads spawned by the Tokio runtime"))]
13+
pub threads: Option<usize>,
1114
#[clap(long)]
1215
#[clap(help("A GitHub personal access token for increasing the rate limit"))]
1316
pub github_token: Option<String>,

src/main.rs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod cli;
2+
mod mutex_ext;
23
mod subcommands;
34

45
use anyhow::{anyhow, bail, Result};
@@ -9,8 +10,9 @@ use ferinth::Ferinth;
910
use furse::Furse;
1011
use lazy_static::lazy_static;
1112
use libium::config;
13+
use std::sync::Arc;
1214
use subcommands::{add, upgrade};
13-
use tokio::{fs::create_dir_all, io::AsyncReadExt};
15+
use tokio::{fs::create_dir_all, io::AsyncReadExt, runtime, spawn};
1416

1517
const CROSS: &str = "×";
1618
lazy_static! {
@@ -20,30 +22,35 @@ lazy_static! {
2022
dialoguer::theme::ColorfulTheme::default();
2123
}
2224

23-
#[tokio::main]
24-
async fn main() {
25-
if let Err(err) = actual_main().await {
25+
fn main() {
26+
let cli = Ferium::parse();
27+
let mut builder = runtime::Builder::new_multi_thread();
28+
builder.enable_all();
29+
builder.thread_name("ferium-worker");
30+
if let Some(threads) = cli.threads {
31+
builder.max_blocking_threads(threads);
32+
}
33+
let runtime = builder.build().expect("Could not initialise Tokio runtime");
34+
if let Err(err) = runtime.block_on(actual_main(cli)) {
2635
eprintln!("{}", err.to_string().red().bold());
36+
runtime.shutdown_background();
2737
std::process::exit(1);
2838
}
2939
}
3040

31-
async fn actual_main() -> Result<()> {
32-
// This also displays the help page or version automatically
33-
let cli_app = Ferium::parse();
34-
41+
async fn actual_main(cli_app: Ferium) -> Result<()> {
3542
let github = {
3643
let mut builder = octocrab::OctocrabBuilder::new();
3744
if let Some(token) = cli_app.github_token {
3845
builder = builder.personal_token(token);
3946
}
4047
octocrab::initialise(builder)
4148
}?;
42-
let modrinth = Ferinth::new();
43-
let curseforge = Furse::new(env!(
49+
let modrinth = Arc::new(Ferinth::new());
50+
let curseforge = Arc::new(Furse::new(env!(
4451
"CURSEFORGE_API_KEY",
4552
"A CurseForge API key is required to build. If you don't have one, you can bypass this by setting the variable to a blank string, however anything using the CurseForge API will not work."
46-
));
53+
)));
4754
let mut config_file =
4855
config::get_file(cli_app.config_file.unwrap_or_else(config::file_path)).await?;
4956
let mut config_file_contents = String::new();
@@ -144,22 +151,28 @@ async fn actual_main() -> Result<()> {
144151
},
145152
SubCommands::List { verbose } => {
146153
check_empty_profile(profile)?;
147-
for mod_ in &profile.mods {
148-
if verbose {
154+
if verbose {
155+
check_internet().await?;
156+
let mut tasks = Vec::new();
157+
for mod_ in &profile.mods {
149158
use config::structs::ModIdentifier;
150-
check_internet().await?;
151159
match &mod_.identifier {
152-
ModIdentifier::CurseForgeProject(project_id) => {
153-
subcommands::list::curseforge(&curseforge, *project_id).await
154-
},
155-
ModIdentifier::ModrinthProject(project_id) => {
156-
subcommands::list::modrinth(&modrinth, project_id).await
157-
},
158-
ModIdentifier::GitHubRepository(full_name) => {
159-
subcommands::list::github(&github, full_name).await
160-
},
161-
}?;
162-
} else {
160+
ModIdentifier::CurseForgeProject(project_id) => tasks.push(spawn(
161+
subcommands::list::curseforge(curseforge.clone(), *project_id),
162+
)),
163+
ModIdentifier::ModrinthProject(project_id) => tasks.push(spawn(
164+
subcommands::list::modrinth(modrinth.clone(), project_id.clone()),
165+
)),
166+
ModIdentifier::GitHubRepository(full_name) => tasks.push(spawn(
167+
subcommands::list::github(github.clone(), full_name.clone()),
168+
)),
169+
};
170+
}
171+
for handle in tasks {
172+
handle.await??;
173+
}
174+
} else {
175+
for mod_ in &profile.mods {
163176
println!("{}", mod_.name);
164177
}
165178
}
@@ -200,7 +213,7 @@ async fn actual_main() -> Result<()> {
200213
check_internet().await?;
201214
check_empty_profile(profile)?;
202215
create_dir_all(&profile.output_dir.join(".old")).await?;
203-
upgrade(&modrinth, &curseforge, &github, profile).await?;
216+
upgrade(modrinth, curseforge, github, profile).await?;
204217
},
205218
};
206219

src/mutex_ext.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::sync::{Mutex, MutexGuard};
2+
3+
/// A sketchy way to not deal with mutex poisoning
4+
///
5+
/// **WARNING**: If the poison had occured during a write, the data may be corrupted.
6+
/// _If_ unsafe code had poisoned the mutex, memory corruption is possible
7+
pub trait MutexExt<T> {
8+
fn force_lock(&self) -> MutexGuard<'_, T>;
9+
}
10+
11+
impl<T> MutexExt<T> for Mutex<T> {
12+
fn force_lock(&self) -> MutexGuard<'_, T> {
13+
match self.lock() {
14+
Ok(guard) => guard,
15+
Err(error) => error.into_inner(),
16+
}
17+
}
18+
}

src/subcommands/list.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use ferinth::Ferinth;
33
use furse::Furse;
44
use itertools::Itertools;
55
use octocrab::Octocrab;
6+
use std::sync::Arc;
67

7-
pub async fn curseforge(curseforge: &Furse, project_id: i32) -> Result<()> {
8+
pub async fn curseforge(curseforge: Arc<Furse>, project_id: i32) -> Result<()> {
89
let project = curseforge.get_mod(project_id).await?;
910
let authors = project
1011
.authors
@@ -41,8 +42,8 @@ pub async fn curseforge(curseforge: &Furse, project_id: i32) -> Result<()> {
4142
Ok(())
4243
}
4344

44-
pub async fn modrinth(modrinth: &Ferinth, project_id: &str) -> Result<()> {
45-
let project = modrinth.get_project(project_id).await?;
45+
pub async fn modrinth(modrinth: Arc<Ferinth>, project_id: String) -> Result<()> {
46+
let project = modrinth.get_project(&project_id).await?;
4647
let team_members = modrinth.list_team_members(&project.team).await?;
4748

4849
// Get the usernames of all the developers
@@ -83,7 +84,7 @@ pub async fn modrinth(modrinth: &Ferinth, project_id: &str) -> Result<()> {
8384
}
8485

8586
/// List all the mods in `profile` with some of their metadata
86-
pub async fn github(github: &Octocrab, full_name: &(String, String)) -> Result<()> {
87+
pub async fn github(github: Arc<Octocrab>, full_name: (String, String)) -> Result<()> {
8788
let repo_handler = github.repos(&full_name.0, &full_name.1);
8889
let repo = repo_handler.get().await?;
8990
let releases = repo_handler.releases().list().send().await?;

0 commit comments

Comments
 (0)