Jre api (#66)
* basic push * actual push * JRE detection, and autosetting * removed a println, retrying CI/CD * new game version compare; preset java 7 and 8 using our jre * 1.8 mislabeled * working JRE changes * fixed bugs with JRE setup * fixed bugs with JRE setup * manual merge * prettier * fixes + jre 17 * clippy, prettier * typo * forgot to hook up a function * pr fix + comment fix * added loader_version * take 2
This commit is contained in:
parent
4b41ffbd8a
commit
34005dd2e2
137
theseus/src/api/jre.rs
Normal file
137
theseus/src/api/jre.rs
Normal file
@ -0,0 +1,137 @@
|
||||
//! Authentication flow interface
|
||||
use crate::{
|
||||
launcher::download,
|
||||
prelude::Profile,
|
||||
state::JavaGlobals,
|
||||
util::jre::{self, extract_java_majorminor_version, JavaVersion},
|
||||
State,
|
||||
};
|
||||
|
||||
pub const JAVA_8_KEY: &str = "JAVA_8";
|
||||
pub const JAVA_17_KEY: &str = "JAVA_17";
|
||||
pub const JAVA_18PLUS_KEY: &str = "JAVA_18PLUS";
|
||||
|
||||
// Autodetect JavaSettings default
|
||||
// Make a guess for what the default Java global settings should be
|
||||
pub fn autodetect_java_globals() -> crate::Result<JavaGlobals> {
|
||||
let mut java_8 = find_java8_jres()?;
|
||||
let mut java_17 = find_java17_jres()?;
|
||||
let mut java_18plus = find_java18plus_jres()?;
|
||||
|
||||
// Simply select last one found for initial guess
|
||||
let mut java_globals = JavaGlobals::new();
|
||||
if let Some(jre) = java_8.pop() {
|
||||
java_globals.insert(JAVA_8_KEY.to_string(), jre);
|
||||
}
|
||||
if let Some(jre) = java_17.pop() {
|
||||
java_globals.insert(JAVA_17_KEY.to_string(), jre);
|
||||
}
|
||||
if let Some(jre) = java_18plus.pop() {
|
||||
java_globals.insert(JAVA_18PLUS_KEY.to_string(), jre);
|
||||
}
|
||||
|
||||
Ok(java_globals)
|
||||
}
|
||||
|
||||
// Gets the optimal JRE key for the given profile, using Daedalus
|
||||
// Generally this would be used for profile_create, to get the optimal JRE key
|
||||
// this can be overwritten by the user a profile-by-profile basis
|
||||
pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result<String> {
|
||||
let state = State::get().await?;
|
||||
|
||||
// Fetch version info from stored profile game_version
|
||||
let version = state
|
||||
.metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == profile.metadata.game_version.as_ref())
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid or unknown Minecraft version: {}",
|
||||
profile.metadata.game_version
|
||||
))
|
||||
})?;
|
||||
|
||||
// Get detailed manifest info from Daedalus
|
||||
let version_info =
|
||||
download::download_version_info(&state, version, profile.metadata.loader_version.as_ref()).await?;
|
||||
let optimal_key = match version_info
|
||||
.java_version
|
||||
.as_ref()
|
||||
.map(|it| it.major_version)
|
||||
.unwrap_or(0)
|
||||
{
|
||||
0..=16 => JAVA_8_KEY.to_string(),
|
||||
17 => JAVA_17_KEY.to_string(),
|
||||
_ => JAVA_18PLUS_KEY.to_string(),
|
||||
};
|
||||
Ok(optimal_key)
|
||||
}
|
||||
|
||||
// Searches for jres on the system that are 1.18 or higher
|
||||
pub fn find_java18plus_jres() -> crate::Result<Vec<JavaVersion>> {
|
||||
let version = extract_java_majorminor_version("1.18")?;
|
||||
let jres = jre::get_all_jre()?;
|
||||
// Filter out JREs that are not 1.17 or higher
|
||||
Ok(jres
|
||||
.into_iter()
|
||||
.filter(|jre| {
|
||||
let jre_version = extract_java_majorminor_version(&jre.version);
|
||||
if let Ok(jre_version) = jre_version {
|
||||
jre_version >= version
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
// Searches for jres on the system that are 1.8 exactly
|
||||
pub fn find_java8_jres() -> crate::Result<Vec<JavaVersion>> {
|
||||
let version = extract_java_majorminor_version("1.8")?;
|
||||
let jres = jre::get_all_jre()?;
|
||||
|
||||
// Filter out JREs that are not 1.8
|
||||
Ok(jres
|
||||
.into_iter()
|
||||
.filter(|jre| {
|
||||
let jre_version = extract_java_majorminor_version(&jre.version);
|
||||
if let Ok(jre_version) = jre_version {
|
||||
jre_version == version
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
// Searches for jres on the system that are 1.17 exactly
|
||||
pub fn find_java17_jres() -> crate::Result<Vec<JavaVersion>> {
|
||||
let version = extract_java_majorminor_version("1.17")?;
|
||||
let jres = jre::get_all_jre()?;
|
||||
|
||||
// Filter out JREs that are not 1.8
|
||||
Ok(jres
|
||||
.into_iter()
|
||||
.filter(|jre| {
|
||||
let jre_version = extract_java_majorminor_version(&jre.version);
|
||||
if let Ok(jre_version) = jre_version {
|
||||
jre_version == version
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
// Get all JREs that exist on the system
|
||||
pub fn get_all_jre() -> crate::Result<Vec<JavaVersion>> {
|
||||
Ok(jre::get_all_jre()?)
|
||||
}
|
||||
|
||||
pub async fn validate_globals() -> crate::Result<bool> {
|
||||
let state = State::get().await?;
|
||||
let settings = state.settings.read().await;
|
||||
Ok(settings.java_globals.is_all_valid())
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
//! API for interacting with Theseus
|
||||
pub mod auth;
|
||||
pub mod jre;
|
||||
pub mod pack;
|
||||
pub mod process;
|
||||
pub mod profile;
|
||||
@ -18,8 +19,11 @@ pub mod prelude {
|
||||
pub use crate::{
|
||||
auth::{self, Credentials},
|
||||
data::*,
|
||||
pack, process,
|
||||
jre, pack, process,
|
||||
profile::{self, Profile},
|
||||
profile_create, settings, State,
|
||||
profile_create, settings,
|
||||
state::JavaGlobals,
|
||||
util::jre::JavaVersion,
|
||||
State,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::state::MinecraftChild;
|
||||
use crate::{launcher::download, state::MinecraftChild};
|
||||
pub use crate::{
|
||||
state::{JavaSettings, Profile},
|
||||
State,
|
||||
};
|
||||
use daedalus as d;
|
||||
use std::{
|
||||
future::Future,
|
||||
path::{Path, PathBuf},
|
||||
@ -120,8 +119,8 @@ pub async fn run(
|
||||
profile.metadata.game_version
|
||||
))
|
||||
})?;
|
||||
let version_info = d::minecraft::fetch_version_info(version).await?;
|
||||
|
||||
let version_info =
|
||||
download::download_version_info(&state, version, profile.metadata.loader_version.as_ref()).await?;
|
||||
let pre_launch_hooks =
|
||||
&profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
|
||||
for hook in pre_launch_hooks.iter() {
|
||||
@ -145,29 +144,42 @@ pub async fn run(
|
||||
}
|
||||
}
|
||||
|
||||
let java_install = match profile.java {
|
||||
let java_version = match profile.java {
|
||||
// Load profile-specific Java implementation choice
|
||||
// (This defaults to Daedalus-decided key on init, but can be changed by the user)
|
||||
Some(JavaSettings {
|
||||
install: Some(ref install),
|
||||
jre_key: Some(ref jre_key),
|
||||
..
|
||||
}) => install,
|
||||
_ => if version_info
|
||||
.java_version
|
||||
.as_ref()
|
||||
.filter(|it| it.major_version >= 16)
|
||||
.is_some()
|
||||
{
|
||||
settings.java_17_path.as_ref()
|
||||
} else {
|
||||
settings.java_8_path.as_ref()
|
||||
}) => settings.java_globals.get(jre_key),
|
||||
// Fall back to Daedalus-decided key if no profile-specific key is set
|
||||
_ => {
|
||||
match version_info
|
||||
.java_version
|
||||
.as_ref()
|
||||
.map(|it| it.major_version)
|
||||
.unwrap_or(0)
|
||||
{
|
||||
0..=16 => settings
|
||||
.java_globals
|
||||
.get(&crate::jre::JAVA_8_KEY.to_string()),
|
||||
17 => settings
|
||||
.java_globals
|
||||
.get(&crate::jre::JAVA_17_KEY.to_string()),
|
||||
_ => settings
|
||||
.java_globals
|
||||
.get(&crate::jre::JAVA_18PLUS_KEY.to_string()),
|
||||
}
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"No Java installed for version {}",
|
||||
version_info.java_version.map_or(8, |it| it.major_version),
|
||||
))
|
||||
})?,
|
||||
};
|
||||
let java_version = java_version.as_ref().ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"No Java stored for version {}",
|
||||
version_info.java_version.map_or(8, |it| it.major_version),
|
||||
))
|
||||
})?;
|
||||
|
||||
// Get the path to the Java executable from the chosen Java implementation key
|
||||
let java_install: &Path = &PathBuf::from(&java_version.path);
|
||||
if !java_install.exists() {
|
||||
return Err(crate::ErrorKind::LauncherError(format!(
|
||||
"Could not find Java install: {}",
|
||||
@ -175,7 +187,6 @@ pub async fn run(
|
||||
))
|
||||
.as_error());
|
||||
}
|
||||
|
||||
let java_args = profile
|
||||
.java
|
||||
.as_ref()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::prelude::ModLoader;
|
||||
use crate::{jre, prelude::ModLoader};
|
||||
pub use crate::{
|
||||
state::{JavaSettings, Profile},
|
||||
State,
|
||||
@ -145,6 +145,20 @@ pub async fn profile_create(
|
||||
}
|
||||
|
||||
profile.metadata.linked_project_id = linked_project_id;
|
||||
|
||||
// Attempts to find optimal JRE for the profile from the JavaGlobals
|
||||
// Finds optimal key, and see if key has been set in JavaGlobals
|
||||
let settings = state.settings.read().await;
|
||||
let optimal_version_key = jre::get_optimal_jre_key(&profile).await?;
|
||||
if settings.java_globals.get(&optimal_version_key).is_some() {
|
||||
profile.set_java_settings(Some(JavaSettings {
|
||||
jre_key: Some(optimal_version_key),
|
||||
extra_arguments: None,
|
||||
}))?;
|
||||
} else {
|
||||
println!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.");
|
||||
}
|
||||
|
||||
{
|
||||
let mut profiles = state.profiles.write().await;
|
||||
profiles.insert(profile)?;
|
||||
|
||||
@ -19,7 +19,6 @@ pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
||||
.unwrap();
|
||||
headers.insert(reqwest::header::USER_AGENT, header);
|
||||
reqwest::Client::builder()
|
||||
.timeout(time::Duration::from_secs(15))
|
||||
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
|
||||
@ -76,6 +76,12 @@ pub enum ErrorKind {
|
||||
#[error("Could not create profile: {0}")]
|
||||
ProfileCreationError(#[from] profile_create::ProfileCreationError),
|
||||
|
||||
#[error("JRE error: {0}")]
|
||||
JREError(#[from] crate::util::jre::JREError),
|
||||
|
||||
#[error("Error parsing date: {0}")]
|
||||
ChronoParseError(#[from] chrono::ParseError),
|
||||
|
||||
#[error("Zip error: {0}")]
|
||||
ZipError(#[from] async_zip::error::ZipError),
|
||||
|
||||
|
||||
@ -8,8 +8,7 @@ use tokio::process::{Child, Command};
|
||||
mod args;
|
||||
|
||||
pub mod auth;
|
||||
|
||||
mod download;
|
||||
pub mod download;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn parse_rule(rule: &d::minecraft::Rule) -> bool {
|
||||
|
||||
@ -42,7 +42,7 @@ impl AuthTask {
|
||||
|
||||
// Waits for the task to complete, and returns the credentials
|
||||
let credentials = task
|
||||
.ok_or_else(|| AuthTaskError::TaskMissing)?
|
||||
.ok_or(AuthTaskError::TaskMissing)?
|
||||
.await
|
||||
.map_err(AuthTaskError::from)??;
|
||||
|
||||
|
||||
61
theseus/src/state/java_globals.rs
Normal file
61
theseus/src/state/java_globals.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::prelude::JavaVersion;
|
||||
use crate::util::jre;
|
||||
|
||||
// All stored Java versions, chosen by the user
|
||||
// A wrapper over a Hashmap connecting key -> java version
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct JavaGlobals(HashMap<String, JavaVersion>);
|
||||
|
||||
impl JavaGlobals {
|
||||
pub fn new() -> JavaGlobals {
|
||||
JavaGlobals(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: String, java: JavaVersion) {
|
||||
self.0.insert(key, java);
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &String) {
|
||||
self.0.remove(key);
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &String) -> Option<&JavaVersion> {
|
||||
self.0.get(key)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, key: &String) -> Option<&mut JavaVersion> {
|
||||
self.0.get_mut(key)
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
// Validates that every path here is a valid Java version and that the version matches the version stored here
|
||||
// If false, when checked, the user should be prompted to reselect the Java version
|
||||
pub fn is_all_valid(&self) -> bool {
|
||||
for (_, java) in self.0.iter() {
|
||||
let jre = jre::check_java_at_filepath(
|
||||
PathBuf::from(&java.path).as_path(),
|
||||
);
|
||||
if let Some(jre) = jre {
|
||||
if jre.version != java.version {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JavaGlobals {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
//! Theseus state management system
|
||||
use crate::config::sled_config;
|
||||
use crate::jre;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{Mutex, OnceCell, RwLock, Semaphore};
|
||||
|
||||
@ -31,6 +32,9 @@ pub use self::auth_task::*;
|
||||
mod tags;
|
||||
pub use self::tags::*;
|
||||
|
||||
mod java_globals;
|
||||
pub use self::java_globals::*;
|
||||
|
||||
// Global state
|
||||
static LAUNCHER_STATE: OnceCell<Arc<State>> = OnceCell::const_new();
|
||||
pub struct State {
|
||||
@ -74,7 +78,7 @@ impl State {
|
||||
.open()?;
|
||||
|
||||
// Settings
|
||||
let settings =
|
||||
let mut settings =
|
||||
Settings::init(&directories.settings_file()).await?;
|
||||
|
||||
// Loose initializations
|
||||
@ -101,6 +105,12 @@ impl State {
|
||||
);
|
||||
};
|
||||
|
||||
// On launcher initialization, if global java variables are unset, try to find and set them
|
||||
// (they are required for the game to launch)
|
||||
if settings.java_globals.count() == 0 {
|
||||
settings.java_globals = jre::autodetect_java_globals()?;
|
||||
}
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
database,
|
||||
directories,
|
||||
|
||||
@ -83,7 +83,7 @@ impl std::fmt::Display for ModLoader {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct JavaSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub install: Option<PathBuf>,
|
||||
pub jre_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extra_arguments: Option<Vec<String>>,
|
||||
}
|
||||
@ -146,6 +146,15 @@ impl Profile {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn set_java_settings(
|
||||
&mut self,
|
||||
java: Option<JavaSettings>,
|
||||
) -> crate::Result<()> {
|
||||
self.java = java;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_profile_project_paths(&self) -> crate::Result<Vec<PathBuf>> {
|
||||
let mut files = Vec::new();
|
||||
let mut read_paths = |path: &str| {
|
||||
|
||||
@ -322,7 +322,9 @@ pub async fn infer_data_from_files(
|
||||
title: Some(
|
||||
pack.display_name
|
||||
.clone()
|
||||
.unwrap_or(pack.mod_id.clone()),
|
||||
.unwrap_or_else(|| {
|
||||
pack.mod_id.clone()
|
||||
}),
|
||||
),
|
||||
description: pack.description.clone(),
|
||||
authors: pack
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
//! Theseus settings file
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{collections::HashSet, path::Path};
|
||||
use tokio::fs;
|
||||
|
||||
use super::JavaGlobals;
|
||||
|
||||
// TODO: convert to semver?
|
||||
const CURRENT_FORMAT_VERSION: u32 = 1;
|
||||
|
||||
@ -18,8 +17,7 @@ pub struct Settings {
|
||||
pub game_resolution: WindowSize,
|
||||
pub custom_java_args: Vec<String>,
|
||||
pub custom_env_args: Vec<(String, String)>,
|
||||
pub java_8_path: Option<PathBuf>,
|
||||
pub java_17_path: Option<PathBuf>,
|
||||
pub java_globals: JavaGlobals,
|
||||
pub default_user: Option<uuid::Uuid>,
|
||||
pub hooks: Hooks,
|
||||
pub max_concurrent_downloads: usize,
|
||||
@ -33,8 +31,7 @@ impl Default for Settings {
|
||||
game_resolution: WindowSize::default(),
|
||||
custom_java_args: Vec::new(),
|
||||
custom_env_args: Vec::new(),
|
||||
java_8_path: None,
|
||||
java_17_path: None,
|
||||
java_globals: JavaGlobals::new(),
|
||||
default_user: None,
|
||||
hooks: Hooks::default(),
|
||||
max_concurrent_downloads: 64,
|
||||
|
||||
@ -140,7 +140,6 @@ impl Tags {
|
||||
let licenses = self.fetch_tag("license");
|
||||
let donation_platforms = self.fetch_tag("donation_platform");
|
||||
let report_types = self.fetch_tag("report_type");
|
||||
|
||||
let (
|
||||
categories,
|
||||
loaders,
|
||||
@ -241,14 +240,6 @@ pub struct Loader {
|
||||
pub supported_project_types: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct GameVersion {
|
||||
pub version: String,
|
||||
pub version_type: String,
|
||||
pub date: String,
|
||||
pub major: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct License {
|
||||
pub short: String,
|
||||
@ -260,3 +251,11 @@ pub struct DonationPlatform {
|
||||
pub short: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Decode, Encode, Serialize, Deserialize)]
|
||||
pub struct GameVersion {
|
||||
pub version: String,
|
||||
pub version_type: String,
|
||||
pub date: String,
|
||||
pub major: bool,
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
use dunce::canonicalize;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{collections::HashSet, path::Path};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use winreg::{
|
||||
@ -12,7 +13,7 @@ use winreg::{
|
||||
RegKey,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Clone)]
|
||||
pub struct JavaVersion {
|
||||
pub path: String,
|
||||
pub version: String,
|
||||
@ -35,11 +36,8 @@ pub fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue };
|
||||
for java_subpath in java_subpaths {
|
||||
let path = java_subpath?.path();
|
||||
if let Some(j) =
|
||||
check_java_at_filepath(PathBuf::from(path).join("bin"))
|
||||
{
|
||||
if let Some(j) = check_java_at_filepath(&path.join("bin")) {
|
||||
jres.insert(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,10 +88,9 @@ pub fn get_all_jre_winregkey(
|
||||
subkey.get_value(subkey_value);
|
||||
let Ok(path) = path else {continue};
|
||||
if let Some(j) =
|
||||
check_java_at_filepath(PathBuf::from(path).join("bin"))
|
||||
check_java_at_filepath(&PathBuf::from(path).join("bin"))
|
||||
{
|
||||
jres.insert(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,12 +115,23 @@ pub fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
r"/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands",
|
||||
];
|
||||
for path in java_paths {
|
||||
if let Some(j) = check_java_at_filepath(PathBuf::from(path).join("bin"))
|
||||
if let Some(j) =
|
||||
check_java_at_filepath(&PathBuf::from(path).join("bin"))
|
||||
{
|
||||
jres.insert(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Iterate over JavaVirtualMachines/(something)/Contents/Home/bin
|
||||
let base_path = PathBuf::from("/Library/Java/JavaVirtualMachines/");
|
||||
if base_path.is_dir() {
|
||||
for entry in std::fs::read_dir(base_path)? {
|
||||
let entry = entry?.path().join("Contents/Home/bin");
|
||||
if let Some(j) = check_java_at_filepath(entry.as_path()) {
|
||||
jres.insert(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(jres.into_iter().collect())
|
||||
}
|
||||
|
||||
@ -149,29 +157,29 @@ pub fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
];
|
||||
for path in java_paths {
|
||||
if let Some(j) =
|
||||
check_java_at_filepath(PathBuf::from(path).join("jre").join("bin"))
|
||||
check_java_at_filepath(&PathBuf::from(path).join("jre").join("bin"))
|
||||
{
|
||||
jres.insert(j);
|
||||
break;
|
||||
}
|
||||
if let Some(j) = check_java_at_filepath(PathBuf::from(path).join("bin"))
|
||||
if let Some(j) =
|
||||
check_java_at_filepath(&PathBuf::from(path).join("bin"))
|
||||
{
|
||||
jres.insert(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(jres.into_iter().collect())
|
||||
}
|
||||
|
||||
// Gets all JREs from the PATH env variable
|
||||
#[tracing::instrument]
|
||||
pub fn get_all_jre_path() -> Result<HashSet<JavaVersion>, JREError> {
|
||||
fn get_all_jre_path() -> Result<HashSet<JavaVersion>, JREError> {
|
||||
// Iterate over values in PATH variable, where accessible JREs are referenced
|
||||
let paths = env::var("PATH")?;
|
||||
let paths = env::split_paths(&paths);
|
||||
|
||||
let mut jres = HashSet::new();
|
||||
for path in paths {
|
||||
if let Some(j) = check_java_at_filepath(path) {
|
||||
if let Some(j) = check_java_at_filepath(&path) {
|
||||
jres.insert(j);
|
||||
}
|
||||
}
|
||||
@ -189,14 +197,19 @@ const JAVA_BIN: &str = "java";
|
||||
// For example filepath 'path', attempt to resolve it and get a Java version at this path
|
||||
// If no such path exists, or no such valid java at this path exists, returns None
|
||||
#[tracing::instrument]
|
||||
pub fn check_java_at_filepath(path: PathBuf) -> Option<JavaVersion> {
|
||||
pub fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
|
||||
// Attempt to canonicalize the potential java filepath
|
||||
// If it fails, this path does not exist and None is returned (no Java here)
|
||||
let Ok(path) = canonicalize(path) else { return None };
|
||||
let Some(path_str) = path.to_str() else { return None };
|
||||
|
||||
// Checks for existence of Java at this filepath
|
||||
let java = path.join(JAVA_BIN);
|
||||
// Adds JAVA_BIN to the end of the path if it is not already there
|
||||
let java = if path.file_name()?.to_str()? != JAVA_BIN {
|
||||
path.join(JAVA_BIN)
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
if !java.exists() {
|
||||
return None;
|
||||
};
|
||||
@ -216,7 +229,8 @@ pub fn check_java_at_filepath(path: PathBuf) -> Option<JavaVersion> {
|
||||
// Extract version info from it
|
||||
if let Some(captures) = JAVA_VERSION_CAPTURE.captures(&stderr) {
|
||||
if let Some(version) = captures.get(1) {
|
||||
let path = path_str.to_string();
|
||||
let Some(path) = java.to_str() else { return None };
|
||||
let path = path.to_string();
|
||||
return Some(JavaVersion {
|
||||
path,
|
||||
version: version.as_str().to_string(),
|
||||
@ -226,6 +240,38 @@ pub fn check_java_at_filepath(path: PathBuf) -> Option<JavaVersion> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract major/minor version from a java version string
|
||||
/// Gets the minor version or an error, and assumes 1 for major version if it could not find
|
||||
/// "1.8.0_361" -> (1, 8)
|
||||
/// "20" -> (1, 20)
|
||||
pub fn extract_java_majorminor_version(
|
||||
version: &str,
|
||||
) -> Result<(u32, u32), JREError> {
|
||||
let mut split = version.split('.');
|
||||
let major_opt = split.next();
|
||||
|
||||
let mut major;
|
||||
// Try minor. If doesn't exist, in format like "20" so use major
|
||||
let mut minor = if let Some(minor) = split.next() {
|
||||
major = major_opt.unwrap_or("1").parse::<u32>()?;
|
||||
minor.parse::<u32>()?
|
||||
} else {
|
||||
// Formatted like "20", only one value means that is minor version
|
||||
major = 1;
|
||||
major_opt
|
||||
.ok_or_else(|| JREError::InvalidJREVersion(version.to_string()))?
|
||||
.parse::<u32>()?
|
||||
};
|
||||
|
||||
// Java start should always be 1. If more than 1, it is formatted like "17.0.1.2" and starts with minor version
|
||||
if major > 1 {
|
||||
minor = major;
|
||||
major = 1;
|
||||
}
|
||||
|
||||
Ok((major, minor))
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum JREError {
|
||||
#[error("Command error : {0}")]
|
||||
@ -233,4 +279,32 @@ pub enum JREError {
|
||||
|
||||
#[error("Env error: {0}")]
|
||||
EnvError(#[from] env::VarError),
|
||||
|
||||
#[error("No JRE found for required version: {0}")]
|
||||
NoJREFound(String),
|
||||
|
||||
#[error("Invalid JRE version string: {0}")]
|
||||
InvalidJREVersion(String),
|
||||
|
||||
#[error("Parsing error: {0}")]
|
||||
ParseError(#[from] std::num::ParseIntError),
|
||||
|
||||
#[error("No stored tag for Minecraft Version {0}")]
|
||||
NoMinecraftVersionFound(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::extract_java_majorminor_version;
|
||||
|
||||
#[test]
|
||||
pub fn java_version_parsing() {
|
||||
assert_eq!(extract_java_majorminor_version("1.8").unwrap(), (1, 8));
|
||||
assert_eq!(extract_java_majorminor_version("17.0.6").unwrap(), (1, 17));
|
||||
assert_eq!(extract_java_majorminor_version("20").unwrap(), (1, 20));
|
||||
assert_eq!(
|
||||
extract_java_majorminor_version("1.8.0_361").unwrap(),
|
||||
(1, 8)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
62
theseus_gui/src-tauri/src/api/jre.rs
Normal file
62
theseus_gui/src-tauri/src/api/jre.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::api::Result;
|
||||
use theseus::prelude::JavaVersion;
|
||||
use theseus::prelude::*;
|
||||
|
||||
use super::TheseusSerializableError;
|
||||
|
||||
/// Get all JREs that exist on the system
|
||||
#[tauri::command]
|
||||
pub async fn jre_get_all_jre() -> Result<Vec<JavaVersion>> {
|
||||
Ok(jre::get_all_jre()?)
|
||||
}
|
||||
|
||||
// Finds the isntallation of Java 7, if it exists
|
||||
#[tauri::command]
|
||||
pub async fn jre_find_jre_8_jres() -> Result<Vec<JavaVersion>> {
|
||||
Ok(jre::find_java8_jres()?)
|
||||
}
|
||||
|
||||
// finds the installation of Java 17, if it exists
|
||||
#[tauri::command]
|
||||
pub async fn jre_find_jre_17_jres() -> Result<Vec<JavaVersion>> {
|
||||
Ok(jre::find_java17_jres()?)
|
||||
}
|
||||
|
||||
// Finds the highest version of Java 18+, if it exists
|
||||
#[tauri::command]
|
||||
pub async fn jre_find_jre_18plus_jres() -> Result<Vec<JavaVersion>> {
|
||||
Ok(jre::find_java18plus_jres()?)
|
||||
}
|
||||
|
||||
// Autodetect Java globals, by searching the users computer.
|
||||
// Returns a *NEW* JavaGlobals that can be put into Settings
|
||||
#[tauri::command]
|
||||
pub async fn jre_autodetect_java_globals() -> Result<JavaGlobals> {
|
||||
Ok(jre::autodetect_java_globals()?)
|
||||
}
|
||||
|
||||
// Gets key for the optimal JRE to use, for a given profile Profile
|
||||
// The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists)
|
||||
#[tauri::command]
|
||||
pub async fn jre_get_optimal_jre_key(profile: Profile) -> Result<String> {
|
||||
Ok(jre::get_optimal_jre_key(&profile).await?)
|
||||
}
|
||||
|
||||
// Gets key for the optimal JRE to use, for a given profile path
|
||||
// The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists)
|
||||
#[tauri::command]
|
||||
pub async fn jre_get_optimal_jre_key_by_path(path: &Path) -> Result<String> {
|
||||
let profile = profile::get(path).await?.ok_or_else(|| {
|
||||
TheseusSerializableError::NoProfileFound(path.display().to_string())
|
||||
})?;
|
||||
Ok(jre::get_optimal_jre_key(&profile).await?)
|
||||
}
|
||||
|
||||
// Validates java globals, by checking if the paths exist
|
||||
// If false, recommend to direct them to reassign, or to re-guess
|
||||
#[tauri::command]
|
||||
pub async fn jre_validate_globals() -> Result<bool> {
|
||||
Ok(jre::validate_globals().await?)
|
||||
}
|
||||
@ -3,6 +3,7 @@ use serde::{Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod auth;
|
||||
pub mod jre;
|
||||
|
||||
pub mod pack;
|
||||
pub mod process;
|
||||
@ -29,6 +30,9 @@ pub enum TheseusSerializableError {
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("No profile found at {0}")]
|
||||
NoProfileFound(String),
|
||||
}
|
||||
|
||||
// Generic implementation of From<T> for ErrorTypeA
|
||||
@ -69,4 +73,5 @@ macro_rules! impl_serialize {
|
||||
impl_serialize! {
|
||||
Theseus,
|
||||
IO,
|
||||
NoProfileFound,
|
||||
}
|
||||
|
||||
@ -43,6 +43,14 @@ fn main() {
|
||||
api::tags::tags_get_tag_bundle,
|
||||
api::settings::settings_get,
|
||||
api::settings::settings_set,
|
||||
api::jre::jre_get_all_jre,
|
||||
api::jre::jre_autodetect_java_globals,
|
||||
api::jre::jre_find_jre_18plus_jres,
|
||||
api::jre::jre_find_jre_17_jres,
|
||||
api::jre::jre_find_jre_8_jres,
|
||||
api::jre::jre_validate_globals,
|
||||
api::jre::jre_get_optimal_jre_key,
|
||||
api::jre::jre_get_optimal_jre_key_by_path,
|
||||
api::process::process_get_all_pids,
|
||||
api::process::process_get_all_running_pids,
|
||||
api::process::process_get_pids_by_profile_path,
|
||||
|
||||
63
theseus_gui/src/helpers/jre.js
Normal file
63
theseus_gui/src/helpers/jre.js
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
/*
|
||||
|
||||
JavaVersion {
|
||||
path: Path
|
||||
version: String
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/// Get all JREs that exist on the system
|
||||
// Returns an array of JavaVersion
|
||||
export async function get_all_jre() {
|
||||
return await invoke('jre_get_all_jre')
|
||||
}
|
||||
|
||||
// Finds all the installation of Java 7, if it exists
|
||||
// Returns [JavaVersion]
|
||||
export async function find_jre_8_jres() {
|
||||
return await invoke('jre_find_jre_8_jres')
|
||||
}
|
||||
|
||||
// Finds the installation of Java 17, if it exists
|
||||
// Returns [JavaVersion]
|
||||
export async function find_jre_17_jres() {
|
||||
return await invoke('jre_find_jre_17_jres')
|
||||
}
|
||||
|
||||
// Finds the highest version of Java 18+, if it exists
|
||||
// Returns [JavaVersion]
|
||||
export async function find_jre_18plus_jres() {
|
||||
return await invoke('jre_find_jre_18plus_jres')
|
||||
}
|
||||
|
||||
// Validates globals. Recommend directing the user to reassigned the globals if this returns false
|
||||
// Returns [JavaVersion]
|
||||
export async function validate_globals() {
|
||||
return await invoke('jre_validate_globals')
|
||||
}
|
||||
|
||||
// Gets key for the optimal JRE to use, for a given profile path
|
||||
// The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists)
|
||||
export async function get_optimal_jre_key_by_path(path) {
|
||||
return await invoke('jre_get_optimal_jre_key_by_path', { path })
|
||||
}
|
||||
|
||||
// Gets key for the optimal JRE to use, for a given profile
|
||||
// The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists)
|
||||
export async function get_optimal_jre_ke(path) {
|
||||
return await invoke('jre_get_optimal_jre_key', { path })
|
||||
}
|
||||
|
||||
// Autodetect Java globals, by searching the users computer.
|
||||
// Returns a *NEW* JavaGlobals that can be put into Settings
|
||||
export async function autodetect_java_globals(path) {
|
||||
return await invoke('jre_autodetect_java_globals', { path })
|
||||
}
|
||||
@ -13,8 +13,7 @@ Settings {
|
||||
"game_resolution": [int int],
|
||||
"custom_java_args": [String ...],
|
||||
"custom_env_args" : [(string, string) ... ]>,
|
||||
"java_8_path": Path (can be null),
|
||||
"java_17_path": Path (can be null),
|
||||
"java_globals": Hash of (string, Path),
|
||||
"default_user": Uuid string (can be null),
|
||||
"hooks": Hooks,
|
||||
"max_concurrent_downloads": uint,
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
)]
|
||||
|
||||
use dunce::canonicalize;
|
||||
use std::path::Path;
|
||||
use theseus::prelude::*;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
@ -32,7 +31,7 @@ async fn main() -> theseus::Result<()> {
|
||||
|
||||
// Initialize state
|
||||
let st = State::get().await?;
|
||||
st.settings.write().await.max_concurrent_downloads = 100;
|
||||
st.settings.write().await.max_concurrent_downloads = 1;
|
||||
|
||||
// Clear profiles
|
||||
println!("Clearing profiles.");
|
||||
@ -52,13 +51,14 @@ async fn main() -> theseus::Result<()> {
|
||||
|
||||
// async closure for testing any desired edits
|
||||
// (ie: changing the java runtime of an added profile)
|
||||
println!("Editing.");
|
||||
profile::edit(&profile_path, |profile| {
|
||||
// Eg: Java. TODO: hook up to jre.rs class to pick optimal java
|
||||
profile.java = Some(JavaSettings {
|
||||
install: Some(Path::new("/usr/bin/java").to_path_buf()),
|
||||
extra_arguments: None,
|
||||
});
|
||||
// println!("Editing.");
|
||||
profile::edit(&profile_path, |_profile| {
|
||||
// Eg: Java- this would let you change the java runtime of the profile instead of using the default
|
||||
// use theseus::prelude::jre::JAVA__KEY;
|
||||
// profile.java = Some(JavaSettings {
|
||||
// jre_key: Some(JAVA_17_KEY.to_string()),
|
||||
// extra_arguments: None,
|
||||
// });
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
@ -107,8 +107,8 @@ async fn main() -> theseus::Result<()> {
|
||||
println!("Minecraft PID: {}", pid);
|
||||
|
||||
// Wait 5 seconds
|
||||
println!("Waiting 10 seconds to gather logs...");
|
||||
sleep(Duration::from_secs(10)).await;
|
||||
println!("Waiting 20 seconds to gather logs...");
|
||||
sleep(Duration::from_secs(20)).await;
|
||||
let stdout = process::get_stdout_by_pid(pid).await?;
|
||||
println!("Logs after 5sec <<< {stdout} >>> end stdout");
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user