From 2554d233f41358e1a20f0d0426c2fa2a942c381a Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:03:50 +0100 Subject: [PATCH] feat: Java downloader --- apps/desktop/src/api/commands/mod.rs | 14 ++ .../pages/settings/game/SettingsMinecraft.tsx | 142 ++++++++++++---- package.json | 2 +- packages/client/src/bindings.ts | 21 +++ packages/core/src/api/java.rs | 157 ++++++++++-------- packages/core/src/game/mod.rs | 2 +- 6 files changed, 231 insertions(+), 107 deletions(-) diff --git a/apps/desktop/src/api/commands/mod.rs b/apps/desktop/src/api/commands/mod.rs index 761a54a7..d62b8dd4 100644 --- a/apps/desktop/src/api/commands/mod.rs +++ b/apps/desktop/src/api/commands/mod.rs @@ -83,10 +83,24 @@ macro_rules! collect_commands { set_window_style, get_program_info, get_featured_packages, + get_zulu_packages, + install_java_from_package, ] }}; } +#[specta::specta] +#[tauri::command] +pub async fn get_zulu_packages() -> Result, String> { + Ok(onelauncher::java::get_zulu_packages().await?) +} + +#[specta::specta] +#[tauri::command] +pub async fn install_java_from_package(download: onelauncher::java::JavaZuluPackage) -> Result { + Ok(onelauncher::java::install_java_from_package(download).await?) +} + #[specta::specta] #[tauri::command] pub async fn get_featured_packages() -> Result, String> { diff --git a/apps/frontend/src/ui/pages/settings/game/SettingsMinecraft.tsx b/apps/frontend/src/ui/pages/settings/game/SettingsMinecraft.tsx index 5b539025..c19f1efc 100644 --- a/apps/frontend/src/ui/pages/settings/game/SettingsMinecraft.tsx +++ b/apps/frontend/src/ui/pages/settings/game/SettingsMinecraft.tsx @@ -1,15 +1,18 @@ -import type { JavaVersion, JavaVersions, Memory, Resolution } from '@onelauncher/client/bindings'; -import { ActivityIcon, CpuChip01Icon, Database01Icon, EyeIcon, FilePlus02Icon, FileX02Icon, LayoutTopIcon, Maximize01Icon, ParagraphWrapIcon, VariableIcon, XIcon } from '@untitled-theme/icons-solid'; +import type { JavaVersion, JavaVersions, JavaZuluPackage, Memory, Resolution } from '@onelauncher/client/bindings'; +import { ActivityIcon, CpuChip01Icon, Database01Icon, Download01Icon, EyeIcon, FilePlus02Icon, FileX02Icon, LayoutTopIcon, Maximize01Icon, ParagraphWrapIcon, VariableIcon, XIcon } from '@untitled-theme/icons-solid'; +import { bridge } from '~imports'; import Button from '~ui/components/base/Button'; +import Dropdown from '~ui/components/base/Dropdown'; import TextField from '~ui/components/base/TextField'; import Toggle from '~ui/components/base/Toggle'; -import Modal, { createModal } from '~ui/components/overlay/Modal'; +import Modal, { createModal, type ModalProps } from '~ui/components/overlay/Modal'; import ScrollableContainer from '~ui/components/ScrollableContainer'; import BaseSettingsRow, { type SettingsRowProps } from '~ui/components/SettingsRow'; import Sidebar from '~ui/components/Sidebar'; +import { tryResult } from '~ui/hooks/useCommand'; import useSettings from '~ui/hooks/useSettings'; import { asEnvVariables } from '~utils'; -import { type Accessor, createSignal, For, onMount, type Setter, Show, splitProps, untrack } from 'solid-js'; +import { type Accessor, createMemo, createResource, createSignal, For, onMount, type Setter, Show, splitProps, untrack } from 'solid-js'; function SettingsMinecraft() { return ( @@ -298,38 +301,7 @@ export function JvmSettings(props: { javaArgs: CreateSetting; envVars: CreateSetting<[string, string][]>; }) { - const modal = createModal(controller => ( - , - ]} - title="Java Versions" - {...controller} - > -
- Number.parseFloat(b[1].version) - Number.parseFloat(a[1].version))}> - {([version, meta]) => { - const prettified = version.toLowerCase().replaceAll('_', ' '); - return ( - <> - {prettified} - { - props.javaVersions?.set({ ...props.javaVersions.get(), [version]: { ...meta, path: value } }); - }} - value={meta.path} - /> - - ); - }} - -
-
- )); + const modal = createModal(controller => ); return ( <> @@ -469,3 +441,101 @@ function PageSettings() { } export default SettingsMinecraft; + +function JavaVersionModal(props: ModalProps & { + javaVersions: CreateSetting | undefined; +}) { + const [selectedPackageIndex, setSelectedPackageIndex] = createSignal(-1); + + const [zuluPackages] = createResource(async () => { + try { + return await tryResult(bridge.commands.getZuluPackages); + } + catch (e) { + console.error(e); + return []; + } + }); + + const foundVersions = createMemo(() => { + return zuluPackages()?.map(pkg => pkg.java_version.join('.')) ?? []; + }); + + const setPackage = (version: number, pkg: JavaVersion) => { + props.javaVersions?.set({ + ...props.javaVersions?.get(), + [`JAVA_${version}`]: pkg, + }); + }; + + const download = async (pkg: JavaZuluPackage) => { + const path = await tryResult(() => bridge.commands.installJavaFromPackage(pkg)); + if (path) + setPackage(pkg.java_version[0]!, { + version: pkg.java_version.join('.'), + path, + arch: '', + }); + }; + + return ( + , + ]} + title="Java Versions" + {...props} + > +
+
+

Default Java Paths

+
+ Number.parseFloat(b[1].version) - Number.parseFloat(a[1].version))}> + {([version, meta]) => { + const major = version.toLowerCase().replaceAll('_', ' ').replaceAll('java', ''); + + return ( + <> + + Java + {major} + + { + setPackage(Number.parseInt(major), { ...meta, path: value }); + }} + value={meta.path} + /> + + ); + }} + +
+
+ +
+

Download Java

+
+ + + {version => ( + {version} + )} + + + +
+
+
+
+ ); +} diff --git a/package.json b/package.json index 7647526f..9db2c3e4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "1.0.0-alpha.1", "private": true, "engineStrict": true, - "packageManager": "pnpm@9.12.2", + "packageManager": "pnpm@9.15.0", "description": "Next-generation open source Minecraft launcher and tools for interacting with Polyfrost", "license": "GPL-3.0-only", "homepage": "https://polyfrost.org", diff --git a/packages/client/src/bindings.ts b/packages/client/src/bindings.ts index 026058e8..c04e5716 100644 --- a/packages/client/src/bindings.ts +++ b/packages/client/src/bindings.ts @@ -534,6 +534,26 @@ export const commands = { else return { status: 'error', error: e as any }; } }, + async getZuluPackages(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_zulu_packages') }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, + async installJavaFromPackage(download: JavaZuluPackage): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('install_java_from_package', { download }) }; + } + catch (e) { + if (e instanceof Error) + throw e; + else return { status: 'error', error: e as any }; + } + }, }; /** user-defined events */ @@ -783,6 +803,7 @@ export interface JavaVersion { version: string; arch: string; path: string }; * A `HashMap` of all located and installed available Java versions */ export type JavaVersions = { [key in string]: JavaVersion }; +export interface JavaZuluPackage { download_url: string; name: string; java_version: number[] }; export interface License { id?: string; name?: string; url?: string | null }; /** * Available mod loaders to be used for a cluster. diff --git a/packages/core/src/api/java.rs b/packages/core/src/api/java.rs index 43041cd6..8e8e289a 100644 --- a/packages/core/src/api/java.rs +++ b/packages/core/src/api/java.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use reqwest::Method; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use crate::proxy::send::{init_ingress, send_ingress}; use crate::utils::http::{fetch_advanced, fetch_json}; @@ -23,95 +23,114 @@ pub async fn filter_java_version(java_version: Option) -> crate::Result, +} + +pub async fn get_zulu_packages() -> crate::Result> { + let state = State::get().await?; + fetch_json::>( + Method::GET, + format!( + "https://api.azul.com/metadata/v1/zulu/packages/?os={}&arch={}&archive_type=zip&java_package_type=jre&javafx_bundled=false&latest=true&release_status=ga&availability_types=CA&certifications=tck&page=1&page_size=100", + std::env::consts::OS, + std::env::consts::ARCH + ).as_str(), + None, + None, + &state.fetch_semaphore, + ) + .await } // TODO: support more than just zulu ? #[onelauncher_macros::memory] -pub async fn install_java(java_version: u32) -> crate::Result { +pub async fn install_java_from_major(java_version: u32) -> crate::Result { + let packages = get_zulu_packages().await?; + let package = packages + .into_iter() + .find(|p| p.java_version.contains(&java_version)) + .ok_or(anyhow::anyhow!( + "Could not find a java package for version {}", + java_version + ))?; + + install_java_from_package(package).await +} + +#[onelauncher_macros::memory] +pub async fn install_java_from_package(download: JavaZuluPackage) -> crate::Result { let state = State::get().await?; let ingress = init_ingress( crate::IngressType::DownloadJava { - version: java_version, + version: *download.java_version.get(0).unwrap_or(&0), }, 100.0, "downloading java version", ) .await?; - send_ingress(&ingress, 0.0, Some("fetching java api")).await?; - let packages = fetch_json::>(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?; - send_ingress(&ingress, 10.0, Some("downloading java version")).await?; + send_ingress(&ingress, 0.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((&ingress, 80.0)), - &state.fetch_semaphore, - ) - .await?; + let file = fetch_advanced( + Method::GET, + &download.download_url, + None, + None, + None, + Some((&ingress, 80.0)), + &state.fetch_semaphore, + ) + .await?; - let path = state.directories.java_dir().await; - let mut archive = - zip::ZipArchive::new(std::io::Cursor::new(file)).map_err(IOError::from_zip)?; + let path = state.directories.java_dir().await; + let mut archive = + zip::ZipArchive::new(std::io::Cursor::new(file)).map_err(IOError::from_zip)?; - if let Some(file) = archive.file_names().next() { - if let Some(dir) = file.split('/').next() { - let path = path.join(dir); - if path.exists() { - io::remove_dir_all(path).await?; - } + if let Some(file) = archive.file_names().next() { + if let Some(dir) = file.split('/').next() { + let path = path.join(dir); + if path.exists() { + io::remove_dir_all(path).await?; } } - - send_ingress(&ingress, 0.0, Some("extracing java binary")).await?; - archive.extract(&path).map_err(IOError::from_zip)?; - send_ingress(&ingress, 10.0, Some("extracted java binary")).await?; - let mut base_path = path.join( - download - .name - .file_stem() - .unwrap_or_default() - .to_string_lossy() - .to_string(), - ); - - #[cfg(target_os = "macos")] - { - base_path = base_path - .join(format!("zulu-{java_version}.jre")) - .join("Contents") - .join("Home") - .join("bin") - .join("java"); - } - - #[cfg(not(target_os = "macos"))] - { - base_path = base_path.join("bin").join(crate::constants::JAVA_BIN) - } - - Ok(base_path) - } else { - Err(anyhow::anyhow!( - "no java version found for version {}, os {}, and arch {}", - java_version, - std::env::consts::OS, - std::env::consts::ARCH, - ) - .into()) } + + send_ingress(&ingress, 0.0, Some("extracing java binary")).await?; + archive.extract(&path).map_err(IOError::from_zip)?; + send_ingress(&ingress, 10.0, Some("extracted java binary")).await?; + let mut base_path = path.join( + download + .name + .file_stem() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + ); + + #[cfg(target_os = "macos")] + { + base_path = base_path + .join(format!("zulu-{java_version}.jre")) + .join("Contents") + .join("Home") + .join("bin") + .join("java"); + } + + #[cfg(not(target_os = "macos"))] + { + base_path = base_path.join("bin").join(crate::constants::JAVA_BIN) + } + + send_ingress(&ingress, 100.0, Some("installed java binary")).await?; + + Ok(base_path) } pub async fn check_java(path: PathBuf) -> crate::Result> { @@ -125,4 +144,4 @@ pub async fn test_java(path: PathBuf, major: u32) -> crate::Result { let (maj, _) = get_java_version(&jvm.version)?; Ok(maj == major) -} +} \ No newline at end of file diff --git a/packages/core/src/game/mod.rs b/packages/core/src/game/mod.rs index 284db8e9..bc34d913 100644 --- a/packages/core/src/game/mod.rs +++ b/packages/core/src/game/mod.rs @@ -112,7 +112,7 @@ pub async fn install_minecraft( if let Some(java_version) = java_version_from_cluster(cluster, &version_info).await? { (std::path::PathBuf::from(java_version.path), false) } else { - (crate::api::java::install_java(key).await?, true) + (crate::api::java::install_java_from_major(key).await?, true) }; let java_version = crate::api::java::check_java(java_version.clone())