From a9c7eb0954261c3646e47bd9349987f92ea7cff6 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Tue, 18 May 2021 00:53:26 +0800 Subject: [PATCH] feat: download specified jre from mojang --- .../hmcl/download/java/JavaDownloadTask.java | 145 ++++++++++++++++++ .../hmcl/download/java/JavaDownloads.java | 131 ++++++++++++++++ .../hmcl/download/java/RemoteFiles.java | 94 ++++++++++++ 3 files changed, 370 insertions(+) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloads.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/RemoteFiles.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java new file mode 100644 index 000000000..7b2dbdbb9 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloadTask.java @@ -0,0 +1,145 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java; + +import org.jackhuang.hmcl.download.ArtifactMalformedException; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.game.DownloadInfo; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.io.NetworkUtils; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.tukaani.xz.LZMAInputStream; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +public class JavaDownloadTask extends Task { + private final String javaName; + private final Path rootDir; + private final String platform; + private final Task javaDownloadsTask; + private JavaDownloads.JavaDownload download; + private final List> dependencies = new ArrayList<>(); + + public JavaDownloadTask(String javaName, String javaVersion, Path rootDir, DownloadProvider downloadProvider) throws UnsupportedPlatformException { + this.javaName = javaName; + this.rootDir = rootDir; + this.platform = getCurrentJavaPlatform().orElseThrow(UnsupportedPlatformException::new); + this.javaDownloadsTask = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL( + "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"))) + .thenComposeAsync(javaDownloadsJson -> { + JavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, JavaDownloads.class); + if (!allDownloads.getDownloads().containsKey(platform)) throw new UnsupportedPlatformException(); + Map> osDownloads = allDownloads.getDownloads().get(platform); + if (!osDownloads.containsKey(javaName)) throw new UnsupportedPlatformException(); + List candidates = osDownloads.get(javaName); + for (JavaDownloads.JavaDownload download : candidates) { + if (VersionNumber.VERSION_COMPARATOR.compare(download.getVersion().getName(), javaVersion) >= 0) { + this.download = download; + return new GetTask(NetworkUtils.toURL(download.getManifest().getUrl())); + } + } + throw new UnsupportedPlatformException(); + }) + .thenApplyAsync(javaDownloadJson -> JsonUtils.fromNonNullJson(javaDownloadJson, RemoteFiles.class)); + } + + @Override + public Collection> getDependents() { + return Collections.singleton(javaDownloadsTask); + } + + @Override + public void execute() throws Exception { + Path jvmDir = rootDir.resolve(javaName).resolve(platform).resolve(javaName); + for (Map.Entry file : javaDownloadsTask.getResult().getFiles().entrySet()) { + Path dest = jvmDir.resolve(file.getKey()); + if (file.getValue().getDownloads().containsKey("lzma")) { + DownloadInfo download = file.getValue().getDownloads().get("lzma"); + File tempFile = Files.createTempFile("hmcl", "tmp").toFile(); + FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(download.getUrl()), tempFile, new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1())); + dependencies.add(task.thenRunAsync(() -> { + try { + Files.copy(new LZMAInputStream(new FileInputStream(tempFile)), dest); + } catch (IOException e) { + throw new ArtifactMalformedException("File " + file.getKey() + " is malformed"); + } + })); + } else if (file.getValue().getDownloads().containsKey("raw")) { + DownloadInfo download = file.getValue().getDownloads().get("raw"); + FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(download.getUrl()), dest.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", download.getSha1())); + dependencies.add(task); + } else { + throw new UnsupportedOperationException(); + } + } + } + + @Override + public List> getDependencies() { + return dependencies; + } + + @Override + public boolean doPostExecute() { + return true; + } + + @Override + public void postExecute() throws Exception { + FileUtils.writeText(rootDir.resolve(javaName).resolve(platform).resolve(".version").toFile(), download.getVersion().getName()); + FileUtils.writeText(rootDir.resolve(javaName).resolve(platform).resolve(javaName + ".sha1").toFile(), + javaDownloadsTask.getResult().getFiles().entrySet().stream() + .map(entry -> entry.getKey() + " /#// " + entry.getValue().getDownloads().get("raw").getSha1() + " " + entry.getValue().getDownloads().get("raw").getSize()) + .collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR))); + } + + public static Optional getCurrentJavaPlatform() { + if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) { + if (Architecture.CURRENT == Architecture.X86) { + return Optional.of("linux-i386"); + } else if (Architecture.CURRENT == Architecture.X86_64) { + return Optional.of("linux"); + } + } else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { + if (Architecture.CURRENT == Architecture.X86_64) { + return Optional.of("mac-os"); + } + } else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { + if (Architecture.CURRENT == Architecture.X86) { + return Optional.of("windows-x86"); + } else if (Architecture.CURRENT == Architecture.X86_64) { + return Optional.of("windows-x64"); + } + } + return Optional.empty(); + } + + public static class UnsupportedPlatformException extends Exception {} +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloads.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloads.java new file mode 100644 index 000000000..1c214ea8e --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/JavaDownloads.java @@ -0,0 +1,131 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java; + +import com.google.gson.*; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.game.DownloadInfo; +import org.jackhuang.hmcl.util.Immutable; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +@JsonAdapter(JavaDownloads.Adapter.class) +public class JavaDownloads { + + private final Map>> downloads; + + public JavaDownloads(Map>> downloads) { + this.downloads = downloads; + } + + public Map>> getDownloads() { + return downloads; + } + + public static class Adapter implements JsonSerializer, JsonDeserializer { + + @Override + public JsonElement serialize(JavaDownloads src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(src.downloads); + } + + @Override + public JavaDownloads deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return new JavaDownloads(context.deserialize(json, new TypeToken>>>() { + }.getType())); + } + } + + @Immutable + public static class JavaDownload { + private final Availability availability; + private final DownloadInfo manifest; + private final Version version; + + public JavaDownload() { + this(new Availability(), new DownloadInfo(), new Version()); + } + + public JavaDownload(Availability availability, DownloadInfo manifest, Version version) { + this.availability = availability; + this.manifest = manifest; + this.version = version; + } + + public Availability getAvailability() { + return availability; + } + + public DownloadInfo getManifest() { + return manifest; + } + + public Version getVersion() { + return version; + } + } + + @Immutable + public static class Availability { + private final int group; + private final int progress; + + public Availability() { + this(0, 0); + } + + public Availability(int group, int progress) { + this.group = group; + this.progress = progress; + } + + public int getGroup() { + return group; + } + + public int getProgress() { + return progress; + } + } + + @Immutable + public static class Version { + private final String name; + private final String released; + + public Version() { + this("", ""); + } + + public Version(String name, String released) { + this.name = name; + this.released = released; + } + + public String getName() { + return name; + } + + public String getReleased() { + return released; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/RemoteFiles.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/RemoteFiles.java new file mode 100644 index 000000000..45a4d2d3b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/RemoteFiles.java @@ -0,0 +1,94 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.download.java; + +import org.jackhuang.hmcl.game.DownloadInfo; +import org.jackhuang.hmcl.util.gson.JsonSubtype; +import org.jackhuang.hmcl.util.gson.JsonType; + +import java.util.Map; + +public class RemoteFiles { + private final Map files; + + public RemoteFiles(Map files) { + this.files = files; + } + + public Map getFiles() { + return files; + } + + @JsonType( + property = "type", + subtypes = { + @JsonSubtype(clazz = RemoteFile.class, name = "file"), + @JsonSubtype(clazz = RemoteDirectory.class, name = "directory"), + @JsonSubtype(clazz = RemoteLink.class, name = "link") + } + ) + public static class Remote { + private final String type; + + public Remote(String type) { + this.type = type; + } + + public String getType() { + return type; + } + } + + public static class RemoteFile extends Remote { + private final boolean executable; + private final Map downloads; + + public RemoteFile(boolean executable, Map downloads) { + super("file"); + this.executable = executable; + this.downloads = downloads; + } + + public boolean isExecutable() { + return executable; + } + + public Map getDownloads() { + return downloads; + } + } + + public static class RemoteDirectory extends Remote { + public RemoteDirectory() { + super("directory"); + } + } + + public static class RemoteLink extends Remote { + private final String target; + + public RemoteLink(String target) { + super("link"); + this.target = target; + } + + public String getTarget() { + return target; + } + } +}