You've already forked AstralRinth
forked from didirus/AstralRinth
Various final backend fixes (#117)
* Various final backend fixes * Add FS watching * run lint * Autodetect installed jars
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
//! Authentication flow interface
|
||||
use reqwest::Method;
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::event::emit::{emit_loading, init_loading};
|
||||
use crate::util::fetch::{fetch_advanced, fetch_json};
|
||||
use crate::{
|
||||
launcher::download,
|
||||
prelude::Profile,
|
||||
state::JavaGlobals,
|
||||
util::jre::{self, extract_java_majorminor_version, JavaVersion},
|
||||
State,
|
||||
LoadingBarType, State,
|
||||
};
|
||||
|
||||
pub const JAVA_8_KEY: &str = "JAVA_8";
|
||||
@@ -133,6 +137,87 @@ pub async fn find_java17_jres() -> crate::Result<Vec<JavaVersion>> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::JavaDownload {
|
||||
version: java_version,
|
||||
},
|
||||
100.0,
|
||||
"Downloading java version",
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Package {
|
||||
pub download_url: String,
|
||||
pub name: PathBuf,
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?;
|
||||
let packages = fetch_json::<Vec<Package>>(
|
||||
Method::GET,
|
||||
&format!(
|
||||
"https://api.azul.com/metadata/v1/zulu/packages?arch={}&java_version={}&os={}&archive_type=zip&javafx_bundled=false&java_package_type=jre&page_size=1",
|
||||
std::env::consts::ARCH, java_version, std::env::consts::OS
|
||||
),
|
||||
None,
|
||||
None,
|
||||
&state.fetch_semaphore,
|
||||
).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Downloading java version")).await?;
|
||||
|
||||
if let Some(download) = packages.first() {
|
||||
let file = fetch_advanced(
|
||||
Method::GET,
|
||||
&download.download_url,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some((&loading_bar, 80.0)),
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let path = state.directories.java_versions_dir();
|
||||
|
||||
if path.exists() {
|
||||
tokio::fs::remove_dir_all(&path).await?;
|
||||
}
|
||||
|
||||
let mut archive = zip::ZipArchive::new(std::io::Cursor::new(file))
|
||||
.map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to read java zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?;
|
||||
archive.extract(&path).map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to extract java zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?;
|
||||
Ok(path
|
||||
.join(
|
||||
download
|
||||
.name
|
||||
.file_stem()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
)
|
||||
.join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version)))
|
||||
} else {
|
||||
Err(crate::ErrorKind::LauncherError(format!(
|
||||
"No Java Version found for Java version {}, OS {}, and Architecture {}",
|
||||
java_version, std::env::consts::OS, std::env::consts::ARCH,
|
||||
)).into())
|
||||
}
|
||||
}
|
||||
|
||||
// Get all JREs that exist on the system
|
||||
pub async fn get_all_jre() -> crate::Result<Vec<JavaVersion>> {
|
||||
Ok(jre::get_all_jre().await?)
|
||||
@@ -148,3 +233,14 @@ pub async fn validate_globals() -> crate::Result<bool> {
|
||||
pub async fn check_jre(path: PathBuf) -> crate::Result<Option<JavaVersion>> {
|
||||
Ok(jre::check_java_at_filepath(&path).await)
|
||||
}
|
||||
|
||||
// Gets maximum memory in KiB.
|
||||
pub async fn get_max_memory() -> crate::Result<u64> {
|
||||
Ok(sys_info::mem_info()
|
||||
.map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::LauncherError(
|
||||
"Unable to get computer memory".to_string(),
|
||||
))
|
||||
})?
|
||||
.total)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ use crate::event::emit::{
|
||||
};
|
||||
use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::state::{
|
||||
LinkedData, ModrinthProject, ModrinthVersion, Profile, ProfileInstallStage,
|
||||
SideType,
|
||||
LinkedData, ModrinthProject, ModrinthVersion, ProfileInstallStage, SideType,
|
||||
};
|
||||
use crate::util::fetch::{
|
||||
fetch, fetch_advanced, fetch_json, fetch_mirrors, write, write_cached_icon,
|
||||
@@ -478,7 +477,6 @@ async fn install_pack(
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile, None).await?
|
||||
{
|
||||
Profile::sync_projects_task(profile.clone());
|
||||
crate::launcher::install_minecraft(
|
||||
&profile_val,
|
||||
Some(loading_bar),
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::state::ProjectMetadata;
|
||||
use crate::{
|
||||
auth::{self, refresh},
|
||||
event::{emit::emit_profile, ProfilePayloadType},
|
||||
launcher::download,
|
||||
state::MinecraftChild,
|
||||
};
|
||||
pub use crate::{
|
||||
@@ -109,40 +108,10 @@ pub async fn list(
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Query + sync profile's projects with the UI from the FS
|
||||
#[tracing::instrument]
|
||||
pub async fn sync(path: &Path) -> crate::Result<()> {
|
||||
Box::pin({
|
||||
async move {
|
||||
let state = State::get().await?;
|
||||
let result = {
|
||||
let mut profiles: tokio::sync::RwLockWriteGuard<
|
||||
crate::state::Profiles,
|
||||
> = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(path) {
|
||||
profile.sync_projects().await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
};
|
||||
State::sync().await?;
|
||||
result
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Installs/Repairs a profile
|
||||
#[tracing::instrument]
|
||||
pub async fn install(path: &Path) -> crate::Result<()> {
|
||||
let profile = get(path, None).await?;
|
||||
|
||||
if let Some(profile) = profile {
|
||||
if let Some(profile) = get(path, None).await? {
|
||||
crate::launcher::install_minecraft(&profile, None).await?;
|
||||
} else {
|
||||
return Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
@@ -155,11 +124,8 @@ pub async fn install(path: &Path) -> crate::Result<()> {
|
||||
}
|
||||
|
||||
pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
Box::pin(async move {
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
if let Some(profile) = get(profile_path, None).await? {
|
||||
let loading_bar = init_loading(
|
||||
LoadingBarType::ProfileUpdate {
|
||||
profile_path: profile.path.clone(),
|
||||
@@ -187,8 +153,6 @@ pub async fn update_all(profile_path: &Path) -> crate::Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
profile.sync_projects().await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
@@ -204,10 +168,7 @@ pub async fn update_project(
|
||||
profile_path: &Path,
|
||||
project_path: &Path,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
if let Some(profile) = get(profile_path, None).await? {
|
||||
if let Some(project) = profile.projects.get(project_path) {
|
||||
if let ProjectMetadata::Modrinth {
|
||||
update_version: Some(update_version),
|
||||
@@ -222,15 +183,20 @@ pub async fn update_project(
|
||||
profile.remove_project(project_path, Some(true)).await?;
|
||||
}
|
||||
|
||||
let value = profile.projects.remove(project_path);
|
||||
if let Some(mut project) = value {
|
||||
if let ProjectMetadata::Modrinth {
|
||||
ref mut version, ..
|
||||
} = project.metadata
|
||||
{
|
||||
*version = Box::new(new_version);
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
if let Some(profile) = profiles.0.get_mut(project_path) {
|
||||
let value = profile.projects.remove(project_path);
|
||||
if let Some(mut project) = value {
|
||||
if let ProjectMetadata::Modrinth {
|
||||
ref mut version,
|
||||
..
|
||||
} = project.metadata
|
||||
{
|
||||
*version = Box::new(new_version);
|
||||
}
|
||||
profile.projects.insert(path.clone(), project);
|
||||
}
|
||||
profile.projects.insert(path.clone(), project);
|
||||
}
|
||||
|
||||
return Ok(path);
|
||||
@@ -249,57 +215,15 @@ pub async fn update_project(
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces a project given a new version ID
|
||||
pub async fn replace_project(
|
||||
profile_path: &Path,
|
||||
project: &Path,
|
||||
version_id: String,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
let (path, new_version) =
|
||||
profile.add_project_version(version_id).await?;
|
||||
|
||||
if path != project {
|
||||
profile.remove_project(project, Some(true)).await?;
|
||||
}
|
||||
|
||||
let value = profile.projects.remove(project);
|
||||
if let Some(mut project) = value {
|
||||
if let ProjectMetadata::Modrinth {
|
||||
ref mut version, ..
|
||||
} = project.metadata
|
||||
{
|
||||
*version = Box::new(new_version);
|
||||
}
|
||||
profile.projects.insert(path.clone(), project);
|
||||
}
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
profile_path.display().to_string(),
|
||||
)
|
||||
.as_error())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a project from a version
|
||||
#[tracing::instrument]
|
||||
pub async fn add_project_from_version(
|
||||
profile_path: &Path,
|
||||
version_id: String,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
if let Some(profile) = get(profile_path, None).await? {
|
||||
let (path, _) = profile.add_project_version(version_id).await?;
|
||||
|
||||
Profile::sync_projects_task(profile.path.clone());
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
@@ -316,10 +240,7 @@ pub async fn add_project_from_path(
|
||||
path: &Path,
|
||||
project_type: Option<String>,
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile_path) {
|
||||
if let Some(profile) = get(profile_path, None).await? {
|
||||
let file = fs::read(path).await?;
|
||||
let file_name = path
|
||||
.file_name()
|
||||
@@ -335,8 +256,6 @@ pub async fn add_project_from_path(
|
||||
)
|
||||
.await?;
|
||||
|
||||
Profile::sync_projects_task(profile.path.clone());
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
@@ -352,10 +271,7 @@ pub async fn toggle_disable_project(
|
||||
profile: &Path,
|
||||
project: &Path,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||
if let Some(profile) = get(profile, None).await? {
|
||||
profile.toggle_disable_project(project).await?;
|
||||
|
||||
Ok(())
|
||||
@@ -373,10 +289,7 @@ pub async fn remove_project(
|
||||
profile: &Path,
|
||||
project: &Path,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(profile) {
|
||||
if let Some(profile) = get(profile, None).await? {
|
||||
profile.remove_project(project, None).await?;
|
||||
|
||||
Ok(())
|
||||
@@ -423,7 +336,6 @@ pub async fn run_credentials(
|
||||
Box::pin(async move {
|
||||
let state = State::get().await?;
|
||||
let settings = state.settings.read().await;
|
||||
let metadata = state.metadata.read().await;
|
||||
let profile = get(path, None).await?.ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Tried to run a nonexistent or unloaded profile at path {}!",
|
||||
@@ -431,25 +343,6 @@ pub async fn run_credentials(
|
||||
))
|
||||
})?;
|
||||
|
||||
let version = metadata
|
||||
.minecraft
|
||||
.versions
|
||||
.iter()
|
||||
.find(|it| it.id == profile.metadata.game_version)
|
||||
.ok_or_else(|| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
"Invalid or unknown Minecraft version: {}",
|
||||
profile.metadata.game_version
|
||||
))
|
||||
})?;
|
||||
let version_info = download::download_version_info(
|
||||
&state,
|
||||
version,
|
||||
profile.metadata.loader_version.as_ref(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let pre_launch_hooks =
|
||||
&profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
|
||||
if let Some(hook) = pre_launch_hooks {
|
||||
@@ -473,49 +366,6 @@ pub async fn run_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
jre_key: Some(ref jre_key),
|
||||
..
|
||||
}) => 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()),
|
||||
}
|
||||
}
|
||||
};
|
||||
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: {}",
|
||||
java_install.display()
|
||||
))
|
||||
.as_error());
|
||||
}
|
||||
let java_args = profile
|
||||
.java
|
||||
.as_ref()
|
||||
@@ -550,7 +400,6 @@ pub async fn run_credentials(
|
||||
};
|
||||
|
||||
let mc_process = crate::launcher::launch_minecraft(
|
||||
java_install,
|
||||
java_args,
|
||||
env_args,
|
||||
wrapper,
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
//! Theseus profile management interface
|
||||
use crate::event::emit::emit_warning;
|
||||
use crate::state::LinkedData;
|
||||
use crate::{
|
||||
event::{emit::emit_profile, ProfilePayloadType},
|
||||
jre,
|
||||
prelude::ModLoader,
|
||||
};
|
||||
pub use crate::{
|
||||
@@ -84,7 +82,12 @@ pub async fn profile_create(
|
||||
&canonicalize(&path)?.display()
|
||||
);
|
||||
let loader = if modloader != ModLoader::Vanilla {
|
||||
get_loader_version_from_loader(game_version.clone(), modloader, loader_version).await?
|
||||
get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
modloader,
|
||||
loader_version,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -112,19 +115,6 @@ pub async fn profile_create(
|
||||
|
||||
profile.metadata.linked_data = linked_data;
|
||||
|
||||
// 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.java = Some(JavaSettings {
|
||||
jre_key: Some(optimal_version_key),
|
||||
extra_arguments: None,
|
||||
});
|
||||
} else {
|
||||
emit_warning(&format!("Could not detect optimal JRE: {optimal_version_key}, falling back to system default.")).await?;
|
||||
}
|
||||
|
||||
emit_profile(
|
||||
uuid,
|
||||
path.clone(),
|
||||
@@ -144,7 +134,8 @@ pub async fn profile_create(
|
||||
State::sync().await?;
|
||||
|
||||
Ok(path)
|
||||
}).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_loader_version_from_loader(
|
||||
|
||||
@@ -84,6 +84,9 @@ pub enum ErrorKind {
|
||||
|
||||
#[error("Error: {0}")]
|
||||
OtherError(String),
|
||||
|
||||
#[error("File watching error: {0}")]
|
||||
NotifyError(#[from] notify::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -149,6 +149,9 @@ impl Drop for LoadingBar {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum LoadingBarType {
|
||||
StateInit,
|
||||
JavaDownload {
|
||||
version: u32,
|
||||
},
|
||||
PackFileDownload {
|
||||
profile_path: PathBuf,
|
||||
pack_name: String,
|
||||
|
||||
@@ -22,12 +22,13 @@ pub fn get_class_paths(
|
||||
libraries_path: &Path,
|
||||
libraries: &[Library],
|
||||
client_path: &Path,
|
||||
java_arch: &str,
|
||||
) -> crate::Result<String> {
|
||||
let mut cps = libraries
|
||||
.iter()
|
||||
.filter_map(|library| {
|
||||
if let Some(rules) = &library.rules {
|
||||
if !rules.iter().all(parse_rule) {
|
||||
if !rules.iter().any(|x| parse_rule(x, java_arch)) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -53,19 +54,20 @@ pub fn get_class_paths(
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
Ok(cps.join(classpath_separator()))
|
||||
Ok(cps.join(classpath_separator(java_arch)))
|
||||
}
|
||||
|
||||
pub fn get_class_paths_jar<T: AsRef<str>>(
|
||||
libraries_path: &Path,
|
||||
libraries: &[T],
|
||||
java_arch: &str,
|
||||
) -> crate::Result<String> {
|
||||
let cps = libraries
|
||||
.iter()
|
||||
.map(|library| get_lib_path(libraries_path, library.as_ref(), false))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(cps.join(classpath_separator()))
|
||||
Ok(cps.join(classpath_separator(java_arch)))
|
||||
}
|
||||
|
||||
pub fn get_lib_path(
|
||||
@@ -91,6 +93,8 @@ pub fn get_lib_path(
|
||||
|
||||
Ok(path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn get_jvm_arguments(
|
||||
arguments: Option<&[Argument]>,
|
||||
natives_path: &Path,
|
||||
@@ -99,19 +103,26 @@ pub fn get_jvm_arguments(
|
||||
version_name: &str,
|
||||
memory: MemorySettings,
|
||||
custom_args: Vec<String>,
|
||||
java_arch: &str,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
let mut parsed_arguments = Vec::new();
|
||||
|
||||
if let Some(args) = arguments {
|
||||
parse_arguments(args, &mut parsed_arguments, |arg| {
|
||||
parse_jvm_argument(
|
||||
arg.to_string(),
|
||||
natives_path,
|
||||
libraries_path,
|
||||
class_paths,
|
||||
version_name,
|
||||
)
|
||||
})?;
|
||||
parse_arguments(
|
||||
args,
|
||||
&mut parsed_arguments,
|
||||
|arg| {
|
||||
parse_jvm_argument(
|
||||
arg.to_string(),
|
||||
natives_path,
|
||||
libraries_path,
|
||||
class_paths,
|
||||
version_name,
|
||||
java_arch,
|
||||
)
|
||||
},
|
||||
java_arch,
|
||||
)?;
|
||||
} else {
|
||||
parsed_arguments.push(format!(
|
||||
"-Djava.library.path={}",
|
||||
@@ -147,6 +158,7 @@ fn parse_jvm_argument(
|
||||
libraries_path: &Path,
|
||||
class_paths: &str,
|
||||
version_name: &str,
|
||||
java_arch: &str,
|
||||
) -> crate::Result<String> {
|
||||
argument.retain(|c| !c.is_whitespace());
|
||||
Ok(argument
|
||||
@@ -174,7 +186,7 @@ fn parse_jvm_argument(
|
||||
})?
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.replace("${classpath_separator}", classpath_separator())
|
||||
.replace("${classpath_separator}", classpath_separator(java_arch))
|
||||
.replace("${launcher_name}", "theseus")
|
||||
.replace("${launcher_version}", env!("CARGO_PKG_VERSION"))
|
||||
.replace("${version_name}", version_name)
|
||||
@@ -192,13 +204,37 @@ pub fn get_minecraft_arguments(
|
||||
assets_directory: &Path,
|
||||
version_type: &VersionType,
|
||||
resolution: WindowSize,
|
||||
java_arch: &str,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
if let Some(arguments) = arguments {
|
||||
let mut parsed_arguments = Vec::new();
|
||||
|
||||
parse_arguments(arguments, &mut parsed_arguments, |arg| {
|
||||
parse_minecraft_argument(
|
||||
arg,
|
||||
parse_arguments(
|
||||
arguments,
|
||||
&mut parsed_arguments,
|
||||
|arg| {
|
||||
parse_minecraft_argument(
|
||||
arg,
|
||||
&credentials.access_token,
|
||||
&credentials.username,
|
||||
&credentials.id,
|
||||
version,
|
||||
asset_index_name,
|
||||
game_directory,
|
||||
assets_directory,
|
||||
version_type,
|
||||
resolution,
|
||||
)
|
||||
},
|
||||
java_arch,
|
||||
)?;
|
||||
|
||||
Ok(parsed_arguments)
|
||||
} else if let Some(legacy_arguments) = legacy_arguments {
|
||||
let mut parsed_arguments = Vec::new();
|
||||
for x in legacy_arguments.split(' ') {
|
||||
parsed_arguments.push(parse_minecraft_argument(
|
||||
&x.replace(' ', TEMPORARY_REPLACE_CHAR),
|
||||
&credentials.access_token,
|
||||
&credentials.username,
|
||||
&credentials.id,
|
||||
@@ -208,26 +244,9 @@ pub fn get_minecraft_arguments(
|
||||
assets_directory,
|
||||
version_type,
|
||||
resolution,
|
||||
)
|
||||
})?;
|
||||
|
||||
)?);
|
||||
}
|
||||
Ok(parsed_arguments)
|
||||
} else if let Some(legacy_arguments) = legacy_arguments {
|
||||
Ok(parse_minecraft_argument(
|
||||
&legacy_arguments.replace(' ', TEMPORARY_REPLACE_CHAR),
|
||||
&credentials.access_token,
|
||||
&credentials.username,
|
||||
&credentials.id,
|
||||
version,
|
||||
asset_index_name,
|
||||
game_directory,
|
||||
assets_directory,
|
||||
version_type,
|
||||
resolution,
|
||||
)?
|
||||
.split(' ')
|
||||
.map(|x| x.to_string())
|
||||
.collect())
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
@@ -300,6 +319,7 @@ fn parse_arguments<F>(
|
||||
arguments: &[Argument],
|
||||
parsed_arguments: &mut Vec<String>,
|
||||
parse_function: F,
|
||||
java_arch: &str,
|
||||
) -> crate::Result<()>
|
||||
where
|
||||
F: Fn(&str) -> crate::Result<String>,
|
||||
@@ -314,7 +334,7 @@ where
|
||||
}
|
||||
}
|
||||
Argument::Ruled { rules, value } => {
|
||||
if rules.iter().all(parse_rule) {
|
||||
if rules.iter().any(|x| parse_rule(x, java_arch)) {
|
||||
match value {
|
||||
ArgumentValue::Single(arg) => {
|
||||
parsed_arguments.push(parse_function(
|
||||
|
||||
@@ -24,6 +24,7 @@ pub async fn download_minecraft(
|
||||
st: &State,
|
||||
version: &GameVersionInfo,
|
||||
loading_bar: &LoadingBarId,
|
||||
java_arch: &str,
|
||||
) -> crate::Result<()> {
|
||||
tracing::info!("Downloading Minecraft version {}", version.id);
|
||||
// 5
|
||||
@@ -45,7 +46,7 @@ pub async fn download_minecraft(
|
||||
// Total loading sums to 90/60
|
||||
download_client(st, version, Some(loading_bar)), // 10
|
||||
download_assets(st, version.assets == "legacy", &assets_index, Some(loading_bar), amount), // 40
|
||||
download_libraries(st, version.libraries.as_slice(), &version.id, Some(loading_bar), amount) // 40
|
||||
download_libraries(st, version.libraries.as_slice(), &version.id, Some(loading_bar), amount, java_arch) // 40
|
||||
}?;
|
||||
|
||||
tracing::info!("Done downloading Minecraft!");
|
||||
@@ -247,6 +248,7 @@ pub async fn download_libraries(
|
||||
version: &str,
|
||||
loading_bar: Option<&LoadingBarId>,
|
||||
loading_amount: f64,
|
||||
java_arch: &str,
|
||||
) -> crate::Result<()> {
|
||||
Box::pin(async move {
|
||||
tracing::debug!("Loading libraries");
|
||||
@@ -260,10 +262,12 @@ pub async fn download_libraries(
|
||||
stream::iter(libraries.iter())
|
||||
.map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move {
|
||||
if let Some(rules) = &library.rules {
|
||||
if !rules.iter().all(super::parse_rule) {
|
||||
if !rules.iter().any(|x| super::parse_rule(x, java_arch)) {
|
||||
tracing::trace!("Skipped library {}", &library.name);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
tokio::try_join! {
|
||||
async {
|
||||
let artifact_path = d::get_path_from_artifact(&library.name)?;
|
||||
@@ -278,24 +282,23 @@ pub async fn download_libraries(
|
||||
let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore)
|
||||
.await?;
|
||||
write(&path, &bytes, &st.io_semaphore).await?;
|
||||
tracing::trace!("Fetched library {}", &library.name);
|
||||
tracing::trace!("Fetched library {} to path {:?}", &library.name, &path);
|
||||
Ok::<_, crate::Error>(())
|
||||
}
|
||||
None => {
|
||||
_ => {
|
||||
let url = [
|
||||
library
|
||||
.url
|
||||
.as_deref()
|
||||
.unwrap_or("https://libraries.minecraft.net"),
|
||||
.unwrap_or("https://libraries.minecraft.net/"),
|
||||
&artifact_path
|
||||
].concat();
|
||||
|
||||
let bytes = fetch(&url, None, &st.fetch_semaphore).await?;
|
||||
write(&path, &bytes, &st.io_semaphore).await?;
|
||||
tracing::trace!("Fetched library {}", &library.name);
|
||||
tracing::trace!("Fetched library {} to path {:?}", &library.name, &path);
|
||||
Ok::<_, crate::Error>(())
|
||||
}
|
||||
_ => Ok(())
|
||||
}
|
||||
},
|
||||
async {
|
||||
@@ -304,7 +307,7 @@ pub async fn download_libraries(
|
||||
library
|
||||
.natives
|
||||
.as_ref()?
|
||||
.get(&Os::native_arch())?,
|
||||
.get(&Os::native_arch(java_arch))?,
|
||||
library
|
||||
.downloads
|
||||
.as_ref()?
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Logic for launching Minecraft
|
||||
use crate::event::emit::{emit_loading, init_or_edit_loading};
|
||||
use crate::event::{LoadingBarId, LoadingBarType};
|
||||
use crate::jre::{JAVA_17_KEY, JAVA_18PLUS_KEY, JAVA_8_KEY};
|
||||
use crate::prelude::JavaVersion;
|
||||
use crate::state::ProfileInstallStage;
|
||||
use crate::{
|
||||
process,
|
||||
@@ -8,10 +10,11 @@ use crate::{
|
||||
State,
|
||||
};
|
||||
use daedalus as d;
|
||||
use daedalus::minecraft::VersionInfo;
|
||||
use dunce::canonicalize;
|
||||
use st::Profile;
|
||||
use std::fs;
|
||||
use std::{path::Path, process::Stdio, sync::Arc};
|
||||
use std::{process::Stdio, sync::Arc};
|
||||
use tokio::process::Command;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -21,18 +24,18 @@ pub mod auth;
|
||||
pub mod download;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub fn parse_rule(rule: &d::minecraft::Rule) -> bool {
|
||||
pub fn parse_rule(rule: &d::minecraft::Rule, java_version: &str) -> bool {
|
||||
use d::minecraft::{Rule, RuleAction};
|
||||
|
||||
let res = match rule {
|
||||
Rule {
|
||||
os: Some(ref os), ..
|
||||
} => crate::util::platform::os_rule(os),
|
||||
} => crate::util::platform::os_rule(os, java_version),
|
||||
Rule {
|
||||
features: Some(ref features),
|
||||
..
|
||||
} => features.has_demo_resolution.unwrap_or(false),
|
||||
_ => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
match rule.action {
|
||||
@@ -54,6 +57,40 @@ macro_rules! processor_rules {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_java_version_from_profile(
|
||||
profile: &Profile,
|
||||
version_info: &VersionInfo,
|
||||
) -> crate::Result<JavaVersion> {
|
||||
if let Some(java) = profile.java.clone().and_then(|x| x.override_version) {
|
||||
Ok(java)
|
||||
} else {
|
||||
let optimal_keys = match version_info
|
||||
.java_version
|
||||
.as_ref()
|
||||
.map(|it| it.major_version)
|
||||
.unwrap_or(8)
|
||||
{
|
||||
0..=15 => vec![JAVA_8_KEY, JAVA_17_KEY, JAVA_18PLUS_KEY],
|
||||
16..=17 => vec![JAVA_17_KEY, JAVA_18PLUS_KEY],
|
||||
_ => vec![JAVA_18PLUS_KEY],
|
||||
};
|
||||
|
||||
let state = State::get().await?;
|
||||
let settings = state.settings.read().await;
|
||||
|
||||
for key in optimal_keys {
|
||||
if let Some(java) = settings.java_globals.get(&key.to_string()) {
|
||||
return Ok(java.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Err(crate::ErrorKind::LauncherError(
|
||||
"No available java installation".to_string(),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(profile))]
|
||||
pub async fn install_minecraft(
|
||||
profile: &Profile,
|
||||
@@ -112,8 +149,10 @@ pub async fn install_minecraft(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let java_version = get_java_version_from_profile(profile, &version_info).await?;
|
||||
|
||||
// Download minecraft (5-90)
|
||||
download::download_minecraft(&state, &version_info, &loading_bar).await?;
|
||||
download::download_minecraft(&state, &version_info, &loading_bar, &java_version.architecture).await?;
|
||||
|
||||
if let Some(processors) = &version_info.processors {
|
||||
let client_path = state
|
||||
@@ -157,11 +196,12 @@ pub async fn install_minecraft(
|
||||
cp.push(processor.jar.clone())
|
||||
});
|
||||
|
||||
let child = Command::new("java")
|
||||
let child = Command::new(&java_version.path)
|
||||
.arg("-cp")
|
||||
.arg(args::get_class_paths_jar(
|
||||
&state.directories.libraries_dir(),
|
||||
&cp,
|
||||
&java_version.architecture
|
||||
)?)
|
||||
.arg(
|
||||
args::get_processor_main_class(args::get_lib_path(
|
||||
@@ -231,7 +271,6 @@ pub async fn install_minecraft(
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn launch_minecraft(
|
||||
java_install: &Path,
|
||||
java_args: &[String],
|
||||
env_args: &[(String, String)],
|
||||
wrapper: &Option<String>,
|
||||
@@ -286,6 +325,8 @@ pub async fn launch_minecraft(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let java_version = get_java_version_from_profile(profile, &version_info).await?;
|
||||
|
||||
let client_path = state
|
||||
.directories
|
||||
.version_dir(&version_jar)
|
||||
@@ -294,9 +335,9 @@ pub async fn launch_minecraft(
|
||||
let args = version_info.arguments.clone().unwrap_or_default();
|
||||
let mut command = match wrapper {
|
||||
Some(hook) => {
|
||||
wrap_ref_builder!(it = Command::new(hook) => {it.arg(java_install)})
|
||||
wrap_ref_builder!(it = Command::new(hook) => {it.arg(&java_version.path)})
|
||||
}
|
||||
None => Command::new(String::from(java_install.to_string_lossy())),
|
||||
None => Command::new(&java_version.path),
|
||||
};
|
||||
|
||||
let env_args = Vec::from(env_args);
|
||||
@@ -324,10 +365,12 @@ pub async fn launch_minecraft(
|
||||
&state.directories.libraries_dir(),
|
||||
version_info.libraries.as_slice(),
|
||||
&client_path,
|
||||
&java_version.architecture
|
||||
)?,
|
||||
&version_jar,
|
||||
*memory,
|
||||
Vec::from(java_args),
|
||||
&java_version.architecture
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
@@ -345,6 +388,7 @@ pub async fn launch_minecraft(
|
||||
&state.directories.assets_dir(),
|
||||
&version.type_,
|
||||
*resolution,
|
||||
&java_version.architecture
|
||||
)?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
@@ -362,7 +406,7 @@ pub async fn launch_minecraft(
|
||||
|
||||
// Get Modrinth logs directories
|
||||
let datetime_string =
|
||||
chrono::Local::now().format("%Y%m%y_%H%M%S").to_string();
|
||||
chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
|
||||
let logs_dir = {
|
||||
let st = State::get().await?;
|
||||
st.directories
|
||||
|
||||
@@ -44,6 +44,12 @@ impl DirectoryInfo {
|
||||
self.config_dir.join("meta")
|
||||
}
|
||||
|
||||
/// Get the Minecraft java versions metadata directory
|
||||
#[inline]
|
||||
pub fn java_versions_dir(&self) -> PathBuf {
|
||||
self.metadata_dir().join("java_versions")
|
||||
}
|
||||
|
||||
/// Get the Minecraft versions metadata directory
|
||||
#[inline]
|
||||
pub fn versions_dir(&self) -> PathBuf {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Theseus state management system
|
||||
use crate::event::emit::emit_loading;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::event::emit::init_loading;
|
||||
use crate::event::LoadingBarType;
|
||||
@@ -7,9 +8,14 @@ use crate::loading_join;
|
||||
|
||||
use crate::state::users::Users;
|
||||
use crate::util::fetch::{FetchSemaphore, IoSemaphore};
|
||||
use notify::RecommendedWatcher;
|
||||
use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{OnceCell, RwLock, Semaphore};
|
||||
|
||||
use futures::{channel::mpsc::channel, SinkExt, StreamExt};
|
||||
|
||||
// Submodules
|
||||
mod dirs;
|
||||
pub use self::dirs::*;
|
||||
@@ -69,6 +75,9 @@ pub struct State {
|
||||
pub(crate) users: RwLock<Users>,
|
||||
/// Launcher tags
|
||||
pub(crate) tags: RwLock<Tags>,
|
||||
|
||||
/// File watcher debouncer
|
||||
pub(crate) file_watcher: RwLock<Debouncer<RecommendedWatcher>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@@ -84,6 +93,8 @@ impl State {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut file_watcher = init_watcher().await?;
|
||||
|
||||
let directories = DirectoryInfo::init().await?;
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
|
||||
@@ -100,7 +111,8 @@ impl State {
|
||||
|
||||
let metadata_fut =
|
||||
Metadata::init(&directories, &io_semaphore);
|
||||
let profiles_fut = Profiles::init(&directories);
|
||||
let profiles_fut =
|
||||
Profiles::init(&directories, &mut file_watcher);
|
||||
let tags_fut = Tags::init(
|
||||
&directories,
|
||||
&io_semaphore,
|
||||
@@ -137,6 +149,7 @@ impl State {
|
||||
children: RwLock::new(children),
|
||||
auth_flow: RwLock::new(auth_flow),
|
||||
tags: RwLock::new(tags),
|
||||
file_watcher: RwLock::new(file_watcher),
|
||||
}))
|
||||
}
|
||||
})
|
||||
@@ -220,3 +233,51 @@ impl State {
|
||||
*io_semaphore = Semaphore::new(settings.max_concurrent_downloads);
|
||||
}
|
||||
}
|
||||
|
||||
async fn init_watcher() -> crate::Result<Debouncer<RecommendedWatcher>> {
|
||||
let (mut tx, mut rx) = channel(1);
|
||||
|
||||
let file_watcher = new_debouncer(
|
||||
Duration::from_secs(2),
|
||||
None,
|
||||
move |res: DebounceEventResult| {
|
||||
futures::executor::block_on(async {
|
||||
tx.send(res).await.unwrap();
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
while let Some(res) = rx.next().await {
|
||||
match res {
|
||||
Ok(events) => {
|
||||
let mut visited_paths = Vec::new();
|
||||
events.iter().for_each(|e| {
|
||||
let mut new_path = PathBuf::new();
|
||||
let mut found = false;
|
||||
|
||||
for component in e.path.components() {
|
||||
new_path.push(component);
|
||||
if found {
|
||||
break;
|
||||
}
|
||||
if component.as_os_str() == "profiles" {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !visited_paths.contains(&new_path) {
|
||||
Profile::sync_projects_task(new_path.clone());
|
||||
visited_paths.push(new_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(errors) => errors.iter().for_each(|err| {
|
||||
tracing::warn!("Unable to watch file: {err}")
|
||||
}),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(file_watcher)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::config::MODRINTH_API_URL;
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::event::emit::emit_profile;
|
||||
use crate::event::ProfilePayloadType;
|
||||
use crate::prelude::JavaVersion;
|
||||
use crate::state::projects::Project;
|
||||
use crate::state::{ModrinthVersion, ProjectMetadata, ProjectType};
|
||||
use crate::util::fetch::{
|
||||
@@ -13,6 +14,8 @@ use daedalus::get_hash;
|
||||
use daedalus::modded::LoaderVersion;
|
||||
use dunce::canonicalize;
|
||||
use futures::prelude::*;
|
||||
use notify::{RecommendedWatcher, RecursiveMode};
|
||||
use notify_debouncer_mini::Debouncer;
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Cursor;
|
||||
@@ -124,7 +127,7 @@ impl ModLoader {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct JavaSettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub jre_key: Option<String>,
|
||||
pub override_version: Option<JavaVersion>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extra_arguments: Option<Vec<String>>,
|
||||
}
|
||||
@@ -173,36 +176,10 @@ impl Profile {
|
||||
semaphore: &IoSemaphore,
|
||||
icon: bytes::Bytes,
|
||||
file_name: &str,
|
||||
) -> crate::Result<&'a mut Self> {
|
||||
) -> crate::Result<()> {
|
||||
let file =
|
||||
write_cached_icon(file_name, cache_dir, icon, semaphore).await?;
|
||||
self.metadata.icon = Some(file);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub async fn sync_projects(&mut self) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
|
||||
let paths = self.get_profile_project_paths()?;
|
||||
let projects = crate::state::infer_data_from_files(
|
||||
self.clone(),
|
||||
paths,
|
||||
state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.projects = projects;
|
||||
|
||||
emit_profile(
|
||||
self.uuid,
|
||||
self.path.clone(),
|
||||
&self.metadata.name,
|
||||
ProfilePayloadType::Synced,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -228,6 +205,10 @@ impl Profile {
|
||||
if let Some(profile) = new_profiles.0.get_mut(&path) {
|
||||
profile.projects = projects;
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
"Unable to fetch single profile projects: path {path:?} invalid",
|
||||
);
|
||||
}
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
@@ -268,8 +249,44 @@ impl Profile {
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
pub async fn watch_fs(
|
||||
profile_path: &Path,
|
||||
watcher: &mut Debouncer<RecommendedWatcher>,
|
||||
) -> crate::Result<()> {
|
||||
async fn watch_path(
|
||||
profile_path: &Path,
|
||||
watcher: &mut Debouncer<RecommendedWatcher>,
|
||||
path: &str,
|
||||
) -> crate::Result<()> {
|
||||
let path = profile_path.join(path);
|
||||
|
||||
fs::create_dir_all(&path).await?;
|
||||
|
||||
watcher
|
||||
.watcher()
|
||||
.watch(&profile_path.join(path), RecursiveMode::Recursive)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
watch_path(profile_path, watcher, ProjectType::Mod.get_folder())
|
||||
.await?;
|
||||
watch_path(profile_path, watcher, ProjectType::ShaderPack.get_folder())
|
||||
.await?;
|
||||
watch_path(
|
||||
profile_path,
|
||||
watcher,
|
||||
ProjectType::ResourcePack.get_folder(),
|
||||
)
|
||||
.await?;
|
||||
watch_path(profile_path, watcher, ProjectType::DataPack.get_folder())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_project_version(
|
||||
&mut self,
|
||||
&self,
|
||||
version_id: String,
|
||||
) -> crate::Result<(PathBuf, ModrinthVersion)> {
|
||||
let state = State::get().await?;
|
||||
@@ -314,7 +331,7 @@ impl Profile {
|
||||
}
|
||||
|
||||
pub async fn add_project_bytes(
|
||||
&mut self,
|
||||
&self,
|
||||
file_name: &str,
|
||||
bytes: bytes::Bytes,
|
||||
project_type: Option<ProjectType>,
|
||||
@@ -354,16 +371,22 @@ impl Profile {
|
||||
write(&path, &bytes, &state.io_semaphore).await?;
|
||||
|
||||
let hash = get_hash(bytes).await?;
|
||||
{
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(&self.path) {
|
||||
profile.projects.insert(
|
||||
path.clone(),
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Unknown,
|
||||
file_name: file_name.to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.projects.insert(
|
||||
path.clone(),
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Unknown,
|
||||
file_name: file_name.to_string(),
|
||||
},
|
||||
);
|
||||
emit_profile(
|
||||
self.uuid,
|
||||
self.path.clone(),
|
||||
@@ -376,10 +399,19 @@ impl Profile {
|
||||
}
|
||||
|
||||
pub async fn toggle_disable_project(
|
||||
&mut self,
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> crate::Result<()> {
|
||||
if let Some(mut project) = self.projects.remove(path) {
|
||||
let state = State::get().await?;
|
||||
if let Some(mut project) = {
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(&self.path) {
|
||||
profile.projects.remove(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} {
|
||||
let path = path.to_path_buf();
|
||||
let mut new_path = path.clone();
|
||||
|
||||
@@ -395,7 +427,10 @@ impl Profile {
|
||||
|
||||
fs::rename(path, &new_path).await?;
|
||||
|
||||
self.projects.insert(new_path, project);
|
||||
let mut profiles = state.profiles.write().await;
|
||||
if let Some(profile) = profiles.0.get_mut(&self.path) {
|
||||
profile.projects.insert(new_path, project);
|
||||
}
|
||||
} else {
|
||||
return Err(crate::ErrorKind::InputError(format!(
|
||||
"Project path does not exist: {:?}",
|
||||
@@ -408,14 +443,19 @@ impl Profile {
|
||||
}
|
||||
|
||||
pub async fn remove_project(
|
||||
&mut self,
|
||||
&self,
|
||||
path: &Path,
|
||||
dont_remove_arr: Option<bool>,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
if self.projects.contains_key(path) {
|
||||
fs::remove_file(path).await?;
|
||||
if !dont_remove_arr.unwrap_or(false) {
|
||||
self.projects.remove(path);
|
||||
let mut profiles = state.profiles.write().await;
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(&self.path) {
|
||||
profile.projects.remove(path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(crate::ErrorKind::InputError(format!(
|
||||
@@ -430,8 +470,11 @@ impl Profile {
|
||||
}
|
||||
|
||||
impl Profiles {
|
||||
#[tracing::instrument]
|
||||
pub async fn init(dirs: &DirectoryInfo) -> crate::Result<Self> {
|
||||
#[tracing::instrument(skip(file_watcher))]
|
||||
pub async fn init(
|
||||
dirs: &DirectoryInfo,
|
||||
file_watcher: &mut Debouncer<RecommendedWatcher>,
|
||||
) -> crate::Result<Self> {
|
||||
let mut profiles = HashMap::new();
|
||||
fs::create_dir_all(dirs.profiles_dir()).await?;
|
||||
let mut entries = fs::read_dir(dirs.profiles_dir()).await?;
|
||||
@@ -449,6 +492,7 @@ impl Profiles {
|
||||
};
|
||||
if let Some(profile) = prof {
|
||||
let path = canonicalize(path)?;
|
||||
Profile::watch_fs(&path, file_watcher).await?;
|
||||
profiles.insert(path, profile);
|
||||
}
|
||||
}
|
||||
@@ -517,6 +561,11 @@ impl Profiles {
|
||||
ProfilePayloadType::Added,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let state = State::get().await?;
|
||||
let mut file_watcher = state.file_watcher.write().await;
|
||||
Profile::watch_fs(&profile.path, &mut file_watcher).await?;
|
||||
|
||||
self.0.insert(
|
||||
canonicalize(&profile.path)?
|
||||
.to_str()
|
||||
@@ -529,15 +578,6 @@ impl Profiles {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn insert_from<'a>(
|
||||
&'a mut self,
|
||||
path: &'a Path,
|
||||
) -> crate::Result<&Self> {
|
||||
self.insert(Self::read_profile_from_dir(&canonicalize(path)?).await?)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn remove(
|
||||
&mut self,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use dunce::canonicalize;
|
||||
use futures::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{collections::HashSet, path::Path};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::task::JoinError;
|
||||
|
||||
use crate::State;
|
||||
#[cfg(target_os = "windows")]
|
||||
use winreg::{
|
||||
enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_32KEY, KEY_WOW64_64KEY},
|
||||
@@ -19,6 +20,7 @@ use winreg::{
|
||||
pub struct JavaVersion {
|
||||
pub path: String,
|
||||
pub version: String,
|
||||
pub architecture: String,
|
||||
}
|
||||
|
||||
// Entrypoint function (Windows)
|
||||
@@ -30,6 +32,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
|
||||
// Add JRES directly on PATH
|
||||
jre_paths.extend(get_all_jre_path().await?);
|
||||
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
||||
|
||||
// Hard paths for locations for commonly installed .exes
|
||||
let java_paths = [r"C:/Program Files/Java", r"C:/Program Files (x86)/Java"];
|
||||
@@ -110,6 +113,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
|
||||
// Add JREs directly on PATH
|
||||
jre_paths.extend(get_all_jre_path().await?);
|
||||
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
||||
|
||||
// Hard paths for locations for commonly installed .exes
|
||||
let java_paths = [
|
||||
@@ -128,6 +132,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
jre_paths.insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Get JRE versions from potential paths concurrently
|
||||
let j = check_java_at_filepaths(jre_paths)
|
||||
.await?
|
||||
@@ -146,6 +151,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
|
||||
// Add JREs directly on PATH
|
||||
jre_paths.extend(get_all_jre_path().await?);
|
||||
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
||||
|
||||
// Hard paths for locations for commonly installed locations
|
||||
let java_paths = [
|
||||
@@ -177,6 +183,30 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
Ok(j)
|
||||
}
|
||||
|
||||
// Gets all JREs from the PATH env variable
|
||||
#[tracing::instrument]
|
||||
async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
||||
{
|
||||
let state = State::get().await.map_err(|_| JREError::StateError)?;
|
||||
|
||||
let mut jre_paths = HashSet::new();
|
||||
let base_path = state.directories.java_versions_dir();
|
||||
|
||||
if base_path.is_dir() {
|
||||
for entry in std::fs::read_dir(base_path)? {
|
||||
let entry = entry?;
|
||||
let file_path = entry.path().join("bin");
|
||||
let contents = std::fs::read_to_string(file_path)?;
|
||||
|
||||
let entry = entry.path().join(contents);
|
||||
println!("{:?}", entry);
|
||||
jre_paths.insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(jre_paths)
|
||||
}
|
||||
|
||||
// Gets all JREs from the PATH env variable
|
||||
#[tracing::instrument]
|
||||
async fn get_all_jre_path() -> Result<HashSet<PathBuf>, JREError> {
|
||||
@@ -231,26 +261,49 @@ pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Run 'java -version' using found java binary
|
||||
let output = Command::new(&java).arg("-version").output().ok()?;
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let mut file = NamedTempFile::new().ok()?;
|
||||
file.write_all(include_bytes!("../../library/JavaInfo.class"))
|
||||
.ok()?;
|
||||
|
||||
// Match: version "1.8.0_361"
|
||||
// Extracting version numbers
|
||||
lazy_static! {
|
||||
static ref JAVA_VERSION_CAPTURE: Regex =
|
||||
Regex::new(r#"version "([\d\._]+)""#)
|
||||
.expect("Error creating java version capture regex");
|
||||
let original_path = file.path().to_path_buf();
|
||||
let mut new_path = original_path.clone();
|
||||
new_path.set_file_name("JavaInfo");
|
||||
new_path.set_extension("class");
|
||||
tokio::fs::rename(&original_path, &new_path).await.ok()?;
|
||||
|
||||
// Run java checker on java binary
|
||||
let output = Command::new(&java)
|
||||
.arg("-cp")
|
||||
.arg(file.path().parent().unwrap())
|
||||
.arg("JavaInfo")
|
||||
.output()
|
||||
.ok()?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let mut java_version = None;
|
||||
let mut java_arch = None;
|
||||
|
||||
for line in stdout.lines() {
|
||||
let mut parts = line.split('=');
|
||||
let key = parts.next().unwrap_or_default();
|
||||
let value = parts.next().unwrap_or_default();
|
||||
|
||||
if key == "os.arch" {
|
||||
java_arch = Some(value);
|
||||
} else if key == "java.version" {
|
||||
java_version = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract version info from it
|
||||
if let Some(captures) = JAVA_VERSION_CAPTURE.captures(&stderr) {
|
||||
if let Some(version) = captures.get(1) {
|
||||
let Some(path) = java.to_str() else { return None };
|
||||
let path = path.to_string();
|
||||
if let Some(arch) = java_arch {
|
||||
if let Some(version) = java_version {
|
||||
let path = java.to_string_lossy().to_string();
|
||||
return Some(JavaVersion {
|
||||
path,
|
||||
version: version.as_str().to_string(),
|
||||
version: version.to_string(),
|
||||
architecture: arch.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -311,6 +364,9 @@ pub enum JREError {
|
||||
|
||||
#[error("No stored tag for Minecraft Version {0}")]
|
||||
NoMinecraftVersionFound(String),
|
||||
|
||||
#[error("Error getting launcher sttae")]
|
||||
StateError,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -8,27 +8,27 @@ pub trait OsExt {
|
||||
fn native() -> Self;
|
||||
|
||||
/// Gets the OS + Arch of the current system
|
||||
fn native_arch() -> Self;
|
||||
fn native_arch(java_arch: &str) -> Self;
|
||||
}
|
||||
|
||||
impl OsExt for Os {
|
||||
fn native_arch() -> Self {
|
||||
fn native_arch(java_arch: &str) -> Self {
|
||||
if std::env::consts::OS == "windows" {
|
||||
if std::env::consts::ARCH == "aarch64" {
|
||||
if java_arch == "aarch64" {
|
||||
Os::WindowsArm64
|
||||
} else {
|
||||
Os::Windows
|
||||
}
|
||||
} else if std::env::consts::OS == "linux" {
|
||||
if std::env::consts::ARCH == "aarch64" {
|
||||
if java_arch == "aarch64" {
|
||||
Os::LinuxArm64
|
||||
} else if std::env::consts::ARCH == "arm" {
|
||||
} else if java_arch == "arm" {
|
||||
Os::LinuxArm32
|
||||
} else {
|
||||
Os::Linux
|
||||
}
|
||||
} else if std::env::consts::OS == "macos" {
|
||||
if std::env::consts::ARCH == "aarch64" {
|
||||
if java_arch == "aarch64" {
|
||||
Os::OsxArm64
|
||||
} else {
|
||||
Os::Osx
|
||||
@@ -39,30 +39,6 @@ impl OsExt for Os {
|
||||
}
|
||||
|
||||
fn native() -> Self {
|
||||
if std::env::consts::OS == "windows" {
|
||||
if std::env::consts::ARCH == "aarch64" {
|
||||
Os::WindowsArm64
|
||||
} else {
|
||||
Os::Windows
|
||||
}
|
||||
} else if std::env::consts::OS == "linux" {
|
||||
if std::env::consts::ARCH == "aarch64" {
|
||||
Os::LinuxArm64
|
||||
} else if std::env::consts::ARCH == "arm" {
|
||||
Os::LinuxArm32
|
||||
} else {
|
||||
Os::Linux
|
||||
}
|
||||
} else if std::env::consts::OS == "macos" {
|
||||
if std::env::consts::ARCH == "aarch64" {
|
||||
Os::OsxArm64
|
||||
} else {
|
||||
Os::Osx
|
||||
}
|
||||
} else {
|
||||
Os::Unknown
|
||||
};
|
||||
|
||||
match std::env::consts::OS {
|
||||
"windows" => Self::Windows,
|
||||
"macos" => Self::Osx,
|
||||
@@ -80,7 +56,7 @@ pub const ARCH_WIDTH: &str = "64";
|
||||
pub const ARCH_WIDTH: &str = "32";
|
||||
|
||||
// Platform rule handling
|
||||
pub fn os_rule(rule: &OsRule) -> bool {
|
||||
pub fn os_rule(rule: &OsRule, java_arch: &str) -> bool {
|
||||
let mut rule_match = true;
|
||||
|
||||
if let Some(ref arch) = rule.arch {
|
||||
@@ -88,7 +64,8 @@ pub fn os_rule(rule: &OsRule) -> bool {
|
||||
}
|
||||
|
||||
if let Some(name) = &rule.name {
|
||||
rule_match &= &Os::native() == name || &Os::native_arch() == name;
|
||||
rule_match &=
|
||||
&Os::native() == name || &Os::native_arch(java_arch) == name;
|
||||
}
|
||||
|
||||
if let Some(version) = &rule.version {
|
||||
@@ -101,8 +78,8 @@ pub fn os_rule(rule: &OsRule) -> bool {
|
||||
rule_match
|
||||
}
|
||||
|
||||
pub fn classpath_separator() -> &'static str {
|
||||
match Os::native_arch() {
|
||||
pub fn classpath_separator(java_arch: &str) -> &'static str {
|
||||
match Os::native_arch(java_arch) {
|
||||
Os::Osx
|
||||
| Os::OsxArm64
|
||||
| Os::Linux
|
||||
|
||||
Reference in New Issue
Block a user