feat: Java downloader
This commit is contained in:
parent
aa6c3f333b
commit
2554d233f4
6 changed files with 231 additions and 107 deletions
|
|
@ -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<Vec<onelauncher::java::JavaZuluPackage>, String> {
|
||||
Ok(onelauncher::java::get_zulu_packages().await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn install_java_from_package(download: onelauncher::java::JavaZuluPackage) -> Result<std::path::PathBuf, String> {
|
||||
Ok(onelauncher::java::install_java_from_package(download).await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_featured_packages() -> Result<Vec<onelauncher::package::content::FeaturedPackage>, String> {
|
||||
|
|
|
|||
|
|
@ -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<string[]>;
|
||||
envVars: CreateSetting<[string, string][]>;
|
||||
}) {
|
||||
const modal = createModal(controller => (
|
||||
<Modal.Simple
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Close"
|
||||
onClick={controller.hide}
|
||||
/>,
|
||||
]}
|
||||
title="Java Versions"
|
||||
{...controller}
|
||||
>
|
||||
<div class="grid grid-cols-[80px_1fr] items-center gap-y-2">
|
||||
<For each={Object.entries(props.javaVersions?.get() ?? {}).sort((a, b) => Number.parseFloat(b[1].version) - Number.parseFloat(a[1].version))}>
|
||||
{([version, meta]) => {
|
||||
const prettified = version.toLowerCase().replaceAll('_', ' ');
|
||||
return (
|
||||
<>
|
||||
<span class="capitalize">{prettified}</span>
|
||||
<TextField
|
||||
onValidSubmit={(value) => {
|
||||
props.javaVersions?.set({ ...props.javaVersions.get(), [version]: { ...meta, path: value } });
|
||||
}}
|
||||
value={meta.path}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Modal.Simple>
|
||||
));
|
||||
const modal = createModal(controller => <JavaVersionModal {...controller} javaVersions={props.javaVersions} />);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -469,3 +441,101 @@ function PageSettings() {
|
|||
}
|
||||
|
||||
export default SettingsMinecraft;
|
||||
|
||||
function JavaVersionModal(props: ModalProps & {
|
||||
javaVersions: CreateSetting<JavaVersions> | undefined;
|
||||
}) {
|
||||
const [selectedPackageIndex, setSelectedPackageIndex] = createSignal<number>(-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 (
|
||||
<Modal.Simple
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Close"
|
||||
onClick={props.hide}
|
||||
/>,
|
||||
]}
|
||||
title="Java Versions"
|
||||
{...props}
|
||||
>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4>Default Java Paths</h4>
|
||||
<div class="grid grid-cols-[80px_1fr] items-center gap-y-2">
|
||||
<For each={Object.entries(props.javaVersions?.get() ?? {}).sort((a, b) => Number.parseFloat(b[1].version) - Number.parseFloat(a[1].version))}>
|
||||
{([version, meta]) => {
|
||||
const major = version.toLowerCase().replaceAll('_', ' ').replaceAll('java', '');
|
||||
|
||||
return (
|
||||
<>
|
||||
<span class="capitalize">
|
||||
Java
|
||||
{major}
|
||||
</span>
|
||||
<TextField
|
||||
onValidSubmit={(value) => {
|
||||
setPackage(Number.parseInt(major), { ...meta, path: value });
|
||||
}}
|
||||
value={meta.path}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4>Download Java</h4>
|
||||
<div class="w-full flex flex-row gap-2">
|
||||
<Dropdown class="w-full" onChange={setSelectedPackageIndex}>
|
||||
<For each={foundVersions()}>
|
||||
{version => (
|
||||
<Dropdown.Row>{version}</Dropdown.Row>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
|
||||
<Button
|
||||
children="Download"
|
||||
iconLeft={<Download01Icon />}
|
||||
onClick={() => download(zuluPackages()![selectedPackageIndex()]!)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Simple>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
21
packages/client/src/bindings.ts
generated
21
packages/client/src/bindings.ts
generated
|
|
@ -534,6 +534,26 @@ export const commands = {
|
|||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getZuluPackages(): Promise<Result<JavaZuluPackage[], string>> {
|
||||
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<Result<string, string>> {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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<u32>) -> crate::Result<Vec
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct JavaPackage {
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct JavaZuluPackage {
|
||||
pub download_url: String,
|
||||
pub name: PathBuf,
|
||||
pub java_version: Vec<u32>,
|
||||
}
|
||||
|
||||
pub async fn get_zulu_packages() -> crate::Result<Vec<JavaZuluPackage>> {
|
||||
let state = State::get().await?;
|
||||
fetch_json::<Vec<JavaZuluPackage>>(
|
||||
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<PathBuf> {
|
||||
pub async fn install_java_from_major(java_version: u32) -> crate::Result<PathBuf> {
|
||||
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<PathBuf> {
|
||||
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::<Vec<JavaPackage>>(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<Option<JavaVersion>> {
|
||||
|
|
@ -125,4 +144,4 @@ pub async fn test_java(path: PathBuf, major: u32) -> crate::Result<bool> {
|
|||
|
||||
let (maj, _) = get_java_version(&jvm.version)?;
|
||||
Ok(maj == major)
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue