#![allow(clippy::expect_used)]
use crate::{
download::{clean, download},
CROSS, STYLE_NO, TICK, YELLOW_TICK,
};
use anyhow::{anyhow, bail, Result};
use colored::Colorize;
use ferinth::Ferinth;
use furse::Furse;
use futures::{stream::FuturesUnordered, StreamExt};
use indicatif::ProgressBar;
use libium::{
config::structs::{ModLoader, Profile},
upgrade::{
mod_downloadable::{self, get_latest_compatible_downloadable},
Downloadable,
},
APIs,
};
use octocrab::Octocrab;
use std::{
fs::read_dir,
sync::{Arc, Mutex},
time::Duration,
};
use tokio::sync::Semaphore;
pub async fn get_platform_downloadables(
modrinth: Ferinth,
curseforge: Furse,
github: Octocrab,
profile: &Profile,
) -> Result<(Vec<Downloadable>, bool)> {
let to_download = Arc::new(Mutex::new(Vec::new()));
let progress_bar = Arc::new(Mutex::new(
ProgressBar::new(profile.mods.len() as u64).with_style(STYLE_NO.clone()),
));
let mut tasks = FuturesUnordered::new();
println!("{}\n", "Determining the Latest Compatible Versions".bold());
let semaphore = Arc::new(Semaphore::new(75));
progress_bar
.lock()
.expect("Mutex poisoned")
.enable_steady_tick(Duration::from_millis(100));
let pad_len = profile
.mods
.iter()
.map(|m| m.name.len())
.max()
.unwrap_or(20)
.clamp(20, 50);
for mod_ in profile.mods.clone() {
let permit = Arc::clone(&semaphore).acquire_owned().await?;
let to_download = Arc::clone(&to_download);
let progress_bar = Arc::clone(&progress_bar);
let apis = APIs::new(&modrinth, &curseforge, &github);
tasks.push(async move {
let _permit = permit;
let result = get_latest_compatible_downloadable(
apis,
&mod_,
&profile.game_version,
profile.mod_loader,
)
.await;
let progress_bar = progress_bar.lock().expect("Mutex poisoned");
progress_bar.inc(1);
match result {
Ok((downloadable, qf_flag)) => {
progress_bar.println(format!(
"{} {:pad_len$} {}",
if qf_flag {
YELLOW_TICK.clone()
} else {
TICK.clone()
},
mod_.name,
downloadable.filename().dimmed()
));
{
to_download
.lock()
.expect("Mutex poisoned")
.push(downloadable);
Ok((false, qf_flag))
}
}
Err(err) => {
if let mod_downloadable::Error::ModrinthError(
ferinth::Error::RateLimitExceeded(_),
) = err
{
progress_bar.finish_and_clear();
bail!(err);
}
progress_bar.println(format!(
"{}",
format!("{CROSS} {:pad_len$} {err}", mod_.name).red()
));
Ok((true, false))
}
}
});
}
let mut error = false;
let mut qf_flag = false;
while let Some(res) = tasks.next().await {
let res = res?;
error |= res.0;
qf_flag |= res.1;
}
Arc::try_unwrap(progress_bar)
.map_err(|_| anyhow!("Failed to run threads to completion"))?
.into_inner()?
.finish_and_clear();
if qf_flag {
println!(
"{}",
"Fabric mod using Quilt backwards compatibility".yellow()
);
}
Ok((
Arc::try_unwrap(to_download)
.map_err(|_| anyhow!("Failed to run threads to completion"))?
.into_inner()?,
error,
))
}
pub async fn upgrade(
modrinth: Ferinth,
curseforge: Furse,
github: Octocrab,
profile: &Profile,
) -> Result<()> {
let (mut to_download, error) =
get_platform_downloadables(modrinth, curseforge, github, profile).await?;
let mut to_install = Vec::new();
if profile.output_dir.join("user").exists() && profile.mod_loader != ModLoader::Quilt {
for file in read_dir(profile.output_dir.join("user"))? {
let file = file?;
let path = file.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "jar") {
to_install.push((file.file_name(), path));
}
}
}
clean(&profile.output_dir, &mut to_download, &mut to_install).await?;
to_download
.iter_mut()
.map(|thing| thing.output = thing.filename().into())
.for_each(drop); if to_download.is_empty() && to_install.is_empty() {
println!("\n{}", "All up to date!".bold());
} else {
println!("\n{}\n", "Downloading Mod Files".bold());
download(profile.output_dir.clone(), to_download, to_install).await?;
}
if error {
Err(anyhow!(
"\nCould not get the latest compatible version of some mods"
))
} else {
Ok(())
}
}