diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bf67ae..ced6a22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog for Ferium +## `v3.28.2` + +- Update to Libium 1.12 +- Improved upgrade code to be faster and more clean +- Immediately fail if rate limit error occured + - Somewhat fixes [#51](https://github.com/theRookieCoder/ferium/issues/51) +- Show the file size when downloading files + ## `v3.28.1` ### 08.05.2022 diff --git a/Cargo.lock b/Cargo.lock index 7441710..6cb8127 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -418,9 +418,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "dialoguer" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d6b4fabcd9e97e1df1ae15395ac7e49fb144946a0d453959dc2696273b9da" +checksum = "d8c8ae48e400addc32a8710c8d62d55cb84249a7d58ac4cd959daecfbaddc545" dependencies = [ "console", "tempfile", @@ -477,9 +477,9 @@ dependencies = [ [[package]] name = "ferinth" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b47db2005894b7bd04c93482017f90a9b070a323ba3b8d625b6a701866c6c55" +checksum = "c040c36045725baf25fd0a3c6124be7c999c6789fc0533daf0d8ea81bdec90ef" dependencies = [ "bytes", "chrono", @@ -493,7 +493,7 @@ dependencies = [ [[package]] name = "ferium" -version = "3.28.1" +version = "3.28.2" dependencies = [ "anyhow", "bytes", @@ -512,6 +512,7 @@ dependencies = [ "reqwest", "semver", "serde_json", + "size", "tokio", "url", ] @@ -987,9 +988,9 @@ checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libium" -version = "1.11.4" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a86feacecf6968a6916f014f7d3b3c37f67f609b7aaf8c228abbed4613b6dd8" +checksum = "37341a12499c2bc64fce436adfe20856a4320e063bc57b05bd6c166e95b43660" dependencies = [ "bytes", "clap", @@ -1055,34 +1056,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1165,9 +1146,9 @@ dependencies = [ [[package]] name = "object" -version = "0.28.3" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "memchr", ] @@ -1621,6 +1602,15 @@ dependencies = [ "time 0.3.9", ] +[[package]] +name = "size" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5021178e8e70579d009fb545932e274ec2dde4c917791c6063d1002bee2a56" +dependencies = [ + "num-traits", +] + [[package]] name = "slab" version = "0.4.6" @@ -1658,12 +1648,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "ca642ba17f8b2995138b1d7711829c92e98c0a25ea019de790f4f09279c4e296" dependencies = [ "libc", - "winapi", + "windows-sys", ] [[package]] @@ -1808,9 +1798,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.18.1" +version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" dependencies = [ "bytes", "libc", @@ -2159,11 +2149,24 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08746b4b7ac95f708b3cccceb97b7f9a21a8916dd47fc99b0e6aaf7208f26fd7" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.35.0", + "windows_i686_gnu 0.35.0", + "windows_i686_msvc 0.35.0", + "windows_x86_64_gnu 0.35.0", + "windows_x86_64_msvc 0.35.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -2172,30 +2175,60 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3bc5134e8ce0da5d64dcec3529793f1d33aee5a51fc2b4662e0f881dd463e6" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_i686_gnu" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0343a6f35bf43a07b009b8591b78b10ea03de86b06f48e28c96206cd0f453b50" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_msvc" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1acdcbf4ca63d8e7a501be86fee744347186275ec2754d129ddeab7a1e3a02e4" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_x86_64_gnu" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "893c0924c5a990ec73cd2264d1c0cba1773a929e1a3f5dbccffd769f8c4edebb" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_msvc" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a29bd61f32889c822c99a8fdf2e93378bd2fae4d7efd2693fab09fcaaf7eff4b" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "winreg" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index 16ae4b7..f67b09c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ferium" -version = "3.28.1" +version = "3.28.2" edition = "2021" authors = ["Ilesh Thiada (theRookieCoder) ", "薛詠謙 (KyleUltimate)", "Daniel Hauck (SolidTux)"] description = "Ferium is a CLI program for managing Minecraft mods from Modrinth, CurseForge, and Github Releases" @@ -22,7 +22,7 @@ default = ["gui"] gui = ["libium/gui"] [dependencies] -tokio = { version = "1.18", default-features = false, features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.18", default-features = false, features = ["rt-multi-thread", "macros", "time"] } reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] } octocrab = { version = "0.16", default-features = false, features = ["rustls"] } clap = { version = "3.1", features = ["derive"] } @@ -33,12 +33,13 @@ itertools = "0.10" fs_extra = "1.2" colored = "2.0" ferinth = "2.2" -libium = "1.11" +libium = "1.12" anyhow = "1.0" online = "3.0" semver = "1.0" bytes = "1.1" furse = "1.1" +size = "0.1" url = "2.2" [dev-dependencies] diff --git a/src/main.rs b/src/main.rs index 7c3128c..9df36bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -109,7 +109,7 @@ async fn actual_main(cli_app: Ferium) -> Result<()> { } => { check_internet().await?; add::modrinth( - &modrinth, + modrinth, &project_id, profile, Some(!dont_check_game_version), @@ -139,7 +139,7 @@ async fn actual_main(cli_app: Ferium) -> Result<()> { } => { check_internet().await?; add::curseforge( - &curseforge, + curseforge, project_id, profile, Some(!dont_check_game_version), diff --git a/src/mutex_ext.rs b/src/mutex_ext.rs index 3ae70bc..28962c3 100644 --- a/src/mutex_ext.rs +++ b/src/mutex_ext.rs @@ -2,7 +2,7 @@ use std::sync::{Mutex, MutexGuard}; /// A sketchy way to not deal with mutex poisoning /// -/// **WARNING**: If the poison had occured during a write, the data may be corrupted. +/// **WARNING**: If the poison had occurred during a write, the data may be corrupted. /// _If_ unsafe code had poisoned the mutex, memory corruption is possible pub trait MutexExt { fn force_lock(&self) -> MutexGuard<'_, T>; diff --git a/src/subcommands/add.rs b/src/subcommands/add.rs index d2d0af7..d3f3b04 100644 --- a/src/subcommands/add.rs +++ b/src/subcommands/add.rs @@ -6,6 +6,7 @@ use ferinth::Ferinth; use furse::{structures::file_structs::FileRelationType, Furse}; use libium::{add, config, upgrade}; use octocrab::repos::RepoHandler; +use std::sync::Arc; pub async fn github( repo_handler: RepoHandler<'_>, @@ -21,8 +22,8 @@ pub async fn github( should_check_mod_loader, ) .await?; - upgrade::github( - &repo_handler, + upgrade::get_latest_compatible_asset( + repo_handler, &profile.game_version, &profile.mod_loader, should_check_game_version, @@ -34,7 +35,7 @@ pub async fn github( } pub async fn modrinth( - modrinth: &Ferinth, + modrinth: Arc, project_id: &str, profile: &mut config::structs::Profile, should_check_game_version: Option, @@ -42,44 +43,29 @@ pub async fn modrinth( ) -> Result<()> { eprint!("Adding mod... "); let project = add::modrinth( - modrinth, + modrinth.clone(), project_id, profile, should_check_game_version, should_check_mod_loader, ) .await?; - // Add dependencies - let result = upgrade::modrinth( - modrinth, + let latest_version = upgrade::get_latest_compatible_version( + modrinth.clone(), &project.id, &profile.game_version, &profile.mod_loader, should_check_game_version, should_check_mod_loader, ) - .await; - let latest_version = if matches!(result, Err(upgrade::Error::NoCompatibleFile)) - && profile.mod_loader == config::structs::ModLoader::Quilt - { - upgrade::modrinth( - modrinth, - &project.id, - &profile.game_version, - &config::structs::ModLoader::Fabric, - should_check_game_version, - should_check_mod_loader, - ) - .await - } else { - result - }?; + .await? + .1; println!("{} ({})", *TICK, project.title); - for dependency in latest_version.dependencies { - let id = if let Some(project_id) = dependency.project_id { - project_id - } else if let Some(version_id) = dependency.version_id { - modrinth.get_version(&version_id).await?.project_id + for dependency in &latest_version.dependencies { + let id = if let Some(project_id) = &dependency.project_id { + project_id.clone() + } else if let Some(version_id) = &dependency.version_id { + modrinth.get_version(version_id).await?.project_id } else { break; }; @@ -90,7 +76,7 @@ pub async fn modrinth( // If it's required, add it without asking if dependency.dependency_type == DependencyType::Required { eprint!("Adding required dependency... "); - let project = add::modrinth(modrinth, &id, profile, None, None).await?; + let project = add::modrinth(modrinth.clone(), &id, profile, None, None).await?; println!("{} ({})", *TICK, project.title); } else if dependency.dependency_type == DependencyType::Optional { let project = modrinth.get_project(&id).await?; @@ -102,7 +88,7 @@ pub async fn modrinth( .interact()?; if should_add { eprint!("Adding optional dependency... "); - let project = add::modrinth(modrinth, &id, profile, None, None).await?; + let project = add::modrinth(modrinth.clone(), &id, profile, None, None).await?; println!("{} ({})", *TICK, project.title); } } @@ -113,7 +99,7 @@ pub async fn modrinth( } pub async fn curseforge( - curseforge: &Furse, + curseforge: Arc, project_id: i32, profile: &mut config::structs::Profile, should_check_game_version: Option, @@ -121,40 +107,25 @@ pub async fn curseforge( ) -> Result<()> { eprint!("Adding mod... "); let project = add::curseforge( - curseforge, + curseforge.clone(), project_id, profile, should_check_game_version, should_check_mod_loader, ) .await?; - // Add dependencies - let result = upgrade::curseforge( - curseforge, + let latest_file = upgrade::get_latest_compatible_file( + curseforge.clone(), project.id, &profile.game_version, &profile.mod_loader, should_check_game_version, should_check_mod_loader, ) - .await; - let latest_version = if matches!(result, Err(upgrade::Error::NoCompatibleFile)) - && profile.mod_loader == config::structs::ModLoader::Quilt - { - upgrade::curseforge( - curseforge, - project.id, - &profile.game_version, - &config::structs::ModLoader::Fabric, - should_check_game_version, - should_check_mod_loader, - ) - .await - } else { - result - }?; + .await? + .0; println!("{} ({})", *TICK, project.name); - for dependency in latest_version.dependencies { + for dependency in &latest_file.dependencies { let id = dependency.mod_id; // Check if the dependency has already been added if !profile @@ -165,7 +136,7 @@ pub async fn curseforge( // If it's required, add it without asking if dependency.relation_type == FileRelationType::RequiredDependency { eprint!("Adding required dependency... "); - let project = add::curseforge(curseforge, id, profile, None, None).await?; + let project = add::curseforge(curseforge.clone(), id, profile, None, None).await?; println!("{} ({})", *TICK, project.name); } else if dependency.relation_type == FileRelationType::OptionalDependency { let project = curseforge.get_mod(id).await?; @@ -177,7 +148,8 @@ pub async fn curseforge( .interact()?; if should_add { eprint!("Adding optional dependency... "); - let project = add::curseforge(curseforge, id, profile, None, None).await?; + let project = + add::curseforge(curseforge.clone(), id, profile, None, None).await?; println!("{} ({})", *TICK, project.name); } } diff --git a/src/subcommands/upgrade.rs b/src/subcommands/upgrade.rs index 3181148..990a4d8 100644 --- a/src/subcommands/upgrade.rs +++ b/src/subcommands/upgrade.rs @@ -1,64 +1,21 @@ use crate::{mutex_ext::MutexExt, CROSS, TICK, YELLOW_TICK}; -use anyhow::Error; -use anyhow::{bail, Result}; +use anyhow::{bail, Error, Result}; use colored::Colorize; use ferinth::Ferinth; use fs_extra::file::{move_file, CopyOptions}; use furse::Furse; use itertools::Itertools; -use libium::config; -use libium::upgrade; +use libium::{check, config, upgrade}; use octocrab::Octocrab; -use std::sync::atomic::{AtomicBool, Ordering}; +use size::{Base, Size, Style}; use std::{ fs::read_dir, - sync::{Arc, Mutex}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, }; -use tokio::fs::copy; -use tokio::spawn; - -#[derive(Debug, Clone)] -struct Downloadable { - filename: String, - download_url: String, -} -impl From for Downloadable { - fn from(file: furse::structures::file_structs::File) -> Self { - Self { - filename: file.file_name, - download_url: file.download_url, - } - } -} -impl From for Downloadable { - #[allow(clippy::redundant_else)] // The `else` makes it more readable - fn from(version: ferinth::structures::version_structs::Version) -> Self { - let mut files = Vec::new(); - for file in version.files { - if file.primary { - return Self { - filename: file.filename, - download_url: file.url, - }; - } else { - files.push(file); - } - } - let file = files.remove(0); - Self { - filename: file.filename, - download_url: file.url, - } - } -} -impl From for Downloadable { - fn from(asset: octocrab::models::repos::Asset) -> Self { - Self { - filename: asset.name, - download_url: asset.browser_download_url.into(), - } - } -} +use tokio::{fs::copy, spawn}; pub async fn upgrade( modrinth: Arc, @@ -76,131 +33,26 @@ pub async fn upgrade( for mod_ in &profile.mods { let backwards_compat_msg = backwards_compat_msg.clone(); let to_download = to_download.clone(); - let error = error.clone(); let curseforge = curseforge.clone(); let modrinth = modrinth.clone(); let profile = profile.clone(); + let error = error.clone(); let github = github.clone(); let mod_ = mod_.clone(); tasks.push(spawn(async move { - use libium::config::structs::ModIdentifier; - let (result, backwards_compat): (Result, bool) = match &mod_.identifier - { - ModIdentifier::CurseForgeProject(project_id) => { - let result = upgrade::curseforge( - &curseforge, - *project_id, - &profile.game_version, - &profile.mod_loader, - mod_.check_game_version, - mod_.check_mod_loader, - ) - .await; - if matches!(result, Err(upgrade::Error::NoCompatibleFile)) - && profile.mod_loader == config::structs::ModLoader::Quilt - { - ( - upgrade::curseforge( - &curseforge, - *project_id, - &profile.game_version, - &config::structs::ModLoader::Fabric, - mod_.check_game_version, - mod_.check_mod_loader, - ) - .await - .map(Into::into), - true, - ) - } else if let Err(upgrade::Error::CurseForgeError( - furse::Error::ReqwestError(err), - )) = &result - { - if err.is_status() { - ( - upgrade::curseforge( - &curseforge, - *project_id, - &profile.game_version, - &profile.mod_loader, - mod_.check_game_version, - mod_.check_mod_loader, - ) - .await - .map(Into::into), - false, - ) - } else { - (result.map(Into::into), false) - } - } else { - (result.map(Into::into), false) - } - }, - ModIdentifier::ModrinthProject(project_id) => { - let result = upgrade::modrinth( - &modrinth, - project_id, - &profile.game_version, - &profile.mod_loader, - mod_.check_game_version, - mod_.check_mod_loader, - ) - .await; - if matches!(result, Err(upgrade::Error::NoCompatibleFile)) - && profile.mod_loader == config::structs::ModLoader::Quilt - { - ( - upgrade::modrinth( - &modrinth, - project_id, - &profile.game_version, - &config::structs::ModLoader::Fabric, - mod_.check_game_version, - mod_.check_mod_loader, - ) - .await - .map(Into::into), - true, - ) - } else { - (result.map(Into::into), false) - } - }, - ModIdentifier::GitHubRepository(full_name) => { - let result = upgrade::github( - &github.repos(&full_name.0, &full_name.1), - &profile.game_version, - &profile.mod_loader, - mod_.check_game_version, - mod_.check_mod_loader, - ) - .await; - if matches!(result, Err(upgrade::Error::NoCompatibleFile)) - && profile.mod_loader == config::structs::ModLoader::Quilt - { - ( - upgrade::github( - &github.repos(&full_name.0, &full_name.1), - &profile.game_version, - &config::structs::ModLoader::Fabric, - mod_.check_game_version, - mod_.check_mod_loader, - ) - .await - .map(Into::into), - true, - ) - } else { - (result.map(Into::into), false) - } - }, - }; - + let result = upgrade::get_latest_compatible_downloadable( + modrinth.clone(), + curseforge.clone(), + github.clone(), + &mod_, + &profile.game_version, + &profile.mod_loader, + ) + .await; match result { - Ok(result) => { + Ok((downloadable, backwards_compat)) => { println!( - "{} {:40}{}", + "{} {:45} {}", if backwards_compat { backwards_compat_msg.store(true, Ordering::Relaxed); YELLOW_TICK.clone() @@ -208,22 +60,28 @@ pub async fn upgrade( TICK.clone() }, mod_.name, - format!("({})", result.filename).dimmed() + format!("({})", downloadable.filename).dimmed() ); { let mut to_download = to_download.force_lock(); - to_download.push(result); + to_download.push(downloadable); } }, Err(err) => { - eprintln!("{}", format!("{} {:40}{}", CROSS, mod_.name, err).red()); + if let upgrade::Error::ModrinthError(ferinth::Error::RateLimitExceeded(_)) = err + { + // Immediately fail if there is a rate limit + bail!(err); + } + eprintln!("{}", format!("{} {:45} {}", CROSS, mod_.name, err).red()); error.store(true, Ordering::Relaxed); }, } + Ok(()) })); } for handle in tasks { - handle.await?; + handle.await??; } let mut to_download = Arc::try_unwrap(to_download) .expect("Failed to run threads to completion") @@ -246,8 +104,6 @@ pub async fn upgrade( } } - println!("\n{}\n", "Downloading Mod Files".bold()); - for file in read_dir(&profile.output_dir)? { let file = file?; if file.file_type()?.is_file() { @@ -272,30 +128,42 @@ pub async fn upgrade( } } - let mut tasks = Vec::new(); - for downloadable in to_download { - let profile = profile.clone(); - let downloadable = downloadable.clone(); - tasks.push(spawn(async move { - let contents = reqwest::get(&downloadable.download_url) - .await? - .bytes() - .await?; - upgrade::write_mod_file(&profile, contents, &downloadable.filename).await?; - println!("{} Downloaded {}", &*TICK, downloadable.filename.dimmed()); - Ok::<(), Error>(()) - })); - } - for handle in tasks { - handle.await??; - } - for installable in to_install { - eprint!( - "Installing {}... ", - installable.0.to_string_lossy().dimmed() - ); - copy(installable.1, profile.output_dir.join(installable.0)).await?; - println!("{}", &*TICK); + if to_download.is_empty() && to_install.is_empty() { + println!("\n{}", "All up to date!".bold()); + } else { + println!("\n{}\n", "Downloading Mod Files".bold()); + let mut tasks = Vec::new(); + for downloadable in to_download { + let profile = profile.clone(); + let downloadable = downloadable.clone(); + tasks.push(spawn(async move { + let contents = reqwest::get(&downloadable.download_url) + .await? + .bytes() + .await?; + let size = Size::Bytes(contents.len()); + check::write_mod_file(&profile.output_dir, contents, &downloadable.filename) + .await?; + println!( + "{} Downloaded {:7} {}", + &*TICK, + size.to_string(Base::Base10, Style::Smart), + downloadable.filename.dimmed(), + ); + Ok::<(), Error>(()) + })); + } + for handle in tasks { + handle.await??; + } + for installable in to_install { + eprint!( + "Installing {}... ", + installable.0.to_string_lossy().dimmed() + ); + copy(installable.1, profile.output_dir.join(installable.0)).await?; + println!("{}", &*TICK); + } } if error.load(Ordering::Relaxed) {