OneLauncher-Cracked/packages/core/src/api/cluster/mod.rs
2024-08-04 05:31:41 +02:00

419 lines
10 KiB
Rust

//! **OneLauncher Cluster**
//!
//! API for creating our managed Minecraft instances, Clusters.
// TODO: (pauline) fully implement the cluster::self APIs.
use crate::proxy::send::send_cluster;
use crate::prelude::{ClusterPath, JavaVersion, PackagePath};
use crate::proxy::ClusterPayloadType;
pub use crate::store::{Cluster, JavaOptions, State};
use crate::store::{MinecraftCredentials, ProcessorChild};
use crate::utils::io::{self, IOError};
use std::collections::HashMap;
use std::future::Future;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::process::Command;
use tokio::sync::RwLock;
pub mod content;
pub mod create;
pub mod update;
/// get a cluster by its specified [`ClusterPath`].
#[tracing::instrument]
pub async fn get(path: &ClusterPath, clear: Option<bool>) -> crate::Result<Option<Cluster>> {
let state = State::get().await?;
let clusters = state.clusters.read().await;
let mut cluster = clusters.0.get(path).cloned();
if clear.unwrap_or(false) {
if let Some(cluster) = &mut cluster {
cluster.packages = HashMap::new();
}
}
Ok(cluster)
}
/// get a list of all [`Cluster`]s
#[tracing::instrument]
pub async fn list(clear: Option<bool>) -> crate::Result<Vec<Cluster>> {
let state = State::get().await?;
let clusters = state.clusters.read().await;
Ok(clusters
.0
.clone()
.into_iter()
.map(|mut it| {
if clear.unwrap_or(false) {
it.1.packages = HashMap::new();
}
it.1
})
.collect())
}
/// get a Map of [`Cluster`] group names containing lists of [`Cluster`]s
#[tracing::instrument]
pub async fn list_grouped(clear: Option<bool>) -> crate::Result<HashMap<String, Vec<Cluster>>> {
// TODO: This can 100% be made better
let list = list(clear).await?;
let mut map = HashMap::<String, Vec<Cluster>>::new();
for cluster in list {
let group = cluster
.meta
.group
.clone()
.unwrap_or(String::from("Ungrouped"));
if let Some(items) = map.get_mut(&group) {
items.push(cluster);
} else {
map.insert(group, vec![cluster]);
}
}
Ok(map)
}
/// run a Minecraft [`Cluster`] using the default credentials.
#[tracing::instrument]
pub async fn run_default(path: &ClusterPath) -> crate::Result<Arc<RwLock<ProcessorChild>>> {
let state = State::get().await?;
let default = {
let mut users = state.users.write().await;
users
.get_default()
.await?
.ok_or_else(|| anyhow::anyhow!("no default credentials found!"))?
};
run_credentials(path, &default).await
}
/// run a Minecraft [`Cluster`] using [`MinecraftCredentials`] for authentication.
/// returns an [`Arc`] pointer to [`RwLock`] to [`ProcessorChild`].
#[tracing::instrument(skip(creds))]
#[onelauncher_macros::memory]
pub async fn run(
path: &ClusterPath,
creds: Option<&MinecraftCredentials>,
) -> crate::Result<Arc<RwLock<ProcessorChild>>> {
if let Some(creds) = creds {
run_credentials(path, creds).await
} else {
run_default(path).await
}
}
/// run a Minecraft [`Cluster`] using [`MinecraftCredentials`] for authentication.
/// returns an [`Arc`] pointer to [`RwLock`] to [`ProcessorChild`].
#[tracing::instrument(skip(creds))]
#[onelauncher_macros::memory]
pub async fn run_credentials(
path: &ClusterPath,
creds: &MinecraftCredentials,
) -> crate::Result<Arc<RwLock<ProcessorChild>>> {
let state = State::get().await?;
let settings = state.settings.read().await;
let cluster = get(path, None)
.await?
.ok_or_else(|| anyhow::anyhow!("failed to run a nonexistent cluster at path {}", path))?;
let pre = &cluster
.init_hooks
.as_ref()
.unwrap_or(&settings.init_hooks)
.pre;
if let Some(hook) = pre {
// TODO: hook parameters
let mut cmd = hook.split(' ');
if let Some(command) = cmd.next() {
let full_path = path.full_path().await?;
let result = Command::new(command)
.args(cmd.collect::<Vec<&str>>())
.current_dir(&full_path)
.spawn()
.map_err(|e| IOError::with_path(e, &full_path))?
.wait()
.await
.map_err(IOError::from)?;
if !result.success() {
return Err(anyhow::anyhow!(
"non-zero exit code for pre-launch hook: {}",
result.code().unwrap_or(-1)
)
.into());
}
}
}
let java_args = cluster
.java
.as_ref()
.and_then(|it| it.custom_arguments.as_ref())
.unwrap_or(&settings.custom_java_args);
let wrapper = cluster
.init_hooks
.as_ref()
.map_or(&settings.init_hooks.wrapper, |it| &it.wrapper);
let memory = cluster.memory.unwrap_or(settings.memory);
let resolution = cluster.resolution.unwrap_or(settings.resolution);
let env_args = cluster
.java
.as_ref()
.and_then(|it| it.custom_env_arguments.as_ref())
.unwrap_or(&settings.custom_env_args);
let post = cluster
.init_hooks
.as_ref()
.unwrap_or(&settings.init_hooks)
.post
.clone();
let mut mc_options: Vec<(String, String)> = vec![];
if let Some(fullscreen) = cluster.force_fullscreen {
mc_options.push(("fullscreen".to_string(), fullscreen.to_string()));
} else if settings.force_fullscreen {
mc_options.push(("fullscreen".to_string(), "true".to_string()));
}
let process = crate::game::launch_minecraft(
&cluster,
java_args,
env_args,
&mc_options,
post,
creds,
&resolution,
&memory,
wrapper,
)
.await?;
Ok(process)
}
/// remove a specified cluster from it's [`ClusterPath`].
#[tracing::instrument]
pub async fn remove(path: &ClusterPath) -> crate::Result<()> {
let state = State::get().await?;
let mut clusters = state.clusters.write().await;
if let Some(cluster) = clusters.remove(path).await? {
send_cluster(
cluster.uuid,
path,
&cluster.meta.name,
ClusterPayloadType::Deleted,
)
.await?;
}
Ok(())
}
/// get a cluster by it's [`uuid::Uuid`]
#[tracing::instrument]
pub async fn get_by_uuid(uuid: uuid::Uuid, clear: Option<bool>) -> crate::Result<Option<Cluster>> {
let state = State::get().await?;
let clusters = state.clusters.read().await;
let mut cluster = clusters.0.values().find(|c| c.uuid == uuid).cloned();
if clear.unwrap_or(false) {
if let Some(cluster) = &mut cluster {
cluster.packages = HashMap::new();
}
}
Ok(cluster)
}
/// get a cluster's full path by it's [`ClusterPath`].
#[tracing::instrument]
pub async fn get_full_path(path: &ClusterPath) -> crate::Result<PathBuf> {
let _ = get(path, Some(true)).await?.ok_or_else(|| {
anyhow::anyhow!("failed to get the full path of cluster at path {}", path)
})?;
let full_path = io::canonicalize(path.full_path().await?)?;
Ok(full_path)
}
/// get a specific mod's full path in the filesystem by it's [`ClusterPath`] and [`PackagePath`].
#[tracing::instrument]
pub async fn get_mod_path(
cluster_path: &ClusterPath,
package_path: &PackagePath,
) -> crate::Result<PathBuf> {
if get(cluster_path, Some(true)).await?.is_some() {
let full_path = io::canonicalize(package_path.full_path(cluster_path.clone()).await?)?;
return Ok(full_path);
}
Err(anyhow::anyhow!(
"failed to get the full path of a cluster at path {}",
package_path
.full_path(cluster_path.clone())
.await?
.display()
)
.into())
}
/// edit a cluster with an async closure and it's [`ClusterPath`]
pub async fn edit<FutFn>(
path: &ClusterPath,
action: impl Fn(&mut Cluster) -> FutFn,
) -> crate::Result<()>
where
FutFn: Future<Output = crate::Result<()>>,
{
let state = State::get().await?;
let mut clusters = state.clusters.write().await;
match clusters.0.get_mut(path) {
Some(ref mut cluster) => {
action(cluster).await?;
send_cluster(
cluster.uuid,
path,
&cluster.meta.name,
ClusterPayloadType::Edited,
)
.await?;
Ok(())
}
None => Err(anyhow::anyhow!("unmanaged cluster edited at {}", path.to_string()).into()),
}
}
/// update a [`Cluster`]'s icon
pub async fn edit_icon(path: &ClusterPath, icon_path: Option<&Path>) -> crate::Result<()> {
let state = State::get().await?;
let result = if let Some(icon) = icon_path {
let bytes = io::read(icon).await?;
let mut clusters = state.clusters.write().await;
match clusters.0.get_mut(path) {
Some(ref mut cluster) => {
cluster
.set_icon_bytes(
&state.directories.caches_dir().await,
&state.io_semaphore,
bytes::Bytes::from(bytes),
&icon.to_string_lossy(),
)
.await?;
send_cluster(
cluster.uuid,
path,
&cluster.meta.name,
ClusterPayloadType::Edited,
)
.await?;
Ok(())
}
None => Err(anyhow::anyhow!(
"failed to update unmanaged cluster at {}",
path.to_string()
)
.into()),
}
} else {
edit(path, |cluster| {
cluster.meta.icon = None;
async { Ok(()) }
})
.await?;
State::sync().await?;
Ok(())
};
State::sync().await?;
result
}
/// gets the optimal java version for a given [`Cluster`].
pub async fn get_optimal_java_version(path: &ClusterPath) -> crate::Result<Option<JavaVersion>> {
let state = State::get().await?;
if let Some(cluster) = get(path, None).await? {
let metadata = state.metadata.read().await;
let minecraft_metadata = metadata
.minecraft
.to_owned()
.ok_or(anyhow::anyhow!("couldn't get minecraft metadata"))?;
let version = minecraft_metadata
.versions
.iter()
.find(|it| it.id == cluster.meta.mc_version)
.ok_or_else(|| {
anyhow::anyhow!(
"invalid or unknown Minecraft version {}",
cluster.meta.mc_version
)
})?;
let version_info = crate::game::metadata::download_version_info(
&state,
version,
cluster.meta.loader_version.as_ref(),
None,
None,
)
.await?;
let version = crate::game::java_version_from_cluster(&cluster, &version_info).await?;
Ok(version)
} else {
Err(anyhow::anyhow!(
"failed to get the java version of unmanaged cluster at {}",
path.to_string()
)
.into())
}
}
/// Try to update a [`Cluster`]'s playtime.
#[tracing::instrument]
#[onelauncher_macros::memory]
pub async fn update_playtime(path: &ClusterPath) -> crate::Result<()> {
let state = State::get().await?;
let cluster = get(path, None)
.await?
.ok_or_else(|| anyhow::anyhow!("failed to update playtime at path {}", path))?;
let recent_playtime = cluster.meta.recently_played;
/*
* todo
*/
let mut clusters = state.clusters.write().await;
if let Some(cluster) = clusters.0.get_mut(path) {
cluster.meta.overall_played += recent_playtime;
cluster.meta.recently_played = 0;
}
drop(clusters);
State::sync().await?;
Ok(())
}
/// Sanitize a user-inputted [`Cluster`] name.
pub fn sanitize_cluster_name(input: &str) -> String {
input.replace(['/', '\\', '?', '*', ':', '\'', '\"', '|', '<', '>'], "_")
}