You've already forked AstralRinth
forked from didirus/AstralRinth
fullscreen (#360)
* fullscreen * improvements, and error catching * yarn prettier * discord rpc * fixed uninitialized options.txt * working discord version * incorrect boolean * change * merge issue; regex solution * fixed error * multi line mode * moved \n to start
This commit is contained in:
@@ -27,7 +27,7 @@ pub mod prelude {
|
||||
jre, metadata, pack, process,
|
||||
profile::{self, create, Profile},
|
||||
settings,
|
||||
state::JavaGlobals,
|
||||
state::{JavaGlobals, SetFullscreen},
|
||||
state::{ProfilePathId, ProjectPathId},
|
||||
util::{
|
||||
io::{canonicalize, IOError},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
event::LoadingBarId,
|
||||
@@ -106,7 +105,7 @@ pub struct ATLauncherMod {
|
||||
// Check if folder has a instance.json that parses
|
||||
pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool {
|
||||
let instance: String =
|
||||
fs::read_to_string(&instance_folder.join("instance.json"))
|
||||
io::read_to_string(&instance_folder.join("instance.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let instance: Result<ATInstance, serde_json::Error> =
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
@@ -42,7 +41,7 @@ pub struct MinecraftInstance {
|
||||
// Check if folder has a minecraftinstance.json that parses
|
||||
pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool {
|
||||
let minecraftinstance: String =
|
||||
fs::read_to_string(&instance_folder.join("minecraftinstance.json"))
|
||||
io::read_to_string(&instance_folder.join("minecraftinstance.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let minecraftinstance: Result<MinecraftInstance, serde_json::Error> =
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
prelude::{ModLoader, ProfilePathId},
|
||||
@@ -32,7 +31,7 @@ pub struct GDLauncherLoader {
|
||||
// Check if folder has a config.json that parses
|
||||
pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool {
|
||||
let config: String =
|
||||
fs::read_to_string(&instance_folder.join("config.json"))
|
||||
io::read_to_string(&instance_folder.join("config.json"))
|
||||
.await
|
||||
.unwrap_or("".to_string());
|
||||
let config: Result<GDLauncherConfig, serde_json::Error> =
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
pack::{
|
||||
@@ -126,7 +125,7 @@ pub async fn is_valid_mmc(instance_folder: PathBuf) -> bool {
|
||||
let instance_cfg = instance_folder.join("instance.cfg");
|
||||
let mmc_pack = instance_folder.join("mmc-pack.json");
|
||||
|
||||
let mmc_pack = match fs::read_to_string(&mmc_pack).await {
|
||||
let mmc_pack = match io::read_to_string(&mmc_pack).await {
|
||||
Ok(mmc_pack) => mmc_pack,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
@@ -66,45 +66,46 @@ pub async fn get_importable_instances(
|
||||
}
|
||||
|
||||
// Import an instance from a launcher type and base path
|
||||
// Note: this *deletes* the submitted empty profile
|
||||
#[theseus_macros::debug_pin]
|
||||
#[tracing::instrument]
|
||||
pub async fn import_instance(
|
||||
profile_path: ProfilePathId,
|
||||
profile_path: ProfilePathId, // This should be a blank profile
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
instance_folder: String,
|
||||
) -> crate::Result<()> {
|
||||
tracing::debug!("Importing instance from {instance_folder}");
|
||||
match launcher_type {
|
||||
let res = match launcher_type {
|
||||
ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => {
|
||||
mmc::import_mmc(
|
||||
base_path, // path to base mmc folder
|
||||
instance_folder, // instance folder in mmc_base_path
|
||||
profile_path, // path to profile
|
||||
base_path, // path to base mmc folder
|
||||
instance_folder, // instance folder in mmc_base_path
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::ATLauncher => {
|
||||
atlauncher::import_atlauncher(
|
||||
base_path, // path to atlauncher folder
|
||||
instance_folder, // instance folder in atlauncher
|
||||
profile_path, // path to profile
|
||||
base_path, // path to atlauncher folder
|
||||
instance_folder, // instance folder in atlauncher
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::GDLauncher => {
|
||||
gdlauncher::import_gdlauncher(
|
||||
base_path.join("instances").join(instance_folder), // path to gdlauncher folder
|
||||
profile_path, // path to profile
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::Curseforge => {
|
||||
curseforge::import_curseforge(
|
||||
base_path.join("Instances").join(instance_folder), // path to curseforge folder
|
||||
profile_path, // path to profile
|
||||
profile_path.clone(), // path to profile
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
ImportLauncherType::Unknown => {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
@@ -112,6 +113,16 @@ pub async fn import_instance(
|
||||
)
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
// If import failed, delete the profile
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
tracing::warn!("Import failed: {:?}", e);
|
||||
let _ = crate::api::profile::remove(&profile_path).await;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Check existing managed packs for potential updates
|
||||
|
||||
@@ -121,7 +121,7 @@ pub async fn wait_for_by_uuid(uuid: &Uuid) -> crate::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Kill a running child process directly, and wait for it to be killed
|
||||
// Kill a running child process directly
|
||||
#[tracing::instrument(skip(running))]
|
||||
pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> {
|
||||
running
|
||||
@@ -131,7 +131,7 @@ pub async fn kill(running: &mut MinecraftChild) -> crate::Result<()> {
|
||||
.kill()
|
||||
.await
|
||||
.map_err(IOError::from)?;
|
||||
wait_for(running).await
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Await on the completion of a child process directly
|
||||
|
||||
@@ -7,7 +7,9 @@ use crate::event::LoadingBarType;
|
||||
use crate::pack::install_from::{
|
||||
EnvType, PackDependency, PackFile, PackFileHash, PackFormat,
|
||||
};
|
||||
use crate::prelude::{JavaVersion, ProfilePathId, ProjectPathId};
|
||||
use crate::prelude::{
|
||||
JavaVersion, ProfilePathId, ProjectPathId, SetFullscreen,
|
||||
};
|
||||
use crate::state::ProjectMetadata;
|
||||
|
||||
use crate::util::io::{self, IOError};
|
||||
@@ -838,9 +840,23 @@ pub async fn run_credentials(
|
||||
None
|
||||
};
|
||||
|
||||
// Any options.txt settings that we want set, add here
|
||||
let mut mc_set_options: Vec<(String, String)> = Vec::new();
|
||||
match profile.force_fullscreen {
|
||||
SetFullscreen::LeaveUnset => {}
|
||||
SetFullscreen::SetWindowed => {
|
||||
mc_set_options
|
||||
.push(("fullscreen".to_string(), "false".to_string()));
|
||||
}
|
||||
SetFullscreen::SetFullscreen => {
|
||||
mc_set_options.push(("fullscreen".to_string(), "true".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let mc_process = crate::launcher::launch_minecraft(
|
||||
java_args,
|
||||
env_args,
|
||||
&mc_set_options,
|
||||
wrapper,
|
||||
&memory,
|
||||
&resolution,
|
||||
|
||||
@@ -49,6 +49,9 @@ pub enum ErrorKind {
|
||||
#[error("Incorrect Sha1 hash for download: {0} != {1}")]
|
||||
HashError(String, String),
|
||||
|
||||
#[error("Regex error: {0}")]
|
||||
RegexError(#[from] regex::Error),
|
||||
|
||||
#[error("Paths stored in the database need to be valid UTF-8: {0}")]
|
||||
UTFError(std::path::PathBuf),
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
use crate::event::emit::{emit_loading, init_or_edit_loading};
|
||||
use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::jre::{self, JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY};
|
||||
use crate::launcher::io::IOError;
|
||||
use crate::prelude::JavaVersion;
|
||||
use crate::state::ProfileInstallStage;
|
||||
use crate::util::io;
|
||||
@@ -164,6 +165,16 @@ pub async fn install_minecraft(
|
||||
)
|
||||
})?;
|
||||
|
||||
// Test jre version
|
||||
let java_version = jre::check_jre(java_version.path.clone().into())
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Java path invalid or non-functional: {}",
|
||||
java_version.path
|
||||
))
|
||||
})?;
|
||||
|
||||
// Download minecraft (5-90)
|
||||
download::download_minecraft(
|
||||
&state,
|
||||
@@ -246,6 +257,7 @@ pub async fn install_minecraft(
|
||||
)?)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| IOError::with_path(e, &java_version.path))
|
||||
.map_err(|err| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Error running processor: {err}",
|
||||
@@ -291,6 +303,7 @@ pub async fn install_minecraft(
|
||||
pub async fn launch_minecraft(
|
||||
java_args: &[String],
|
||||
env_args: &[(String, String)],
|
||||
mc_set_options: &[(String, String)],
|
||||
wrapper: &Option<String>,
|
||||
memory: &st::MemorySettings,
|
||||
resolution: &st::WindowSize,
|
||||
@@ -440,6 +453,33 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
command.envs(env_args);
|
||||
|
||||
// Overwrites the minecraft options.txt file with the settings from the profile
|
||||
// Uses 'a:b' syntax which is not quite yaml
|
||||
use regex::Regex;
|
||||
|
||||
let options_path = instance_path.join("options.txt");
|
||||
let mut options_string = String::new();
|
||||
|
||||
if options_path.exists() {
|
||||
options_string = io::read_to_string(&options_path).await?;
|
||||
}
|
||||
|
||||
for (key, value) in mc_set_options {
|
||||
let re = Regex::new(&format!(r"(?m)^{}:.*$", regex::escape(key)))?;
|
||||
// check if the regex exists in the file
|
||||
if !re.is_match(&options_string) {
|
||||
// The key was not found in the file, so append it
|
||||
options_string.push_str(&format!("\n{}:{}", key, value));
|
||||
} else {
|
||||
let replaced_string = re
|
||||
.replace_all(&options_string, &format!("{}:{}", key, value))
|
||||
.to_string();
|
||||
options_string = replaced_string;
|
||||
}
|
||||
}
|
||||
|
||||
io::write(&options_path, options_string).await?;
|
||||
|
||||
// Get Modrinth logs directories
|
||||
let datetime_string =
|
||||
chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
|
||||
@@ -501,6 +541,14 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Add game played to discord rich presence
|
||||
let _ = state
|
||||
.discord_rpc
|
||||
.set_activity(&format!("Playing {}", profile.metadata.name), true)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Create Minecraft child by inserting it into the state
|
||||
// This also spawns the process and prepares the subsequent processes
|
||||
let mut state_children = state.children.write().await;
|
||||
|
||||
@@ -145,6 +145,13 @@ impl Children {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Clear game played for Discord RPC
|
||||
// May have other active processes, so we clear to the next running process
|
||||
let state = crate::State::get().await?;
|
||||
let _ = state.discord_rpc.clear_to_default(true).await;
|
||||
}
|
||||
|
||||
// If in tauri, window should show itself again after process exists if it was hidden
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
|
||||
167
theseus/src/state/discord.rs
Normal file
167
theseus/src/state/discord.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use discord_rich_presence::{
|
||||
activity::{Activity, Assets},
|
||||
DiscordIpc, DiscordIpcClient,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::State;
|
||||
|
||||
pub struct DiscordGuard {
|
||||
client: Arc<RwLock<DiscordIpcClient>>,
|
||||
connected: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DiscordGuard {
|
||||
/// Initialize discord IPC client, and attempt to connect to it
|
||||
/// If it fails, it will still return a DiscordGuard, but the client will be unconnected
|
||||
pub async fn init() -> crate::Result<DiscordGuard> {
|
||||
let mut dipc =
|
||||
DiscordIpcClient::new("1084015525241311292").map_err(|e| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not create Discord client {}",
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
let res = dipc.connect(); // Do not need to connect to Discord to use app
|
||||
let connected = if res.is_ok() {
|
||||
Arc::new(AtomicBool::new(true))
|
||||
} else {
|
||||
Arc::new(AtomicBool::new(false))
|
||||
};
|
||||
|
||||
let client = Arc::new(RwLock::new(dipc));
|
||||
Ok(DiscordGuard { client, connected })
|
||||
}
|
||||
|
||||
/// If the client failed connecting during init(), this will check for connection and attempt to reconnect
|
||||
/// This MUST be called first in any client method that requires a connection, because those can PANIC if the client is not connected
|
||||
/// (No connection is different than a failed connection, the latter will not panic and can be retried)
|
||||
pub async fn retry_if_not_ready(&self) -> bool {
|
||||
let mut client = self.client.write().await;
|
||||
if !self.connected.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
if client.connect().is_ok() {
|
||||
self.connected
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Set the activity to the given message
|
||||
pub async fn set_activity(
|
||||
&self,
|
||||
msg: &str,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
// Attempt to connect if not connected. Do not continue if it fails, as the client.set_activity can panic if it never was connected
|
||||
if !self.retry_if_not_ready().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let activity = Activity::new().state(msg).assets(
|
||||
Assets::new()
|
||||
.large_image("modrinth_simple")
|
||||
.large_text("Modrinth Logo"),
|
||||
);
|
||||
|
||||
// Attempt to set the activity
|
||||
// If the existing connection fails, attempt to reconnect and try again
|
||||
let mut client: tokio::sync::RwLockWriteGuard<'_, DiscordIpcClient> =
|
||||
self.client.write().await;
|
||||
let res = client.set_activity(activity.clone());
|
||||
let could_not_set_err = |e: Box<dyn serde::ser::StdError>| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not update Discord activity {}",
|
||||
e,
|
||||
))
|
||||
};
|
||||
|
||||
if reconnect_if_fail {
|
||||
if let Err(_e) = res {
|
||||
client.reconnect().map_err(|e| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not reconnect to Discord IPC {}",
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
return Ok(client
|
||||
.set_activity(activity)
|
||||
.map_err(could_not_set_err)?); // try again, but don't reconnect if it fails again
|
||||
}
|
||||
} else {
|
||||
res.map_err(could_not_set_err)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear the activity
|
||||
pub async fn clear_activity(
|
||||
&self,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
// Attempt to connect if not connected. Do not continue if it fails, as the client.clear_activity can panic if it never was connected
|
||||
if !self.retry_if_not_ready().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Attempt to clear the activity
|
||||
// If the existing connection fails, attempt to reconnect and try again
|
||||
let mut client = self.client.write().await;
|
||||
let res = client.clear_activity();
|
||||
|
||||
let could_not_clear_err = |e: Box<dyn serde::ser::StdError>| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not clear Discord activity {}",
|
||||
e,
|
||||
))
|
||||
};
|
||||
|
||||
if reconnect_if_fail {
|
||||
if res.is_err() {
|
||||
client.reconnect().map_err(|e| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Could not reconnect to Discord IPC {}",
|
||||
e,
|
||||
))
|
||||
})?;
|
||||
return Ok(client
|
||||
.clear_activity()
|
||||
.map_err(could_not_clear_err)?); // try again, but don't reconnect if it fails again
|
||||
}
|
||||
} else {
|
||||
res.map_err(could_not_clear_err)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear the activity, but if there is a running profile, set the activity to that instead
|
||||
pub async fn clear_to_default(
|
||||
&self,
|
||||
reconnect_if_fail: bool,
|
||||
) -> crate::Result<()> {
|
||||
let state: Arc<tokio::sync::RwLockReadGuard<'_, State>> =
|
||||
State::get().await?;
|
||||
if let Some(existing_child) = state
|
||||
.children
|
||||
.read()
|
||||
.await
|
||||
.running_profile_paths()
|
||||
.await?
|
||||
.first()
|
||||
{
|
||||
self.set_activity(
|
||||
&format!("Playing {}", existing_child),
|
||||
reconnect_if_fail,
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
self.clear_activity(reconnect_if_fail).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,9 @@ pub use self::java_globals::*;
|
||||
mod safe_processes;
|
||||
pub use self::safe_processes::*;
|
||||
|
||||
mod discord;
|
||||
pub use self::discord::*;
|
||||
|
||||
// Global state
|
||||
// RwLock on state only has concurrent reads, except for config dir change which takes control of the State
|
||||
static LAUNCHER_STATE: OnceCell<RwLock<State>> = OnceCell::const_new();
|
||||
@@ -81,6 +84,9 @@ pub struct State {
|
||||
/// Launcher processes that should be safely exited on shutdown
|
||||
pub(crate) safety_processes: RwLock<SafeProcesses>,
|
||||
|
||||
/// Discord RPC
|
||||
pub discord_rpc: DiscordGuard,
|
||||
|
||||
/// File watcher debouncer
|
||||
pub(crate) file_watcher: RwLock<Debouncer<RecommendedWatcher>>,
|
||||
}
|
||||
@@ -156,6 +162,9 @@ impl State {
|
||||
let children = Children::new();
|
||||
let auth_flow = AuthTask::new();
|
||||
let safety_processes = SafeProcesses::new();
|
||||
|
||||
let discord_rpc = DiscordGuard::init().await?;
|
||||
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
Ok::<RwLock<Self>, crate::Error>(RwLock::new(Self {
|
||||
@@ -175,6 +184,7 @@ impl State {
|
||||
children: RwLock::new(children),
|
||||
auth_flow: RwLock::new(auth_flow),
|
||||
tags: RwLock::new(tags),
|
||||
discord_rpc,
|
||||
safety_processes: RwLock::new(safety_processes),
|
||||
file_watcher: RwLock::new(file_watcher),
|
||||
}))
|
||||
|
||||
@@ -149,6 +149,8 @@ pub struct Profile {
|
||||
pub memory: Option<MemorySettings>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<WindowSize>,
|
||||
#[serde(default)]
|
||||
pub force_fullscreen: SetFullscreen,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hooks: Option<Hooks>,
|
||||
pub projects: HashMap<ProjectPathId, Project>,
|
||||
@@ -223,6 +225,21 @@ impl ModLoader {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Copy)]
|
||||
pub enum SetFullscreen {
|
||||
#[serde(rename = "Leave unset")]
|
||||
LeaveUnset,
|
||||
#[serde(rename = "Set windowed")]
|
||||
SetWindowed,
|
||||
#[serde(rename = "Set fullscreen")]
|
||||
SetFullscreen,
|
||||
}
|
||||
impl Default for SetFullscreen {
|
||||
fn default() -> Self {
|
||||
Self::LeaveUnset
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct JavaSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -268,6 +285,7 @@ impl Profile {
|
||||
java: None,
|
||||
memory: None,
|
||||
resolution: None,
|
||||
force_fullscreen: SetFullscreen::LeaveUnset,
|
||||
hooks: None,
|
||||
modrinth_update_version: None,
|
||||
})
|
||||
|
||||
@@ -46,11 +46,9 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
];
|
||||
for java_path in java_paths {
|
||||
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue };
|
||||
for java_subpath in java_subpaths {
|
||||
if let Ok(java_subpath) = java_subpath {
|
||||
let path = java_subpath.path();
|
||||
jre_paths.insert(path.join("bin"));
|
||||
}
|
||||
for java_subpath in java_subpaths.flatten() {
|
||||
let path = java_subpath.path();
|
||||
jre_paths.insert(path.join("bin"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,19 +91,17 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
pub fn get_paths_from_jre_winregkey(jre_key: RegKey) -> HashSet<PathBuf> {
|
||||
let mut jre_paths = HashSet::new();
|
||||
|
||||
for subkey in jre_key.enum_keys() {
|
||||
if let Ok(subkey) = subkey {
|
||||
if let Ok(subkey) = jre_key.open_subkey(subkey) {
|
||||
let subkey_value_names =
|
||||
[r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"];
|
||||
for subkey in jre_key.enum_keys().flatten() {
|
||||
if let Ok(subkey) = jre_key.open_subkey(subkey) {
|
||||
let subkey_value_names =
|
||||
[r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"];
|
||||
|
||||
for subkey_value in subkey_value_names {
|
||||
let path: Result<String, std::io::Error> =
|
||||
subkey.get_value(subkey_value);
|
||||
let Ok(path) = path else {continue};
|
||||
for subkey_value in subkey_value_names {
|
||||
let path: Result<String, std::io::Error> =
|
||||
subkey.get_value(subkey_value);
|
||||
let Ok(path) = path else {continue};
|
||||
|
||||
jre_paths.insert(PathBuf::from(path).join("bin"));
|
||||
}
|
||||
jre_paths.insert(PathBuf::from(path).join("bin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user