mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-15 14:56:05 -04:00
use separate tasks for forge installer processors
This commit is contained in:
parent
7d39d00a6a
commit
9bc10fb27d
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
|
||||
@ -29,7 +29,8 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricInstallTask;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeOldInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameInstallTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
@ -110,7 +111,7 @@ public final class TaskListPane extends StackPane {
|
||||
task.setName(i18n("assets.download_all"));
|
||||
} else if (task instanceof GameInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.game")));
|
||||
} else if (task instanceof ForgeInstallTask) {
|
||||
} else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
|
||||
} else if (task instanceof LiteLoaderInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.liteloader")));
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
|
||||
@ -60,6 +60,7 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
|
||||
@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download.forge;
|
||||
import org.jackhuang.hmcl.download.ArtifactMalformedException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
@ -36,6 +37,7 @@ import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@ -45,6 +47,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipException;
|
||||
@ -55,6 +58,118 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class ForgeNewInstallTask extends Task<Version> {
|
||||
|
||||
private class ProcessorTask extends Task<Void> {
|
||||
|
||||
private Processor processor;
|
||||
private Map<String, String> vars;
|
||||
|
||||
public ProcessorTask(@NotNull Processor processor, @NotNull Map<String, String> vars) {
|
||||
this.processor = processor;
|
||||
this.vars = vars;
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Map<String, String> outputs = new HashMap<>();
|
||||
boolean miss = false;
|
||||
|
||||
for (Map.Entry<String, String> entry : processor.getOutputs().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
key = parseLiteral(key, vars, ExceptionalFunction.identity());
|
||||
value = parseLiteral(value, vars, ExceptionalFunction.identity());
|
||||
|
||||
if (key == null || value == null) {
|
||||
throw new ArtifactMalformedException("Invalid forge installation configuration");
|
||||
}
|
||||
|
||||
outputs.put(key, value);
|
||||
|
||||
Path artifact = Paths.get(key);
|
||||
if (Files.exists(artifact)) {
|
||||
String code;
|
||||
try (InputStream stream = Files.newInputStream(artifact)) {
|
||||
code = encodeHex(digest("SHA-1", stream));
|
||||
}
|
||||
|
||||
if (!Objects.equals(code, value)) {
|
||||
Files.delete(artifact);
|
||||
LOG.info("Found existing file is not valid: " + artifact);
|
||||
|
||||
miss = true;
|
||||
}
|
||||
} else {
|
||||
miss = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!processor.getOutputs().isEmpty() && !miss) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path jar = gameRepository.getArtifactFile(version, processor.getJar());
|
||||
if (!Files.isRegularFile(jar))
|
||||
throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess");
|
||||
|
||||
String mainClass;
|
||||
try (JarFile jarFile = new JarFile(jar.toFile())) {
|
||||
mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(mainClass))
|
||||
throw new Exception("Game processor jar does not have main class " + jar);
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
command.add("-cp");
|
||||
|
||||
List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);
|
||||
for (Artifact artifact : processor.getClasspath()) {
|
||||
Path file = gameRepository.getArtifactFile(version, artifact);
|
||||
if (!Files.isRegularFile(file))
|
||||
throw new Exception("Game processor dependency missing");
|
||||
classpath.add(file.toString());
|
||||
}
|
||||
classpath.add(jar.toString());
|
||||
command.add(String.join(OperatingSystem.PATH_SEPARATOR, classpath));
|
||||
|
||||
command.add(mainClass);
|
||||
|
||||
List<String> args = new ArrayList<>(processor.getArgs().size());
|
||||
for (String arg : processor.getArgs()) {
|
||||
String parsed = parseLiteral(arg, vars, ExceptionalFunction.identity());
|
||||
if (parsed == null)
|
||||
throw new ArtifactMalformedException("Invalid forge installation configuration");
|
||||
args.add(parsed);
|
||||
}
|
||||
|
||||
command.addAll(args);
|
||||
|
||||
LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString());
|
||||
int exitCode = SystemUtils.callExternalProcess(command);
|
||||
if (exitCode != 0)
|
||||
throw new IOException("Game processor exited abnormally with code " + exitCode);
|
||||
|
||||
for (Map.Entry<String, String> entry : outputs.entrySet()) {
|
||||
Path artifact = Paths.get(entry.getKey());
|
||||
if (!Files.isRegularFile(artifact))
|
||||
throw new FileNotFoundException("File missing: " + artifact);
|
||||
|
||||
String code;
|
||||
try (InputStream stream = Files.newInputStream(artifact)) {
|
||||
code = encodeHex(digest("SHA-1", stream));
|
||||
}
|
||||
|
||||
if (!Objects.equals(code, entry.getValue())) {
|
||||
Files.delete(artifact);
|
||||
throw new ChecksumMismatchException("SHA-1", entry.getValue(), code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final DefaultGameRepository gameRepository;
|
||||
private final Version version;
|
||||
@ -63,9 +178,13 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
private ForgeNewInstallProfile profile;
|
||||
private List<Processor> processors;
|
||||
private Version forgeVersion;
|
||||
private final String selfVersion;
|
||||
|
||||
private Path tempDir;
|
||||
private AtomicInteger processorDoneCount = new AtomicInteger(0);
|
||||
|
||||
ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameRepository = dependencyManager.getGameRepository();
|
||||
@ -73,7 +192,7 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
this.installer = installer;
|
||||
this.selfVersion = selfVersion;
|
||||
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
setSignificance(TaskSignificance.MAJOR);
|
||||
}
|
||||
|
||||
private static String replaceTokens(Map<String, String> tokens, String value) {
|
||||
@ -150,6 +269,7 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
public void preExecute() throws Exception {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
profile = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath("install_profile.json")), ForgeNewInstallProfile.class);
|
||||
processors = profile.getProcessors();
|
||||
forgeVersion = JsonUtils.fromNonNullJson(FileUtils.readText(fs.getPath(profile.getJson())), Version.class);
|
||||
|
||||
for (Library library : profile.getLibraries()) {
|
||||
@ -167,151 +287,74 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
FileUtils.copyFile(mainJar, dest);
|
||||
}
|
||||
}
|
||||
} catch (ZipException ex) {
|
||||
throw new ArtifactMalformedException("Malformed forge installer file", ex);
|
||||
}
|
||||
|
||||
dependents.add(new GameLibrariesTask(dependencyManager, version, true, profile.getLibraries()));
|
||||
}
|
||||
|
||||
private Task<?> createProcessorTask(Processor processor, Map<String, String> vars) {
|
||||
Task<?> task = new ProcessorTask(processor, vars);
|
||||
task.onDone().register(
|
||||
() -> updateProgress(processorDoneCount.incrementAndGet(), processors.size()));
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Path temp = Files.createTempDirectory("forge_installer");
|
||||
int finished = 0;
|
||||
tempDir = Files.createTempDirectory("forge_installer");
|
||||
|
||||
Map<String, String> vars = new HashMap<>();
|
||||
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
List<ForgeNewInstallProfile.Processor> processors = profile.getProcessors();
|
||||
Map<String, String> data = profile.getData();
|
||||
|
||||
updateProgress(0, processors.size());
|
||||
|
||||
for (Map.Entry<String, String> entry : data.entrySet()) {
|
||||
for (Map.Entry<String, String> entry : profile.getData().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
data.put(key, parseLiteral(value,
|
||||
vars.put(key, parseLiteral(value,
|
||||
Collections.emptyMap(),
|
||||
str -> {
|
||||
Path dest = Files.createTempFile(temp, null, null);
|
||||
Path dest = Files.createTempFile(tempDir, null, null);
|
||||
FileUtils.copyFile(fs.getPath(str), dest);
|
||||
return dest.toString();
|
||||
}));
|
||||
}
|
||||
|
||||
data.put("SIDE", "client");
|
||||
data.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath());
|
||||
data.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath());
|
||||
data.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath());
|
||||
data.put("INSTALLER", installer.toAbsolutePath().toString());
|
||||
data.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath());
|
||||
|
||||
for (ForgeNewInstallProfile.Processor processor : processors) {
|
||||
Map<String, String> outputs = new HashMap<>();
|
||||
boolean miss = false;
|
||||
|
||||
for (Map.Entry<String, String> entry : processor.getOutputs().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
|
||||
key = parseLiteral(key, data, ExceptionalFunction.identity());
|
||||
value = parseLiteral(value, data, ExceptionalFunction.identity());
|
||||
|
||||
if (key == null || value == null) {
|
||||
throw new ArtifactMalformedException("Invalid forge installation configuration");
|
||||
}
|
||||
|
||||
outputs.put(key, value);
|
||||
|
||||
Path artifact = Paths.get(key);
|
||||
if (Files.exists(artifact)) {
|
||||
String code;
|
||||
try (InputStream stream = Files.newInputStream(artifact)) {
|
||||
code = encodeHex(digest("SHA-1", stream));
|
||||
}
|
||||
|
||||
if (!Objects.equals(code, value)) {
|
||||
Files.delete(artifact);
|
||||
LOG.info("Found existing file is not valid: " + artifact);
|
||||
|
||||
miss = true;
|
||||
}
|
||||
} else {
|
||||
miss = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!processor.getOutputs().isEmpty() && !miss) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Path jar = gameRepository.getArtifactFile(version, processor.getJar());
|
||||
if (!Files.isRegularFile(jar))
|
||||
throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess");
|
||||
|
||||
String mainClass;
|
||||
try (JarFile jarFile = new JarFile(jar.toFile())) {
|
||||
mainClass = jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(mainClass))
|
||||
throw new Exception("Game processor jar does not have main class " + jar);
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
command.add("-cp");
|
||||
|
||||
List<String> classpath = new ArrayList<>(processor.getClasspath().size() + 1);
|
||||
for (Artifact artifact : processor.getClasspath()) {
|
||||
Path file = gameRepository.getArtifactFile(version, artifact);
|
||||
if (!Files.isRegularFile(file))
|
||||
throw new Exception("Game processor dependency missing");
|
||||
classpath.add(file.toString());
|
||||
}
|
||||
classpath.add(jar.toString());
|
||||
command.add(String.join(OperatingSystem.PATH_SEPARATOR, classpath));
|
||||
|
||||
command.add(mainClass);
|
||||
|
||||
List<String> args = new ArrayList<>(processor.getArgs().size());
|
||||
for (String arg : processor.getArgs()) {
|
||||
String parsed = parseLiteral(arg, data, ExceptionalFunction.identity());
|
||||
if (parsed == null)
|
||||
throw new ArtifactMalformedException("Invalid forge installation configuration");
|
||||
args.add(parsed);
|
||||
}
|
||||
|
||||
command.addAll(args);
|
||||
|
||||
LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString());
|
||||
int exitCode = SystemUtils.callExternalProcess(command);
|
||||
if (exitCode != 0)
|
||||
throw new IOException("Game processor exited abnormally");
|
||||
|
||||
for (Map.Entry<String, String> entry : outputs.entrySet()) {
|
||||
Path artifact = Paths.get(entry.getKey());
|
||||
if (!Files.isRegularFile(artifact))
|
||||
throw new FileNotFoundException("File missing: " + artifact);
|
||||
|
||||
String code;
|
||||
try (InputStream stream = Files.newInputStream(artifact)) {
|
||||
code = encodeHex(digest("SHA-1", stream));
|
||||
}
|
||||
|
||||
if (!Objects.equals(code, entry.getValue())) {
|
||||
Files.delete(artifact);
|
||||
throw new ChecksumMismatchException("SHA-1", entry.getValue(), code);
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(++finished, processors.size());
|
||||
}
|
||||
} catch (ZipException ex) {
|
||||
throw new ArtifactMalformedException("Malformed forge installer file", ex);
|
||||
}
|
||||
|
||||
vars.put("SIDE", "client");
|
||||
vars.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath());
|
||||
vars.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath());
|
||||
vars.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath());
|
||||
vars.put("INSTALLER", installer.toAbsolutePath().toString());
|
||||
vars.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath());
|
||||
|
||||
updateProgress(0, processors.size());
|
||||
|
||||
Task<?> processorsTask = Task.runSequentially(
|
||||
processors.stream()
|
||||
.map(processor -> createProcessorTask(processor, vars))
|
||||
.toArray(Task<?>[]::new));
|
||||
|
||||
dependencies.add(
|
||||
processorsTask.thenComposeAsync(
|
||||
dependencyManager.checkLibraryCompletionAsync(forgeVersion, true)));
|
||||
|
||||
setResult(forgeVersion
|
||||
.setPriority(30000)
|
||||
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
|
||||
.setVersion(selfVersion));
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion, true));
|
||||
}
|
||||
|
||||
FileUtils.deleteDirectory(temp.toFile());
|
||||
@Override
|
||||
public boolean doPostExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postExecute() throws Exception {
|
||||
FileUtils.deleteDirectory(tempDir.toFile());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
|
||||
@ -49,7 +49,7 @@ public class ForgeOldInstallTask extends Task<Version> {
|
||||
this.installer = installer;
|
||||
this.selfVersion = selfVersion;
|
||||
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
setSignificance(TaskSignificance.MAJOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> 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
|
||||
@ -902,6 +902,25 @@ public abstract class Task<T> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new task that runs the given tasks sequentially
|
||||
* and returns the result of the last task.
|
||||
*
|
||||
* @param tasks tasks to run sequentially
|
||||
* @return the combination of these tasks
|
||||
*/
|
||||
public static Task<?> runSequentially(Task<?>... tasks) {
|
||||
if (tasks.length == 0) {
|
||||
return new SimpleTask<>(() -> null);
|
||||
}
|
||||
|
||||
Task<?> task = tasks[0];
|
||||
for (int i = 1; i < tasks.length; i++) {
|
||||
task = task.thenComposeAsync(tasks[i]);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
public enum TaskSignificance {
|
||||
MAJOR,
|
||||
MODERATE,
|
||||
|
Loading…
x
Reference in New Issue
Block a user