use super::ressources::{libraries, natives, client, assets, mods, resourcepacks, shaderpacks, datapacks};
use lighty_loaders::types::{Loader, VersionInfo, version_metadata::{Mods, Version}};
use lighty_core::{mkdir, time_it};
use lighty_modsloader::WithMods;
use crate::errors::InstallerResult;
#[cfg(feature = "events")]
use lighty_event::{EventBus, Event, LaunchEvent};
pub use self::installer_trait::Installer;
mod installer_trait {
use super::*;
use std::future::Future;
pub trait Installer {
fn install(
&self,
builder: &Version,
#[cfg(feature = "events")] event_bus: Option<&EventBus>,
) -> impl Future<Output = InstallerResult<()>> + Send;
}
}
impl<T> Installer for T
where
T: VersionInfo<LoaderType = Loader> + WithMods,
{
async fn install(
&self,
builder: &Version,
#[cfg(feature = "events")] event_bus: Option<&EventBus>,
) -> InstallerResult<()> {
lighty_core::trace_info!("[Installer] Starting installation for {}", self.name());
create_directories(self).await;
let resolved = resolve_extra_mods(
self,
builder,
#[cfg(feature = "events")]
event_bus,
)
.await?;
let builder: &Version = resolved.as_ref().unwrap_or(builder);
lighty_core::trace_info!("[Installer] Verifying installed files...");
let mods_slice = builder.mods.as_deref().unwrap_or(&[]);
let (
library_tasks,
client_task,
asset_tasks,
(mod_tasks, mod_bytes),
(resourcepack_tasks, resourcepack_bytes),
(shaderpack_tasks, shaderpack_bytes),
(datapack_tasks, datapack_bytes),
(native_download_tasks, native_extract_paths),
) = tokio::join!(
libraries::collect_library_tasks(self, &builder.libraries),
client::collect_client_task(self, builder.client.as_ref()),
assets::collect_asset_tasks(self, builder.assets.as_ref()),
mods::collect_mod_tasks(self, mods_slice),
resourcepacks::collect_resourcepack_tasks(self, mods_slice),
shaderpacks::collect_shaderpack_tasks(self, mods_slice),
datapacks::collect_datapack_tasks(self, mods_slice),
natives::collect_native_tasks(self, builder.natives.as_deref().unwrap_or(&[])),
);
let total_downloads = library_tasks.len()
+ client_task.as_ref().map(|_| 1).unwrap_or(0)
+ asset_tasks.len()
+ mod_tasks.len()
+ resourcepack_tasks.len()
+ shaderpack_tasks.len()
+ datapack_tasks.len()
+ native_download_tasks.len();
if total_downloads == 0 {
#[cfg(feature = "events")]
if let Some(bus) = event_bus {
bus.emit(Event::Launch(LaunchEvent::IsInstalled {
version: self.name().to_string(),
}));
}
lighty_core::trace_info!("[Installer] All files already up-to-date");
if !native_extract_paths.is_empty() {
natives::download_and_extract_natives(
self,
native_download_tasks,
native_extract_paths,
#[cfg(feature = "events")]
event_bus,
)
.await?;
}
lighty_core::trace_info!("[Installer] Installation completed successfully!");
return Ok(());
}
#[cfg(feature = "events")]
let total_bytes = {
let mod_like = mod_bytes + resourcepack_bytes + shaderpack_bytes + datapack_bytes;
calculate_download_size(
builder,
&library_tasks,
&client_task,
&asset_tasks,
&native_download_tasks,
mod_like,
)
};
#[cfg(feature = "events")]
if let Some(bus) = event_bus {
bus.emit(Event::Launch(LaunchEvent::InstallStarted {
version: self.name().to_string(),
total_bytes,
}));
}
lighty_core::trace_info!("[Installer] Downloading {} file(s)...", total_downloads);
time_it!("Total installation", {
tokio::try_join!(
libraries::download_libraries(
library_tasks,
#[cfg(feature = "events")]
event_bus
),
natives::download_and_extract_natives(
self,
native_download_tasks,
native_extract_paths,
#[cfg(feature = "events")]
event_bus
),
mods::download_mods(
mod_tasks,
#[cfg(feature = "events")]
event_bus
),
resourcepacks::download_resourcepacks(
resourcepack_tasks,
resourcepack_bytes,
#[cfg(feature = "events")]
event_bus
),
shaderpacks::download_shaderpacks(
shaderpack_tasks,
shaderpack_bytes,
#[cfg(feature = "events")]
event_bus
),
datapacks::download_datapacks(
datapack_tasks,
datapack_bytes,
#[cfg(feature = "events")]
event_bus
),
client::download_client(
client_task,
#[cfg(feature = "events")]
event_bus
),
assets::download_assets(
asset_tasks,
#[cfg(feature = "events")]
event_bus
),
)?;
});
#[cfg(feature = "events")]
if let Some(bus) = event_bus {
bus.emit(Event::Launch(LaunchEvent::InstallCompleted {
version: self.name().to_string(),
total_bytes,
}));
}
lighty_core::trace_info!("[Installer] Installation completed successfully!");
Ok(())
}
}
async fn resolve_extra_mods<T>(
version: &T,
builder: &Version,
#[cfg(feature = "events")] event_bus: Option<&EventBus>,
) -> InstallerResult<Option<Version>>
where
T: VersionInfo<LoaderType = Loader> + WithMods,
{
let (modpack_mods, user_mods) = tokio::try_join!(
resolve_modpack_mods(
version,
#[cfg(feature = "events")]
event_bus,
),
resolve_user_mod_requests(
version,
#[cfg(feature = "events")]
event_bus,
),
)?;
if modpack_mods.is_empty() && user_mods.is_empty() {
return Ok(None);
}
let mut merged = builder.clone();
let combined_capacity = modpack_mods.len() + user_mods.len();
let slot = merged
.mods
.get_or_insert_with(|| Vec::with_capacity(combined_capacity));
slot.extend(modpack_mods);
slot.extend(user_mods);
Ok(Some(merged))
}
async fn resolve_modpack_mods<T>(
version: &T,
#[cfg(feature = "events")] event_bus: Option<&EventBus>,
) -> InstallerResult<Vec<Mods>>
where
T: VersionInfo<LoaderType = Loader> + WithMods,
{
#[cfg(any(feature = "modrinth", feature = "curseforge"))]
{
match version.modpack() {
Some(source) => {
super::ressources::modpack::process(
source,
version,
#[cfg(feature = "events")]
event_bus,
)
.await
}
None => Ok(Vec::new()),
}
}
#[cfg(not(any(feature = "modrinth", feature = "curseforge")))]
{
let _ = version;
Ok(Vec::new())
}
}
async fn resolve_user_mod_requests<T>(
version: &T,
#[cfg(feature = "events")] event_bus: Option<&EventBus>,
) -> InstallerResult<Vec<Mods>>
where
T: VersionInfo<LoaderType = Loader> + WithMods,
{
#[cfg(any(feature = "modrinth", feature = "curseforge"))]
{
let mod_requests = WithMods::mod_requests(version);
if mod_requests.is_empty() {
return Ok(Vec::new());
}
let ttl = version.ttl();
Ok(lighty_modsloader::resolver::resolve(
mod_requests,
version.minecraft_version(),
version.loader(),
ttl,
#[cfg(feature = "events")]
event_bus,
)
.await?)
}
#[cfg(not(any(feature = "modrinth", feature = "curseforge")))]
{
let _ = version;
Ok(Vec::new())
}
}
async fn create_directories(version: &impl VersionInfo) {
let parent_path = version.game_dirs().to_path_buf();
mkdir!(version.runtime_dir());
mkdir!(parent_path.join("libraries"));
mkdir!(parent_path.join("natives"));
mkdir!(parent_path.join("assets").join("objects"));
}
#[cfg(feature = "events")]
fn calculate_download_size(
builder: &Version,
library_tasks: &[(String, std::path::PathBuf)],
client_task: &Option<(String, std::path::PathBuf)>,
asset_tasks: &[(String, std::path::PathBuf)],
native_download_tasks: &[(String, std::path::PathBuf)],
mod_like_bytes: u64,
) -> u64 {
let mut total = mod_like_bytes;
for (url, _) in library_tasks {
if let Some(lib) = builder.libraries.iter().find(|l| l.url.as_ref() == Some(url)) {
total += lib.size.unwrap_or(0);
}
}
if client_task.is_some() {
if let Some(client) = &builder.client {
total += client.size.unwrap_or(0);
}
}
if let Some(assets) = &builder.assets {
for (url, _) in asset_tasks {
if let Some(asset) = assets.objects.values().find(|a| a.url.as_ref() == Some(url)) {
total += asset.size;
}
}
}
if let Some(natives) = &builder.natives {
for (url, _) in native_download_tasks {
if let Some(native) = natives.iter().find(|n| n.url.as_ref() == Some(url)) {
total += native.size.unwrap_or(0);
}
}
}
total
}