feat: Java 25+ is now downloadable

fix(ui): Scrollviews with no scrollable areas capturing scroll wheel events
This commit is contained in:
LynithDev 2026-07-12 17:38:36 +02:00
parent 68c8762b08
commit 1c8db8ceff
14 changed files with 498 additions and 100 deletions

View file

@ -432,11 +432,7 @@ impl CoreBridgeRuntime {
)
.await
{
Ok(runtime) => {
notifier.send_info(
"Java installed",
&format!("Java {} ({})", runtime.major, runtime.vendor),
);
Ok(_) => {
notifier.invalidate_java();
}
Err(err) => {

View file

@ -59,18 +59,50 @@ impl Component for Dropdown {
let mut open = use_state(|| false);
let mut hovering = use_state(|| false);
let mut button_area = use_state(|| None::<Area>);
let mut list_size = use_state(|| None::<Size2D>);
let selected = self.selected.clone();
let options = self.options.clone();
let on_select = self.on_select.clone();
let list_width = self.width.clone();
let is_open = open();
if !is_open && list_size().is_some() {
let _ = list_size.take();
}
let trigger_bg = if hovering() {
colors::ghost_overlay_hover()
} else {
colors::ghost_overlay()
};
let visible = options.len().min(MAX_VISIBLE_OPTIONS);
let list_h = visible as f32 * OPTION_HEIGHT
+ visible.saturating_sub(1) as f32 * OPTION_SPACING;
let offset_y = match (button_area(), list_size()) {
(Some(button), Some(list)) => {
let root_height = Platform::get().root_size.peek().height;
let space_below = root_height - button.max_y();
let space_above = button.min_y();
if list.height > space_below && list.height <= space_above {
-(button.height() + list.height + 8.)
} else {
0.
}
}
_ => 0.,
};
let list_width = button_area()
.map(|b| Size::px(b.width()))
.unwrap_or_else(|| self.width.clone());
let on_global_pointer_press = move |_: Event<PointerEventData>| {
open.set_if_modified(false);
};
rect()
.width(self.width.clone())
.child(
@ -85,6 +117,10 @@ impl Component for Dropdown {
.background(trigger_bg)
.on_pointer_enter(move |_| hovering.set(true))
.on_pointer_leave(move |_| hovering.set(false))
.on_global_pointer_press(on_global_pointer_press)
.on_sized(move |e: Event<SizedEventData>| {
button_area.set_if_modified(Some(e.area));
})
.on_press(move |e: Event<PressEventData>| {
e.prevent_default();
e.stop_propagation();
@ -103,44 +139,41 @@ impl Component for Dropdown {
),
)
.maybe_child(is_open.then(|| {
let visible = options.len().min(MAX_VISIBLE_OPTIONS);
let list_h = visible as f32 * OPTION_HEIGHT
+ visible.saturating_sub(1) as f32 * OPTION_SPACING;
rect().height(Size::px(0.)).width(list_width)
.layer(Layer::Overlay)
.child(
rect()
.layer(Layer::RelativeOverlay(10))
.position(Position::new_global().top(0.).left(0.))
.width(Size::window_percent(100.))
.height(Size::window_percent(100.))
.on_press(move |_| open.set(false)),
)
.child(
rect().width(Size::px(0.)).height(Size::px(0.)).child(
rect()
.width(Size::fill())
.layer(Layer::Overlay)
.width(list_width)
.margin(Gaps::new(4., 0., 0., 0.))
.layer(Layer::RelativeOverlay(12))
.padding(4.)
.corner_radius(CornerRadius::new_all(8.))
.border(border_all_color(1., colors::component_border()))
.background(colors::page_elevated())
.overflow(Overflow::Clip)
.offset_y(offset_y)
.content(Content::Fit)
.on_sized(move |e: Event<SizedEventData>| {
list_size.set_if_modified(Some(e.area.size));
})
.child(
ScrollArea::new()
.height(Size::px(list_h))
.spacing(OPTION_SPACING)
.children(options.into_iter().enumerate().map(|(idx, option)| {
let on_select = on_select.clone();
DropdownOption::new(option, move |_| {
if let Some(handler) = &on_select {
handler.call(idx);
}
open.set(false);
})
.into_element()
})),
rect()
.width(Size::fill())
.padding(4.)
.corner_radius(CornerRadius::new_all(8.))
.border(border_all_color(1., colors::component_border()))
.background(colors::page_elevated())
.overflow(Overflow::Clip)
.child(
ScrollArea::new()
.height(Size::px(list_h))
.spacing(OPTION_SPACING)
.children(options.into_iter().enumerate().map(
|(idx, option)| {
let on_select = on_select.clone();
DropdownOption::new(option, move |_| {
if let Some(handler) = &on_select {
handler.call(idx);
}
open.set(false);
})
.into_element()
},
)),
),
),
)
}))

View file

@ -42,7 +42,7 @@ pub(crate) fn scrollbar_pos_and_size(inner: f32, viewport: f32, scroll: f32) ->
let scroll_range = inner - viewport;
let thumb_range = viewport - thumb;
let normalized = -scroll / scroll_range;
(normalized * thumb_range, thumb)
}
@ -307,7 +307,17 @@ impl ScrollArea {
let us_thumb = on_user_scroll.clone();
let us_h_thumb = on_user_scroll;
let can_scroll_v = vp_h > 0. && vp_h < ct_h;
let can_scroll_h = horizontal && vp_w > 0. && vp_w < content_w;
let on_wheel = move |e: Event<WheelEventData>| {
let wants_h = horizontal && *shift_held.read();
// check if scrollable
if (wants_h && !can_scroll_h) || !can_scroll_v {
return;
}
e.stop_propagation();
if (e.delta_y != 0.0 || e.delta_x != 0.0)
@ -316,7 +326,7 @@ impl ScrollArea {
cb.call(());
}
if horizontal && *shift_held.read() {
if wants_h {
let delta = if e.delta_x != 0.0 { e.delta_x } else { e.delta_y };
if delta != 0.0 {
let cur_x = corrected_scroll(content_w, vp_w, *scroll_x.read());

View file

@ -15,6 +15,8 @@ fn providers() -> Vec<(JavaVendor, &'static str)> {
vec![
(JavaVendor::Zulu, "Azul Zulu"),
(JavaVendor::Adoptium, "Eclipse Temurin"),
(JavaVendor::Corretto, "Amazon Corretto"),
(JavaVendor::Liberica, "BellSoft Liberica"),
]
}
@ -209,18 +211,31 @@ impl Component for RuntimeRow {
.child(
rect()
.width(Size::flex(1.0))
.overflow(Overflow::Clip)
.child(
label()
.text(runtime.absolute_path.clone())
.font_size(12.)
.max_lines(1)
.color(colors::fg_secondary()),
ScrollArea::new()
.horizontal(path_content_width(&runtime.absolute_path))
.width(Size::fill())
.height(Size::px(18.))
.show_scrollbar(false)
.child(
label()
.text(runtime.absolute_path.clone())
.font_size(12.)
.max_lines(1)
.color(colors::fg_secondary())
.into_element(),
),
),
)
.child(remove_button(dispatch, path))
}
}
fn path_content_width(path: &str) -> f32 {
(path.chars().count() as f32 * 7.0).max(1.0)
}
fn remove_button(dispatch: BridgeDispatch, path: String) -> impl IntoElement {
Button::new()
.ghost()

View file

@ -4,7 +4,9 @@ use std::env;
use oneclient_core::dev;
use oneclient_core::java::check_java_runtime;
use oneclient_core::java::vendors::AdoptiumRuntimeProvider;
use oneclient_core::java::vendors::CorrettoRuntimeProvider;
use oneclient_core::java::vendors::JavaRuntimeProvider;
use oneclient_core::java::vendors::LibericaRuntimeProvider;
use oneclient_core::java::vendors::ZuluRuntimeProvider;
use oneclient_core::logger;
use oneclient_core::LauncherResult;
@ -18,7 +20,7 @@ async fn main() -> LauncherResult<()> {
let vendor = args
.next()
.unwrap_or_else(|| {
eprintln!("usage: java_install_provider <zulu|adoptium|corretto> [major]");
eprintln!("usage: java_install_provider <zulu|adoptium|corretto|liberica> [major]");
std::process::exit(1);
});
@ -33,13 +35,15 @@ async fn main() -> LauncherResult<()> {
let provider: Box<dyn JavaRuntimeProvider> = match vendor.as_str() {
"zulu" => Box::new(ZuluRuntimeProvider),
"adoptium" => Box::new(AdoptiumRuntimeProvider),
"corretto" => Box::new(CorrettoRuntimeProvider),
"liberica" => Box::new(LibericaRuntimeProvider),
other => {
eprintln!("unknown vendor '{other}', use zulu, adoptium, or corretto");
eprintln!("unknown vendor '{other}', use zulu, adoptium, corretto, or liberica");
std::process::exit(1);
}
};
let packages = provider.list_packages_by_major(major, &services).await?;
let packages = provider.list_packages(Some(major), &services).await?;
let package = packages
.into_iter()
.find(|p| p.java_version.contains(&major))

View file

@ -10,19 +10,25 @@ use oneclient_core::logger;
async fn main() -> LauncherResult<()> {
logger::init_debug()?;
let major: u32 = env::args()
.nth(1)
.unwrap_or_else(|| "21".to_string())
.parse()
.expect("usage: java_list_providers [major_version]");
let arg = env::args().nth(1).unwrap_or_else(|| "21".to_string());
let major: Option<u32> = if arg == "all" {
None
} else {
Some(arg.parse().expect("usage: java_list_providers [major|all]"))
};
let services = dev::ephemeral_services().await?;
println!("Listing JRE packages for Java {major} (metadata only):\n");
match major {
Some(major) => println!("Listing JRE packages for Java {major} (metadata only):\n"),
None => println!("Listing JRE packages for all majors (metadata only):\n"),
}
for provider in runtime_providers() {
let packages = provider.list_packages_by_major(major, &services).await?;
print_vendor(provider.vendor(), &packages);
match provider.list_packages(major, &services).await {
Ok(packages) => print_vendor(provider.vendor(), &packages),
Err(err) => println!("{:?}: ERROR {err}", provider.vendor()),
}
}
Ok(())

View file

@ -13,6 +13,8 @@ pub struct ResponseOptions {
pub struct ResponseNotifyOptions {
child: Option<GroupedProgressChild>,
standalone_label: Option<String>,
standalone_id: Option<Uuid>,
done_label: Option<String>,
}
impl ResponseNotifyOptions {
@ -20,6 +22,8 @@ impl ResponseNotifyOptions {
Self {
child: Some(child),
standalone_label: None,
standalone_id: None,
done_label: None,
}
}
@ -27,8 +31,20 @@ impl ResponseNotifyOptions {
Self {
child: None,
standalone_label: Some(label.into()),
standalone_id: None,
done_label: None,
}
}
pub fn with_id(mut self, id: Uuid) -> Self {
self.standalone_id = Some(id);
self
}
pub fn done_label(mut self, label: impl Into<String>) -> Self {
self.done_label = Some(label.into());
self
}
}
#[async_trait::async_trait]
@ -59,7 +75,14 @@ impl ResponseExt for Response {
.notify
.as_ref()
.and_then(|n| n.standalone_label.clone());
let standalone_id = standalone_label.as_ref().map(|_| Uuid::new_v4());
let done_label = options
.notify
.as_ref()
.and_then(|n| n.done_label.clone());
let standalone_id = standalone_label
.as_ref()
.map(|_| options.notify.as_ref().and_then(|n| n.standalone_id).unwrap_or_else(Uuid::new_v4));
if let Some(ref child) = grouped_child {
child.set_progress(0, Some(total));
@ -67,6 +90,7 @@ impl ResponseExt for Response {
notifier.send_progress(id, label, 0, total);
}
let notifier = notifier.clone();
let stream = futures_lite::StreamExt::map(self.bytes_stream(), move |item| {
match item {
Ok(chunk) => {
@ -75,6 +99,12 @@ impl ResponseExt for Response {
if let Some(ref child) = grouped_child {
child.set_progress(current, Some(total));
} else if let (Some(id), Some(label)) = (&standalone_id, &standalone_label) {
let done = current >= total;
let label = if done {
done_label.as_deref().unwrap_or(label)
} else {
label
};
notifier.send_progress(id, label, current, total);
}

View file

@ -22,16 +22,19 @@ pub async fn install_package(
let archive_path = java_dir.join(polyio::sanitize_path(&package.name));
let major = package.java_version.first().copied().unwrap_or(0);
services.requester
.download_file(
reqwest::Request::new(Method::GET, Url::parse(&package.download_url)?),
&archive_path,
ResponseOptions {
notify: Some(ResponseNotifyOptions::standalone(format!(
"Installing {} {}",
package.vendor,
package.java_version.first().copied().unwrap_or(0)
))),
notify: Some(
ResponseNotifyOptions::standalone(format!(
"Installing {} {}",
package.vendor, major
))
.done_label(format!("Installed {} {}", package.vendor, major)),
),
},
services,
)

View file

@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
@ -92,23 +93,23 @@ impl JavaManager {
return Ok(Vec::new());
};
let results = futures_util::future::join_all(INSTALLABLE_MAJORS.iter().map(|&major| {
let provider = &provider;
async move {
(major, provider.latest_package_by_major(major, services).await)
}
}))
.await;
let packages = provider.list_packages(None, services).await?;
let mut available = Vec::new();
for (major, result) in results {
match result {
Ok(Some(package)) => available.push(AvailableJava { major, package }),
Ok(None) => {}
Err(err) => tracing::warn!(?vendor, major, "failed to query packages: {err}"),
}
let mut by_major =
BTreeMap::<u32, JavaPackage>::new();
for package in packages {
let Some(&major) = package.java_version.first() else {
continue;
};
by_major.entry(major).or_insert(package);
}
let mut available: Vec<AvailableJava> = by_major
.into_iter()
.map(|(major, package)| AvailableJava { major, package })
.collect();
available.sort_by_key(|b| std::cmp::Reverse(b.major));
Ok(available)
@ -171,10 +172,10 @@ async fn download_and_register(
for provider in vendors::runtime_providers() {
let vendor = provider.vendor();
let packages = provider.list_packages_by_major(major, services).await;
let packages = provider.list_packages(Some(major), services).await;
let package = match &packages {
Ok(packages) => match packages.first() {
Ok(packages) => match packages.iter().find(|p| p.java_version.first() == Some(&major)).or_else(|| packages.first()) {
Some(package) => package,
None => {
tracing::warn!(?vendor, major, "no packages found");

View file

@ -10,6 +10,12 @@ use crate::state::LauncherServices;
pub struct AdoptiumRuntimeProvider;
// `/v3/info/available_releases`
#[derive(Debug, Deserialize)]
struct AdoptiumAvailableReleases {
available_releases: Vec<u32>,
}
#[derive(Debug, Deserialize)]
struct AdoptiumRelease {
version_data: AdoptiumVersionData,
@ -19,6 +25,7 @@ struct AdoptiumRelease {
#[derive(Debug, Deserialize)]
struct AdoptiumVersionData {
semver: String,
major: u32,
}
#[derive(Debug, Deserialize)]
@ -38,30 +45,50 @@ impl JavaRuntimeProvider for AdoptiumRuntimeProvider {
JavaVendor::Adoptium
}
async fn list_packages_by_major(
async fn list_packages(
&self,
major: u32,
major: Option<u32>,
services: &LauncherServices,
) -> LauncherResult<Vec<JavaPackage>> {
let url = adoptium_url(major)?;
// fetched from `/v3/info/available_releases`.
let (low, high) = match major {
Some(major) => (major, major),
None => {
let info = services
.requester
.send_as::<AdoptiumAvailableReleases>(Request::new(
Method::GET,
Url::parse("https://api.adoptium.net/v3/info/available_releases")?,
))
.await?;
let low = info.available_releases.iter().copied().min().unwrap_or(8);
let high = info.available_releases.iter().copied().max().unwrap_or(low);
(low, high)
}
};
let url = adoptium_url(low, high)?;
let releases = services
.requester
.send_as::<Vec<AdoptiumRelease>>(Request::new(Method::GET, url))
.await?;
let mut packages = Vec::new();
for release in releases {
let java_version: Vec<u32> = release
let mut java_version: Vec<u32> = release
.version_data
.semver
.split(|c: char| !c.is_numeric())
.filter_map(|s| s.parse::<u32>().ok())
.collect();
if java_version.first() != Some(&release.version_data.major) {
java_version.insert(0, release.version_data.major);
}
for binary in release.binaries {
packages.push(JavaPackage {
archive: PackageArchive::Zip,
archive: PackageArchive::from_filename(&binary.package.name),
download_url: binary.package.link,
java_version: java_version.clone(),
name: binary.package.name,
@ -74,10 +101,29 @@ impl JavaRuntimeProvider for AdoptiumRuntimeProvider {
}
}
fn adoptium_url(major: u32) -> LauncherResult<Url> {
Ok(Url::parse(&format!(
"https://api.adoptium.net/v3/assets/feature_releases/{major}/ga?os={ADOPTIUM_OS}&architecture={ADOPTIUM_ARCH}&image_type=jre&jvm_impl=hotspot&project=jdk&heap_size=normal&vendor=eclipse&page=0&page_size=5"
))?)
// One request spanning every GA major via a version range (`[low,high]`),
// newest build per major, instead of one request per major.
fn adoptium_url(low: u32, high: u32) -> LauncherResult<Url> {
let range = format!("%5B{low}%2C{}%5D", high + 1);
let mut url = Url::parse(&format!(
"https://api.adoptium.net/v3/assets/version/{range}"
))?;
url.query_pairs_mut()
.append_pair("os", ADOPTIUM_OS)
.append_pair("architecture", ADOPTIUM_ARCH)
.append_pair("image_type", "jre")
.append_pair("jvm_impl", "hotspot")
.append_pair("project", "jdk")
.append_pair("heap_size", "normal")
.append_pair("vendor", "eclipse")
.append_pair("release_type", "ga")
.append_pair("sort_method", "DATE")
.append_pair("sort_order", "DESC")
.append_pair("page", "0")
.append_pair("page_size", "50");
Ok(url)
}
const ADOPTIUM_ARCH: &str = cfg_select! {
@ -89,7 +135,7 @@ const ADOPTIUM_ARCH: &str = cfg_select! {
const ADOPTIUM_OS: &str = cfg_select! {
target_os = "windows" => "windows",
target_os = "macos" => "mac",
target_os = "macos" => "mac",
target_os = "linux" => cfg_select! {
target_env = "musl" => "alpine-linux",
_ => "linux",

View file

@ -0,0 +1,103 @@
use reqwest::{Method, Request};
use serde_json::Value;
use url::Url;
use crate::LauncherResult;
use crate::java::PackageArchive;
use crate::java::data::JavaPackage;
use crate::java::vendors::{JavaRuntimeProvider, JavaVendor};
use crate::state::LauncherServices;
pub struct CorrettoRuntimeProvider;
// { os: { arch: { "jdk": { major: { ext: ... } } } } }
const INDEX_URL: &str =
"https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json";
#[async_trait::async_trait]
impl JavaRuntimeProvider for CorrettoRuntimeProvider {
fn vendor(&self) -> JavaVendor {
JavaVendor::Corretto
}
async fn list_packages(
&self,
major: Option<u32>,
services: &LauncherServices,
) -> LauncherResult<Vec<JavaPackage>> {
let index = services
.requester
.send_as::<Value>(Request::new(Method::GET, Url::parse(INDEX_URL)?))
.await?;
// Corretto ships JDK only
let Some(by_major) = index
.get(CORRETTO_OS)
.and_then(|by_arch| by_arch.get(CORRETTO_ARCH))
.and_then(|by_type| by_type.get("jdk"))
.and_then(Value::as_object)
else {
return Ok(Vec::new());
};
let mut packages = Vec::new();
for (major_str, by_ext) in by_major {
let Ok(this_major) = major_str.parse::<u32>() else {
continue;
};
if let Some(filter) = major
&& filter != this_major
{
continue;
}
let has_ext = by_ext
.as_object()
.is_some_and(|exts| exts.contains_key(CORRETTO_EXT.0));
if !has_ext {
continue;
}
let download_url = latest_url(this_major, CORRETTO_EXT.0);
let name = format!(
"amazon-corretto-{this_major}-{CORRETTO_ARCH}-{CORRETTO_OS}-jdk.{}",
CORRETTO_EXT.0
);
packages.push(JavaPackage {
archive: CORRETTO_EXT.1,
download_url,
java_version: vec![this_major],
name,
vendor: JavaVendor::Corretto,
});
}
packages.sort_by_key(|p| std::cmp::Reverse(p.java_version.first().copied().unwrap_or(0)));
Ok(packages)
}
}
fn latest_url(major: u32, ext: &str) -> String {
format!(
"https://corretto.aws/downloads/latest/amazon-corretto-{major}-{CORRETTO_ARCH}-{CORRETTO_OS}-jdk.{ext}"
)
}
const CORRETTO_ARCH: &str = cfg_select! {
target_arch = "x86" => "x86",
target_arch = "x86_64" => "x64",
target_arch = "arm" => "arm",
target_arch = "aarch64" => "aarch64",
};
const CORRETTO_OS: &str = cfg_select! {
target_os = "windows" => "windows",
target_os = "macos" => "macos",
target_os = "linux" => "linux",
};
const CORRETTO_EXT: (&str, PackageArchive) = cfg_select! {
target_os = "windows" => ("zip", PackageArchive::Zip),
_ => ("tar.gz", PackageArchive::TarGz),
};

View file

@ -0,0 +1,123 @@
use reqwest::{Method, Request};
use serde::Deserialize;
use url::Url;
use crate::LauncherResult;
use crate::java::PackageArchive;
use crate::java::data::JavaPackage;
use crate::java::vendors::{JavaRuntimeProvider, JavaVendor};
use crate::state::LauncherServices;
pub struct LibericaRuntimeProvider;
#[derive(Debug, Deserialize)]
struct LibericaRelease {
#[serde(rename = "downloadUrl")]
download_url: String,
filename: String,
#[serde(rename = "featureVersion")]
feature_version: u32,
#[serde(rename = "updateVersion", default)]
update_version: u32,
#[serde(rename = "buildVersion", default)]
build_version: u32,
version: String,
}
#[async_trait::async_trait]
impl JavaRuntimeProvider for LibericaRuntimeProvider {
fn vendor(&self) -> JavaVendor {
JavaVendor::Liberica
}
async fn list_packages(
&self,
major: Option<u32>,
services: &LauncherServices,
) -> LauncherResult<Vec<JavaPackage>> {
let url = liberica_url(major)?;
let releases = services
.requester
.send_as::<Vec<LibericaRelease>>(Request::new(Method::GET, url))
.await?;
let mut releases = releases;
releases.sort_by_key(|r| {
std::cmp::Reverse((r.feature_version, r.update_version, r.build_version))
});
let packages: Vec<JavaPackage> = releases
.into_iter()
.map(|r| {
let mut java_version: Vec<u32> = r
.version
.split(|c: char| !c.is_numeric())
.filter_map(|s| s.parse::<u32>().ok())
.collect();
if java_version.first() != Some(&r.feature_version) {
java_version.insert(0, r.feature_version);
}
JavaPackage {
archive: PackageArchive::from_filename(&r.filename),
download_url: r.download_url,
java_version,
name: r.filename,
vendor: JavaVendor::Liberica,
}
})
.collect();
Ok(packages)
}
}
fn liberica_url(major: Option<u32>) -> LauncherResult<Url> {
let mut url = Url::parse("https://api.bell-sw.com/v1/liberica/releases")?;
{
let mut q = url.query_pairs_mut();
q.append_pair("os", LIBERICA_OS)
.append_pair("arch", LIBERICA_ARCH)
.append_pair("bundle-type", "jre")
.append_pair("bitness", LIBERICA_BITNESS)
.append_pair("package-type", LIBERICA_PACKAGE)
.append_pair("installation-type", "archive")
.append_pair(
"fields",
"downloadUrl,filename,featureVersion,updateVersion,buildVersion,version",
)
.append_pair("output", "json");
if let Some(major) = major {
q.append_pair("version-feature", &major.to_string());
}
}
Ok(url)
}
const LIBERICA_ARCH: &str = cfg_select! {
target_arch = "x86" => "x86",
target_arch = "x86_64" => "x86",
target_arch = "arm" => "arm",
target_arch = "aarch64" => "arm",
};
const LIBERICA_BITNESS: &str = cfg_select! {
target_arch = "x86" => "32",
target_arch = "x86_64" => "64",
target_arch = "arm" => "32",
target_arch = "aarch64" => "64",
};
const LIBERICA_OS: &str = cfg_select! {
target_os = "windows" => "windows",
target_os = "macos" => "macos",
target_os = "linux" => cfg_select! {
target_env = "musl" => "linux-musl",
_ => "linux"
},
};
const LIBERICA_PACKAGE: &str = cfg_select! {
target_os = "windows" => "zip",
_ => "tar.gz",
};

View file

@ -5,9 +5,13 @@ use crate::state::LauncherServices;
use crate::LauncherResult;
mod adoptium;
mod corretto;
mod liberica;
mod zulu;
pub use adoptium::AdoptiumRuntimeProvider;
pub use corretto::CorrettoRuntimeProvider;
pub use liberica::LibericaRuntimeProvider;
use serde::{Deserialize, Deserializer, Serialize};
pub use zulu::ZuluRuntimeProvider;
@ -15,6 +19,8 @@ pub fn runtime_providers() -> Vec<Box<dyn JavaRuntimeProvider>> {
vec![
Box::new(ZuluRuntimeProvider),
Box::new(AdoptiumRuntimeProvider),
Box::new(CorrettoRuntimeProvider),
Box::new(LibericaRuntimeProvider),
]
}
@ -22,9 +28,9 @@ pub fn runtime_providers() -> Vec<Box<dyn JavaRuntimeProvider>> {
pub trait JavaRuntimeProvider: Send + Sync {
fn vendor(&self) -> JavaVendor;
async fn list_packages_by_major(
async fn list_packages(
&self,
major: u32,
major: Option<u32>,
services: &LauncherServices,
) -> LauncherResult<Vec<JavaPackage>>;
@ -33,8 +39,8 @@ pub trait JavaRuntimeProvider: Send + Sync {
major: u32,
services: &LauncherServices
) -> LauncherResult<Option<JavaPackage>> {
let packages = self.list_packages_by_major(major, services).await?;
Ok(packages.first().cloned())
let packages = self.list_packages(Some(major), services).await?;
Ok(packages.into_iter().find(|p| p.java_version.first() == Some(&major)))
}
async fn install_package(
@ -53,6 +59,7 @@ pub enum JavaVendor {
Oracle,
Microsoft,
Adoptium,
Liberica,
OpenJDK,
Other(String),
}
@ -62,6 +69,7 @@ impl std::fmt::Display for JavaVendor {
match self {
JavaVendor::Zulu => f.write_str("Zulu"),
JavaVendor::Adoptium => f.write_str("Temurin"),
JavaVendor::Liberica => f.write_str("Liberica"),
JavaVendor::Corretto => f.write_str("Corretto"),
JavaVendor::Microsoft => f.write_str("Microsoft"),
JavaVendor::Oracle => f.write_str("Oracle"),
@ -77,8 +85,10 @@ impl FromStr for JavaVendor {
fn from_str(s: &str) -> Result<Self, Self::Err> {
let v = s.to_lowercase();
Ok(if v.contains("adoptium") || v.contains("adoptopenjdk") {
Ok(if v.contains("adoptium") || v.contains("adoptopenjdk") || v.contains("temurin") {
Self::Adoptium
} else if v.contains("liberica") || v.contains("bellsoft") {
Self::Liberica
} else if v.contains("microsoft") {
Self::Microsoft
} else if v.contains("openjdk") {

View file

@ -23,7 +23,7 @@ impl JavaRuntimeProvider for ZuluRuntimeProvider {
JavaVendor::Zulu
}
async fn list_packages_by_major(&self, major: u32, services: &LauncherServices) -> LauncherResult<Vec<JavaPackage>> {
async fn list_packages(&self, major: Option<u32>, services: &LauncherServices) -> LauncherResult<Vec<JavaPackage>> {
let url = zulu_url(major)?;
let packages = services.requester.send_as::<Vec<ZuluPackage>>(Request::new(Method::GET, url)).await?;
let packages = packages.into_iter().map(map_zulu_package).collect();
@ -42,8 +42,26 @@ fn map_zulu_package(pkg: ZuluPackage) -> JavaPackage {
}
}
fn zulu_url(major: u32) -> LauncherResult<Url> {
Ok(Url::parse(&format!("https://api.azul.com/metadata/v1/zulu/packages/?java_version={major}&os={ZULU_OS}&arch={ZULU_ARCH}&archive_type=zip&java_package_type=jre&javafx_bundled=false&release_status=ga&availability_types=CA&certifications=tck&page=1&page_size=5"))?)
fn zulu_url(major: Option<u32>) -> LauncherResult<Url> {
let mut url = Url::parse("https://api.azul.com/metadata/v1/zulu/packages/")?;
{
let mut q = url.query_pairs_mut();
q.append_pair("os", ZULU_OS)
.append_pair("arch", ZULU_ARCH)
.append_pair("archive_type", "zip")
.append_pair("java_package_type", "jre")
.append_pair("javafx_bundled", "false")
.append_pair("release_status", "ga")
.append_pair("availability_types", "CA")
.append_pair("certifications", "tck")
.append_pair("latest", "true")
.append_pair("page", "1")
.append_pair("page_size", "100");
if let Some(major) = major {
q.append_pair("java_version", &major.to_string());
}
}
Ok(url)
}
const ZULU_ARCH: &str = cfg_select! {