use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Context;
use nitro_shared::later::Later;
use nitro_shared::output::NitroOutput;
use nitro_shared::output::{MessageContents, MessageLevel};
use nitro_shared::versions::VersionInfo;
use nitro_shared::Side;
use crate::config::BrandingProperties;
use crate::instance::{Instance, InstanceConfiguration, InstanceParameters};
use crate::io::files::paths::Paths;
use crate::io::java::install::{JavaInstallParameters, JavaInstallation, JavaInstallationKind};
use crate::io::java::JavaMajorVersion;
use crate::io::persistent::PersistentData;
use crate::io::update::UpdateManager;
use crate::net::game_files::client_meta::{self, ClientMeta};
use crate::net::game_files::version_manifest::{
self, VersionEntry, VersionManifest, VersionManifestAndList,
};
use crate::net::game_files::{assets, game_jar, libraries};
use crate::user::UserManager;
use crate::util::versions::{MinecraftVersion, VersionName};
pub struct InstalledVersion<'inner, 'params> {
pub(crate) inner: &'inner mut InstalledVersionInner,
pub(crate) params: VersionParameters<'params>,
}
impl InstalledVersion<'_, '_> {
pub fn get_version(&self) -> &VersionName {
&self.inner.version
}
pub fn get_client_meta(&self) -> &ClientMeta {
&self.inner.client_meta
}
#[must_use]
pub fn get_version_info(&self) -> VersionInfo {
VersionInfo {
version: self.inner.version.to_string(),
versions: self.inner.version_manifest.list.clone(),
}
}
pub async fn get_instance(
&mut self,
config: InstanceConfiguration,
o: &mut impl NitroOutput,
) -> anyhow::Result<Instance> {
let params = InstanceParameters {
version: &self.inner.version,
version_manifest: &self.inner.version_manifest,
paths: self.params.paths,
req_client: self.params.req_client,
persistent: self.params.persistent,
update_manager: self.params.update_manager,
client_meta: &self.inner.client_meta,
users: self.params.users,
java_installations: self.params.java_installations,
client_assets_and_libs: &mut self.inner.client_assets_and_libs,
censor_secrets: self.params.censor_secrets,
disable_hardlinks: self.params.disable_hardlinks,
branding: self.params.branding,
};
let instance = Instance::load(config, params, o)
.await
.context("Failed to load instance")?;
Ok(instance)
}
pub async fn ensure_client_assets_and_libs(
&mut self,
o: &mut impl NitroOutput,
) -> anyhow::Result<()> {
let params = ClientAssetsAndLibsParameters {
client_meta: &self.inner.client_meta,
version: &self.inner.version,
paths: self.params.paths,
req_client: self.params.req_client,
version_manifest: &self.inner.version_manifest,
update_manager: self.params.update_manager,
};
self.inner.client_assets_and_libs.load(params, o).await
}
pub async fn get_java_installation(
&mut self,
kind: JavaInstallationKind,
o: &mut impl NitroOutput,
) -> anyhow::Result<JavaInstallation> {
let key = (kind.clone(), self.inner.client_meta.java_info.major_version);
if !self.params.java_installations.contains_key(&key) {
let java_params = JavaInstallParameters {
paths: self.params.paths,
update_manager: self.params.update_manager,
persistent: self.params.persistent,
req_client: self.params.req_client,
};
let java = JavaInstallation::install(
kind,
self.inner.client_meta.java_info.major_version,
java_params,
o,
)
.await
.context("Failed to install or update Java")?;
self.params
.java_installations
.insert(key.clone(), java.clone());
}
Ok(self.params.java_installations.get(&key).unwrap().clone())
}
pub async fn get_game_jar(
&self,
side: Side,
o: &mut impl NitroOutput,
) -> anyhow::Result<PathBuf> {
game_jar::get(
side,
&self.inner.client_meta,
&self.inner.version,
self.params.paths,
self.params.update_manager,
self.params.req_client,
o,
)
.await
.context("Failed to get the game JAR file")?;
Ok(crate::io::minecraft::game_jar::get_path(
side,
&self.inner.version,
None,
self.params.paths,
))
}
}
pub(crate) struct InstalledVersionInner {
version: VersionName,
version_manifest: Arc<VersionManifestAndList>,
client_meta: ClientMeta,
client_assets_and_libs: ClientAssetsAndLibraries,
}
impl InstalledVersionInner {
async fn load(
version: VersionName,
version_manifest: &Arc<VersionManifestAndList>,
params: LoadVersionParameters<'_>,
o: &mut impl NitroOutput,
) -> anyhow::Result<Self> {
o.start_process();
o.display(
MessageContents::StartProcess("Obtaining client metadata".into()),
MessageLevel::Important,
);
let client_meta = client_meta::get(
&version,
&version_manifest.manifest,
params.paths,
params.update_manager,
params.req_client,
o,
)
.await
.context("Failed to get client meta")?;
o.display(
MessageContents::Success("Client meta obtained".into()),
MessageLevel::Important,
);
o.end_process();
Ok(Self {
version,
version_manifest: version_manifest.clone(),
client_meta,
client_assets_and_libs: ClientAssetsAndLibraries::new(),
})
}
}
pub(crate) struct VersionRegistry {
versions: HashMap<VersionName, InstalledVersionInner>,
version_manifest: Later<Arc<VersionManifestAndList>>,
additional_versions: Vec<VersionEntry>,
}
impl VersionRegistry {
pub fn new() -> Self {
Self {
versions: HashMap::new(),
version_manifest: Later::Empty,
additional_versions: Vec::new(),
}
}
pub async fn get_version(
&mut self,
version: &VersionName,
params: LoadVersionParameters<'_>,
o: &mut impl NitroOutput,
) -> anyhow::Result<&mut InstalledVersionInner> {
let requested_version = MinecraftVersion::Version(version.clone());
let vm_params = LoadVersionManifestParameters {
requested_version: Some(&requested_version),
paths: params.paths,
req_client: params.req_client,
update_manager: params.update_manager,
};
self.load_version_manifest(vm_params, o)
.await
.context("Failed to get version manifest")?;
if !self.versions.contains_key(version) {
let installed_version = InstalledVersionInner::load(
version.clone(),
self.version_manifest.get(),
params,
o,
)
.await?;
self.versions.insert(version.clone(), installed_version);
}
Ok(self
.versions
.get_mut(version)
.expect("Version should exist in map"))
}
pub async fn load_version_manifest(
&mut self,
params: LoadVersionManifestParameters<'_>,
o: &mut impl NitroOutput,
) -> anyhow::Result<&Arc<VersionManifestAndList>> {
if self.version_manifest.is_empty() {
let mut manifest = version_manifest::get_with_output(
params.requested_version,
params.paths,
params.update_manager,
params.req_client,
o,
)
.await
.context("Failed to get version manifest")?;
let additional_versions = std::mem::take(&mut self.additional_versions);
add_versions(&mut manifest, additional_versions);
let combo = VersionManifestAndList::new(manifest);
self.version_manifest.fill(Arc::new(combo));
}
Ok(self.version_manifest.get())
}
pub fn add_additional_versions(&mut self, versions: Vec<VersionEntry>) {
self.additional_versions.extend(versions);
}
}
pub(crate) struct VersionParameters<'a> {
pub paths: &'a Paths,
pub req_client: &'a reqwest::Client,
pub persistent: &'a mut PersistentData,
pub update_manager: &'a mut UpdateManager,
pub users: &'a mut UserManager,
pub java_installations:
&'a mut HashMap<(JavaInstallationKind, JavaMajorVersion), JavaInstallation>,
pub censor_secrets: bool,
pub disable_hardlinks: bool,
pub branding: &'a BrandingProperties,
}
#[derive(Clone)]
pub(crate) struct LoadVersionParameters<'a> {
pub paths: &'a Paths,
pub req_client: &'a reqwest::Client,
pub update_manager: &'a UpdateManager,
}
#[derive(Clone)]
pub(crate) struct LoadVersionManifestParameters<'a> {
pub requested_version: Option<&'a MinecraftVersion>,
pub paths: &'a Paths,
pub req_client: &'a reqwest::Client,
pub update_manager: &'a UpdateManager,
}
pub(crate) struct ClientAssetsAndLibraries {
loaded: bool,
}
impl ClientAssetsAndLibraries {
pub fn new() -> Self {
Self { loaded: false }
}
pub async fn load(
&mut self,
params: ClientAssetsAndLibsParameters<'_>,
o: &mut impl NitroOutput,
) -> anyhow::Result<()> {
if self.loaded {
return Ok(());
}
let result = assets::get(
params.client_meta,
params.paths,
params.version,
¶ms.version_manifest.list,
params.update_manager,
params.req_client,
o,
)
.await
.context("Failed to get game assets")?;
params.update_manager.add_result(result);
let result = libraries::get(
¶ms.client_meta.libraries,
¶ms.paths.internal,
params.version,
params.update_manager,
params.req_client,
o,
)
.await
.context("Failed to get game libraries")?;
params.update_manager.add_result(result);
self.loaded = true;
Ok(())
}
}
pub(crate) struct ClientAssetsAndLibsParameters<'a> {
pub client_meta: &'a ClientMeta,
pub version: &'a VersionName,
pub paths: &'a Paths,
pub req_client: &'a reqwest::Client,
pub version_manifest: &'a VersionManifestAndList,
pub update_manager: &'a mut UpdateManager,
}
pub fn add_versions(manifest: &mut VersionManifest, additional_versions: Vec<VersionEntry>) {
for new_version in additional_versions {
if let Some(pos) = manifest
.versions
.iter()
.position(|x| x.id == new_version.id)
{
manifest.versions[pos] = new_version;
} else {
manifest.versions.insert(0, new_version);
}
}
}