feat: Java 25+ is now downloadable
fix(ui): Scrollviews with no scrollable areas capturing scroll wheel events
This commit is contained in:
parent
68c8762b08
commit
1c8db8ceff
14 changed files with 498 additions and 100 deletions
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
103
packages/oneclient_core/src/java/vendors/corretto.rs
vendored
Normal file
103
packages/oneclient_core/src/java/vendors/corretto.rs
vendored
Normal 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),
|
||||
};
|
||||
123
packages/oneclient_core/src/java/vendors/liberica.rs
vendored
Normal file
123
packages/oneclient_core/src/java/vendors/liberica.rs
vendored
Normal 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",
|
||||
};
|
||||
20
packages/oneclient_core/src/java/vendors/mod.rs
vendored
20
packages/oneclient_core/src/java/vendors/mod.rs
vendored
|
|
@ -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") {
|
||||
|
|
|
|||
24
packages/oneclient_core/src/java/vendors/zulu.rs
vendored
24
packages/oneclient_core/src/java/vendors/zulu.rs
vendored
|
|
@ -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! {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue