From ac75ac90a3a2d153658ded608f7add3a4cee0d03 Mon Sep 17 00:00:00 2001 From: Kyle Wood Date: Sun, 26 Sep 2021 01:36:01 -0500 Subject: [PATCH] Add 'available' command to query downloable JDK versions This will include the platform OS and architecture in the API call so only actually valid versions will be shown. --- src/api/adoptium.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++- src/api/def.rs | 3 ++ src/main.rs | 21 +++++++++++-- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/api/adoptium.rs b/src/api/adoptium.rs index 07b3964..c51e2d9 100644 --- a/src/api/adoptium.rs +++ b/src/api/adoptium.rs @@ -2,6 +2,7 @@ use serde::Deserialize; use crate::api::def::{JdkFetchApi, JdkFetchError, JdkFetchResult}; use crate::api::http_failure::handle_response_fail; +use std::collections::HashSet; #[derive(Clone)] pub struct AdoptiumApi { @@ -44,6 +45,26 @@ impl AdoptiumApi { major + 1, )); } + + fn get_jdk_version_list_url(&self, page: u8) -> JdkFetchResult { + let os_name = get_os_name()?; + let arch_name = get_arch_name()?; + + return Ok(format!( + "{}/info/release_names\ + ?architecture={}\ + &OS={}\ + &image_type=jdk\ + &project=jdk\ + &release_type=ga\ + &sort_method=DEFAULT\ + &sort_order=DESC\ + &vendor={}\ + &page={}\ + &page_size=20", + &self.base_url, arch_name, os_name, &self.vendor, page, + )); + } } impl JdkFetchApi for AdoptiumApi { @@ -57,7 +78,7 @@ impl JdkFetchApi for AdoptiumApi { let url = self.get_latest_jdk_version_url(major)?; let response = attohttpc::get(&url).send().map_err(JdkFetchError::HttpIo)?; if !response.is_success() { - if response.status().as_u16() == 404 { + if response.status() == attohttpc::StatusCode::NOT_FOUND { return Ok(None); } return Err(handle_response_fail( @@ -83,6 +104,50 @@ impl JdkFetchApi for AdoptiumApi { }; Ok(Some(fixed_version)) } + + fn get_available_jdk_versions(&self) -> JdkFetchResult> { + let mut versions = HashSet::::new(); + + let mut i = 0; + loop { + let url = self.get_jdk_version_list_url(i)?; + i += 1; + + let response = attohttpc::get(&url).send().map_err(JdkFetchError::HttpIo)?; + + if response.status() == attohttpc::StatusCode::NOT_FOUND { + break; + } + if !response.is_success() { + return Err(handle_response_fail( + response, + "Failed to get available JDK versions", + )); + } + + let page: JdkNamesPage = response.json().map_err(JdkFetchError::HttpIo)?; + for name in page.releases { + let mut version_part = name.as_str(); + if version_part.starts_with("jdk") { + version_part = &version_part[3..]; + } + if version_part.starts_with('-') { + version_part = &version_part[1..]; + } + + let end_index = version_part.find(|c: char| !('0'..='9').contains(&c)).ok_or( + JdkFetchError::Generic { + message: format!("Failed to find major JDK version from: {}", name), + }, + )?; + + version_part = &version_part[..end_index]; + versions.insert(version_part.to_string()); + } + } + + Ok(versions) + } } #[derive(Debug, Deserialize)] @@ -103,6 +168,11 @@ enum Prerelease { EA, } +#[derive(Debug, Deserialize)] +struct JdkNamesPage { + releases: Vec, +} + fn get_os_name() -> JdkFetchResult<&'static str> { let env_os = std::env::consts::OS; match env_os { diff --git a/src/api/def.rs b/src/api/def.rs index 485597a..426f329 100644 --- a/src/api/def.rs +++ b/src/api/def.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use thiserror::Error; #[derive(Error, Debug)] @@ -20,4 +21,6 @@ pub trait JdkFetchApi { /// Get the latest JDK version, returning `None` if the API doesn't know about this major /// version. fn get_latest_jdk_version(&self, major: u8) -> JdkFetchResult>; + + fn get_available_jdk_versions(&self) -> JdkFetchResult>; } diff --git a/src/main.rs b/src/main.rs index 8c6587c..57c5e42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,8 @@ enum Subcommand { }, #[structopt(about = "List downloaded JDKs")] List {}, + #[structopt(about = "List JDKs available to download")] + Available {}, #[structopt(about = "Print currently active JDK version (full)")] Current {}, #[structopt(about = "Configure the default JDK")] @@ -61,14 +63,14 @@ enum Subcommand { JavaHome {}, } -fn parse_jdk_or_keyword(s: String) -> Either { +fn parse_jdk_or_keyword(s: &str) -> Either { s.parse::() .map(Either::Left) .unwrap_or_else(|_| Either::Right(s)) } fn load_default(config: &Configuration, jdk: String) -> Result { - let jdk_or_keyword = parse_jdk_or_keyword(jdk); + let jdk_or_keyword = parse_jdk_or_keyword(jdk.as_str()); match jdk_or_keyword { Either::Left(jdk_major) => Ok(jdk_major), Either::Right(unknown) => { @@ -86,7 +88,7 @@ fn load_jdk_list( config: &Configuration, jdk: String, ) -> Result> { - let jdk_or_keyword = parse_jdk_or_keyword(jdk); + let jdk_or_keyword = parse_jdk_or_keyword(jdk.as_str()); match jdk_or_keyword { Either::Left(jdk_major) => Ok(vec![jdk_major]), Either::Right(unknown) => { @@ -252,6 +254,19 @@ fn main_for_result(args: Jpre) -> Result<()> { ); } } + Subcommand::Available {} => { + let versions = jdk_manager.api.get_available_jdk_versions()?; + let mut sorted_versions = versions + .iter() + .filter_map(|version| parse_jdk_or_keyword(version).left()) + .collect::>(); + sorted_versions.sort_unstable(); + + println!("{}", "Major JDK versions available:".to_string().blue()); + for version in sorted_versions { + println!(" {}", version.to_string().green()); + } + } Subcommand::Current {} => { check_env_bound(&jdk_manager).context("Failed to check environment variables")?; let jdk_version = jdk_manager