Fix 2338 自定义命令问题 (#2339)

* Fix 2338

* Fix: Now HMCL could read Post exit command from config file.

* Invoke processListener.onExit before invoking exit command. Remove 'throw new RuntimeException'.
This commit is contained in:
Burning_TNT 2023-07-07 15:13:12 +08:00 committed by GitHub
parent 372b6376fc
commit c74ef3eca8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 13 deletions

View File

@ -844,6 +844,7 @@ public final class VersionSetting implements Cloneable {
vs.setHeight(Optional.ofNullable(obj.get("height")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0)); vs.setHeight(Optional.ofNullable(obj.get("height")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0));
vs.setJavaDir(Optional.ofNullable(obj.get("javaDir")).map(JsonElement::getAsString).orElse("")); vs.setJavaDir(Optional.ofNullable(obj.get("javaDir")).map(JsonElement::getAsString).orElse(""));
vs.setPreLaunchCommand(Optional.ofNullable(obj.get("precalledCommand")).map(JsonElement::getAsString).orElse("")); vs.setPreLaunchCommand(Optional.ofNullable(obj.get("precalledCommand")).map(JsonElement::getAsString).orElse(""));
vs.setPostExitCommand(Optional.ofNullable(obj.get("postExitCommand")).map(JsonElement::getAsString).orElse(""));
vs.setServerIp(Optional.ofNullable(obj.get("serverIp")).map(JsonElement::getAsString).orElse("")); vs.setServerIp(Optional.ofNullable(obj.get("serverIp")).map(JsonElement::getAsString).orElse(""));
vs.setJava(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse("")); vs.setJava(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse(""));
vs.setWrapper(Optional.ofNullable(obj.get("wrapper")).map(JsonElement::getAsString).orElse("")); vs.setWrapper(Optional.ofNullable(obj.get("wrapper")).map(JsonElement::getAsString).orElse(""));

View File

@ -217,6 +217,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
FXUtils.bindString(txtEnvironmentVariables, versionSetting.environmentVariablesProperty()); FXUtils.bindString(txtEnvironmentVariables, versionSetting.environmentVariablesProperty());
FXUtils.bindString(txtWrapper, versionSetting.wrapperProperty()); FXUtils.bindString(txtWrapper, versionSetting.wrapperProperty());
FXUtils.bindString(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty()); FXUtils.bindString(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty());
FXUtils.bindString(txtPostExitCommand, versionSetting.postExitCommandProperty());
FXUtils.bindEnum(cboRenderer, versionSetting.rendererProperty()); FXUtils.bindEnum(cboRenderer, versionSetting.rendererProperty());
noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty()); noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty());
noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty()); noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty());

View File

@ -419,6 +419,9 @@ public class DefaultLauncher extends Launcher {
// To guarantee that when failed to generate launch command line, we will not call pre-launch command // To guarantee that when failed to generate launch command line, we will not call pre-launch command
List<String> rawCommandLine = command.commandLine.asList(); List<String> rawCommandLine = command.commandLine.asList();
if (StringUtils.isNotBlank(options.getWrapper())) {
rawCommandLine.addAll(0, StringUtils.parseCommand(options.getWrapper(), getEnvVars()));
}
if (command.tempNativeFolder != null) { if (command.tempNativeFolder != null) {
Files.deleteIfExists(command.tempNativeFolder); Files.deleteIfExists(command.tempNativeFolder);
@ -439,7 +442,7 @@ public class DefaultLauncher extends Launcher {
File runDirectory = repository.getRunDirectory(version.getId()); File runDirectory = repository.getRunDirectory(version.getId());
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) { if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPreLaunchCommand())).directory(runDirectory); ProcessBuilder builder = new ProcessBuilder(StringUtils.parseCommand(options.getPreLaunchCommand(), getEnvVars())).directory(runDirectory);
builder.environment().putAll(getEnvVars()); builder.environment().putAll(getEnvVars());
SystemUtils.callExternalProcess(builder); SystemUtils.callExternalProcess(builder);
} }
@ -649,7 +652,7 @@ public class DefaultLauncher extends Launcher {
throw new ExecutionPolicyLimitException(); throw new ExecutionPolicyLimitException();
} }
private static void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, Charset encoding, boolean isDaemon) { private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, Charset encoding, boolean isDaemon) {
processListener.setProcess(managedProcess); processListener.setProcess(managedProcess);
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> { Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {
processListener.onLog(it, false); processListener.onLog(it, false);
@ -661,7 +664,19 @@ public class DefaultLauncher extends Launcher {
managedProcess.addLine(it); managedProcess.addLine(it);
}, encoding), "stderr-pump", isDaemon); }, encoding), "stderr-pump", isDaemon);
managedProcess.addRelatedThread(stderr); managedProcess.addRelatedThread(stderr);
managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), processListener::onExit), "exit-waiter", isDaemon)); managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> {
processListener.onExit(exitCode, exitType);
if (StringUtils.isNotBlank(options.getPostExitCommand())) {
try {
ProcessBuilder builder = new ProcessBuilder(StringUtils.parseCommand(options.getPostExitCommand(), getEnvVars())).directory(options.getGameDir());
builder.environment().putAll(getEnvVars());
SystemUtils.callExternalProcess(builder);
} catch (Throwable e) {
LOG.log(Level.WARNING, "An Exception happened while running exit command.", e);
}
}
}), "exit-waiter", isDaemon));
} }
private static final class Command { private static final class Command {

View File

@ -210,19 +210,58 @@ public final class StringUtils {
public static List<String> tokenize(String str) { public static List<String> tokenize(String str) {
if (str == null) if (str == null)
return new ArrayList<>(); return new ArrayList<>();
else else {
return tokenize(str, " \t\n\r\f"); // Split the string with ' or " and space cleverly.
final char groupSplit;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
groupSplit = '"';
} else {
groupSplit = '\'';
}
ArrayList<String> parts = new ArrayList<>();
{
boolean inside = false;
StringBuilder current = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == groupSplit) {
inside = !inside;
} else if (!inside && c == ' ') {
parts.add(current.toString());
current.setLength(0);
} else {
current.append(c);
}
}
if (current.length() != 0) {
parts.add(current.toString());
}
}
return parts;
}
} }
public static List<String> tokenize(String str, String delim) { public static List<String> parseCommand(String command, Map<String, String> env) {
ArrayList<String> result = new ArrayList<>(); StringBuilder stringBuilder = new StringBuilder(command);
StringTokenizer tokenizer = new StringTokenizer(str, delim); env.forEach((key, value) -> {
while (tokenizer.hasMoreTokens()) { key = "$" + key;
delim = tokenizer.nextToken(); int i = 0;
result.add(delim); while (true) {
} i = stringBuilder.indexOf(key, i);
if (i == -1) {
break;
}
stringBuilder.replace(i, i + key.length(), value);
}
});
return result; return tokenize(stringBuilder.toString());
} }
public static String parseColorEscapes(String original) { public static String parseColorEscapes(String original) {