From c74ef3eca882e9c4263ae6dbe6330be8eb17f87f Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:13:12 +0800 Subject: [PATCH] =?UTF-8?q?Fix=202338=20=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E9=97=AE=E9=A2=98=20(#2339)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix 2338 * Fix: Now HMCL could read Post exit command from config file. * Invoke processListener.onExit before invoking exit command. Remove 'throw new RuntimeException'. --- .../hmcl/setting/VersionSetting.java | 1 + .../versions/AdvancedVersionSettingPage.java | 1 + .../hmcl/launch/DefaultLauncher.java | 21 ++++++- .../org/jackhuang/hmcl/util/StringUtils.java | 59 +++++++++++++++---- 4 files changed, 69 insertions(+), 13 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index f1ca875ee..9b9b752bf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -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.setJavaDir(Optional.ofNullable(obj.get("javaDir")).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.setJava(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse("")); vs.setWrapper(Optional.ofNullable(obj.get("wrapper")).map(JsonElement::getAsString).orElse("")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java index ea5ecb6ac..c2c5adfe5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AdvancedVersionSettingPage.java @@ -217,6 +217,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor FXUtils.bindString(txtEnvironmentVariables, versionSetting.environmentVariablesProperty()); FXUtils.bindString(txtWrapper, versionSetting.wrapperProperty()); FXUtils.bindString(txtPreLaunchCommand, versionSetting.preLaunchCommandProperty()); + FXUtils.bindString(txtPostExitCommand, versionSetting.postExitCommandProperty()); FXUtils.bindEnum(cboRenderer, versionSetting.rendererProperty()); noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty()); noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 93271d101..8ac53cefb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -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 List rawCommandLine = command.commandLine.asList(); + if (StringUtils.isNotBlank(options.getWrapper())) { + rawCommandLine.addAll(0, StringUtils.parseCommand(options.getWrapper(), getEnvVars())); + } if (command.tempNativeFolder != null) { Files.deleteIfExists(command.tempNativeFolder); @@ -439,7 +442,7 @@ public class DefaultLauncher extends Launcher { File runDirectory = repository.getRunDirectory(version.getId()); 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()); SystemUtils.callExternalProcess(builder); } @@ -649,7 +652,7 @@ public class DefaultLauncher extends Launcher { 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); Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> { processListener.onLog(it, false); @@ -661,7 +664,19 @@ public class DefaultLauncher extends Launcher { managedProcess.addLine(it); }, encoding), "stderr-pump", isDaemon); 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 { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java index e3fe3ff86..b58d666d8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -210,19 +210,58 @@ public final class StringUtils { public static List tokenize(String str) { if (str == null) return new ArrayList<>(); - else - return tokenize(str, " \t\n\r\f"); + else { + // Split the string with ' or " and space cleverly. + + final char groupSplit; + if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { + groupSplit = '"'; + } else { + groupSplit = '\''; + } + + ArrayList 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 tokenize(String str, String delim) { - ArrayList result = new ArrayList<>(); - StringTokenizer tokenizer = new StringTokenizer(str, delim); - while (tokenizer.hasMoreTokens()) { - delim = tokenizer.nextToken(); - result.add(delim); - } + public static List parseCommand(String command, Map env) { + StringBuilder stringBuilder = new StringBuilder(command); + env.forEach((key, value) -> { + key = "$" + key; + int i = 0; + 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) {