use clap::{Parser, Subcommand};
use piston_mc::java::{JavaManifest, JavaRuntime};
use simple_download_utility::MultiDownloadProgress;
use std::path::{Path, PathBuf};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct MCJavaArgs {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
List,
Download {
version: String,
directory: PathBuf,
},
Execute {
version: String,
directory: PathBuf,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
java_args: Vec<String>,
},
}
#[tokio::main]
async fn main() {
let args = MCJavaArgs::parse();
match args.command {
Command::List => {
let list = list().await;
println!("Available Java versions:");
for rt in list {
println!("{:<20} ({})", rt.version.name, rt.version.released)
}
}
Command::Download { version, directory } => {
download(version, directory).await;
}
Command::Execute {
version,
directory,
java_args,
} => {
execute(version, directory, java_args).await;
}
}
}
async fn download(version: String, directory: PathBuf) {
let list = list().await;
if let Some(runtime) = list.iter().find(|v| v.version.name == version) {
let (sender, mut receiver) = tokio::sync::mpsc::channel::<MultiDownloadProgress>(16);
let task = runtime.install(directory, 100, Some(sender));
tokio::spawn(async move {
while let Some(progress) = receiver.recv().await {
let percent =
(progress.files_downloaded as f32 / progress.files_total as f32) * 100.0;
let mb_per_sec = progress.bytes_per_second as f32 / 1024.0 / 1024.0;
println!(
"Installing Java: {:.2}% ({}/{} files) {:.2} MB/s",
percent, progress.files_downloaded, progress.files_total, mb_per_sec
);
}
});
task.await.expect("Failed to install Java runtime");
println!("Java runtime installed successfully");
}
}
async fn execute(version: String, directory: PathBuf, java_args: Vec<String>) {
if !Path::exists(&directory.join("bin")) {
download(version.clone(), directory.clone()).await;
}
let java_exe = if cfg!(windows) { "java.exe" } else { "java" };
let java_path = directory.join("bin").join(java_exe);
let status = tokio::process::Command::new(&java_path)
.args(&java_args)
.status()
.await;
match status {
Ok(status) => std::process::exit(status.code().unwrap_or(0)),
Err(e) => {
eprintln!("Failed to execute Java at {}: {}", java_path.display(), e);
std::process::exit(1);
}
}
}
async fn list() -> Vec<JavaRuntime> {
let manifest = match JavaManifest::fetch().await {
Ok(items) => items,
Err(e) => {
eprintln!("Failed to get the list of java versions from Mojang.");
eprintln!("{}", e);
std::process::exit(1);
}
};
let runtime = {
#[cfg(all(target_os = "windows", target_arch = "x86"))]
{
manifest.windows_x86
}
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
{
manifest.windows_x64
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
{
manifest.macos
}
#[cfg(all(target_os = "macos", target_arch = "arm"))]
{
manifest.macos_arm64
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
{
manifest.linux
}
#[cfg(all(target_os = "linux", target_arch = "arm"))]
{
manifest.linux_i386
}
};
let mut runtimes: Vec<JavaRuntime> = vec![];
runtimes.extend(runtime.alpha);
runtimes.extend(runtime.beta);
runtimes.extend(runtime.gamma);
runtimes.extend(runtime.delta);
runtimes.extend(runtime.gamma_snapshot);
runtimes.extend(runtime.epsilon);
runtimes.extend(runtime.legacy);
runtimes.sort_by(|a, b| b.version.released.cmp(&a.version.released));
runtimes
}