Compare commits

..
4 Commits
23 changed files with 135 additions and 71 deletions

No files matched your search

@@ -85,7 +85,7 @@ public class HMCLModpackExportTask extends TaskResult<ZipEngine> {
return null;
});
Version mv = repository.getVersion(version).resolve(repository);
Version mv = repository.getResolvedVersion(version);
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
zip.putTextFile(Constants.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.setting.LauncherVisibility;
@@ -76,7 +77,7 @@ public final class LauncherHelper {
Profile profile = Settings.INSTANCE.getSelectedProfile();
GameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = repository.getVersion(selectedVersion);
Version version = repository.getResolvedVersion(selectedVersion);
Account account = Settings.INSTANCE.getSelectedAccount();
VersionSetting setting = profile.getVersionSetting(selectedVersion);
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
@@ -155,9 +156,7 @@ public final class LauncherHelper {
@Override
public void onStop(boolean success, TaskExecutor executor) {
if (success) {
Controllers.closeDialog();
} else {
if (!success) {
Platform.runLater(() -> {
if (executor.getLastException() != null)
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.CurseCDNDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.util.Lang;
@@ -28,9 +29,9 @@ import java.util.List;
public final class DownloadProviders {
private DownloadProviders() {}
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(MojangDownloadProvider.INSTANCE, BMCLAPIDownloadProvider.INSTANCE);
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(Settings.INSTANCE.getLocale() == Locales.ZH_CN), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
public static DownloadProvider getDownloadProvider(int index) {
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(MojangDownloadProvider.INSTANCE);
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(DOWNLOAD_PROVIDERS.get(0));
}
}
@@ -27,9 +27,7 @@ import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.ProfileChangedEvent;
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
@@ -272,6 +270,10 @@ public class Settings {
SETTINGS.setLogLines(logLines);
}
/****************************************
* AUTHLIB INJECTORS *
****************************************/
public Set<String> getAuthlibInjectorServerURLs() {
return SETTINGS.getAuthlibInjectorServerURLs();
}
@@ -307,24 +309,19 @@ public class Settings {
}
}
/****************************************
* DOWNLOAD PROVIDERS *
****************************************/
public DownloadProvider getDownloadProvider() {
switch (SETTINGS.getDownloadType()) {
case 0:
return MojangDownloadProvider.INSTANCE;
case 1:
return BMCLAPIDownloadProvider.INSTANCE;
default:
return MojangDownloadProvider.INSTANCE;
}
return DownloadProviders.getDownloadProvider(SETTINGS.getDownloadType());
}
public void setDownloadProvider(DownloadProvider downloadProvider) {
if (downloadProvider == MojangDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(0);
else if (downloadProvider == BMCLAPIDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(1);
else
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
if (index == -1)
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
SETTINGS.setDownloadType(index);
}
/****************************************
@@ -53,7 +53,7 @@ public class InstallerController {
public void loadVersion(Profile profile, String versionId) {
this.profile = profile;
this.versionId = versionId;
this.version = profile.getRepository().getVersion(versionId).resolve(profile.getRepository());
this.version = profile.getRepository().getResolvedVersion(versionId);
contentPane.getChildren().clear();
forge = liteLoader = optiFine = null;
@@ -62,7 +62,7 @@ public class InstallerController {
Consumer<InstallerItem> removeAction = x -> {
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
newList.remove(library);
new MaintainTask(profile.getRepository(), version.setLibraries(newList))
new MaintainTask(version.setLibraries(newList))
.then(variables -> new VersionJsonSaveTask(profile.getRepository(), variables.get(MaintainTask.ID)))
.with(profile.getRepository().refreshVersionsAsync())
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
@@ -218,7 +218,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
themeItem.getGroup().getToggles().stream().filter(it -> Settings.INSTANCE.getTheme() == it.getUserData() || Settings.INSTANCE.getTheme().isCustom() && themeItem.isCustomToggle(it)).findFirst().ifPresent(it -> it.setSelected(true));
Settings.INSTANCE.themeProperty().setChangedListenerAndOperate(it -> {
if (it.getName().startsWith("#") || it.getName().startsWith("0x"))
if (it.isCustom())
themeItem.setSubtitle(it.getName());
else
themeItem.setSubtitle(Main.i18n("color." + it.getName().toLowerCase()));
@@ -161,7 +161,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
deleteVersion(profile, version);
break;
case 2: // redownload asset index
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version).resolve(profile.getRepository())).start();
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getResolvedVersion(version)).start();
break;
case 3: // delete libraries
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
+5 -4
View File
@@ -4,7 +4,8 @@
*/
.root {
-fx-font-family: "微软雅黑", "Microsoft YaHei", "Roboto";
-fx-base-font-family: "微软雅黑", "Microsoft YaHei", "Roboto";
-fx-font-family: -fx-base-font-family;
}
.disabled Label {
@@ -129,7 +130,7 @@
.jfx-layout-heading {
-fx-font-size: 20.0px;
-fx-font-weight: BOLD;
-fx-font-family: -fx-base-font-family;
-fx-alignment: center-left;
-fx-padding: 5.0 0.0 5.0 0.0;
}
@@ -152,12 +153,12 @@
.dialog-accept {
-fx-text-fill: #03A9F4;
-fx-font-weight: BOLD;
-fx-font-family: -fx-base-font-family;
-fx-padding: 0.7em 0.8em;
}
.dialog-cancel {
-fx-font-weight: BOLD;
-fx-font-family: -fx-base-font-family;
-fx-padding: 0.7em 0.8em;
}
@@ -0,0 +1,31 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download;
public class CurseCDNDownloadProvider extends MojangDownloadProvider {
public static final CurseCDNDownloadProvider INSTANCE = new CurseCDNDownloadProvider();
protected CurseCDNDownloadProvider() {
super(false);
}
@Override
public String injectURL(String baseURL) {
return baseURL == null ? null : baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
}
}
@@ -84,17 +84,17 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
case "forge":
return new ForgeInstallTask(this, gameVersion, version, libraryVersion)
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
.then(variables -> new MaintainTask(repository, variables.get("version")))
.then(variables -> new MaintainTask(variables.get("version")))
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
case "liteloader":
return new LiteLoaderInstallTask(this, gameVersion, version, libraryVersion)
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
.then(variables -> new MaintainTask(repository, variables.get("version")))
.then(variables -> new MaintainTask(variables.get("version")))
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
case "optifine":
return new OptiFineInstallTask(this, gameVersion, version, libraryVersion)
.then(variables -> new LibrariesUniqueTask(variables.get("version")))
.then(variables -> new MaintainTask(repository, variables.get("version")))
.then(variables -> new MaintainTask(variables.get("version")))
.then(variables -> new VersionJsonSaveTask(repository, variables.get("version")));
default:
throw new IllegalArgumentException("Library id " + libraryId + " is unrecognized.");
@@ -28,16 +28,14 @@ import java.util.List;
public class MaintainTask extends TaskResult<Version> {
private final GameRepository repository;
private final Version version;
private final String id;
public MaintainTask(GameRepository repository, Version version) {
this(repository, version, ID);
public MaintainTask(Version version) {
this(version, ID);
}
public MaintainTask(GameRepository repository, Version version, String id) {
this.repository = repository;
public MaintainTask(Version version, String id) {
this.version = version;
this.id = id;
}
@@ -26,11 +26,12 @@ import org.jackhuang.hmcl.download.optifine.OptiFineVersionList;
* @see <a href="http://wiki.vg">http://wiki,vg</a>
* @author huangyuhui
*/
public final class MojangDownloadProvider implements DownloadProvider {
public class MojangDownloadProvider implements DownloadProvider {
public static final MojangDownloadProvider INSTANCE = new MojangDownloadProvider();
private boolean isChina;
private MojangDownloadProvider() {
public MojangDownloadProvider(boolean isChina) {
this.isChina = isChina;
}
@Override
@@ -76,9 +77,16 @@ public final class MojangDownloadProvider implements DownloadProvider {
@Override
public String injectURL(String baseURL) {
if (baseURL.contains("net/minecraftforge/forge"))
if (baseURL == null)
return null;
//else if (baseURL.contains("scala-swing") || baseURL.contains("scala-xml") || baseURL.contains("scala-parser-combinators"))
// return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
/*else if (baseURL.contains("typesafe") || baseURL.contains("scala"))
if (isChina)
return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
else
return baseURL.replace("http://files.minecraftforge.net/maven", "http://repo1.maven.org/maven2");
*/else
return baseURL;
else
return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
}
}
@@ -55,7 +55,7 @@ public final class GameAssetDownloadTask extends Task {
*/
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
this.version = version.requireResolved();
this.refreshTask = new GameAssetRefreshTask(dependencyManager, version);
this.dependents.add(refreshTask);
}
@@ -49,7 +49,7 @@ public final class GameAssetIndexDownloadTask extends Task {
*/
public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
this.version = version.requireResolved();
setSignificance(TaskSignificance.MODERATE);
}
@@ -54,7 +54,7 @@ public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File,
*/
public GameAssetRefreshTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
this.version = version.requireResolved();
this.assetIndexInfo = version.getAssetIndex();
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
@@ -48,7 +48,7 @@ public final class GameLibrariesTask extends Task {
*/
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
this.version = version.requireResolved();
setSignificance(TaskSignificance.MODERATE);
}
@@ -49,7 +49,7 @@ public final class GameLoggingDownloadTask extends Task {
*/
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
this.version = version.requireResolved();
setSignificance(TaskSignificance.MODERATE);
}
@@ -44,7 +44,7 @@ public final class VersionJsonSaveTask extends Task {
*/
public VersionJsonSaveTask(DefaultGameRepository repository, Version version) {
this.repository = repository;
this.version = version;
this.version = version.requireResolved();
setSignificance(TaskSignificance.MODERATE);
}
@@ -29,7 +29,7 @@ import java.util.Date;
public class ClassicVersion extends Version {
public ClassicVersion() {
super("Classic", "${auth_player_name} ${auth_session} --workDir ${game_directory}",
super(true, "Classic", "${auth_player_name} ${auth_session} --workDir ${game_directory}",
null, "net.minecraft.client.Minecraft", null, null, null, null,
Arrays.asList(new ClassicLibrary("lwjgl"), new ClassicLibrary("jinput"), new ClassicLibrary("lwjgl_util")),
null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0);
@@ -52,6 +52,10 @@ public interface GameRepository extends VersionProvider {
@Override
Version getVersion(String id) throws VersionNotFoundException;
default Version getResolvedVersion(String id) throws VersionNotFoundException {
return getVersion(id).resolve(this);
}
/**
* How many version are there?
*/
@@ -47,7 +47,10 @@ public class Version implements Comparable<Version>, Validation {
private final Date releaseTime;
private final int minimumLauncherVersion;
public Version(String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion) {
private transient final boolean resolved;
public Version(boolean resolved, String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, List<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, int minimumLauncherVersion) {
this.resolved = resolved;
this.id = id;
this.minecraftArguments = minecraftArguments;
this.arguments = arguments;
@@ -140,11 +143,18 @@ public class Version implements Comparable<Version>, Validation {
return false;
}
public Version requireResolved() {
if (!resolved)
throw new RuntimeException("Version not resolved");
return this;
}
/**
* Resolve given version
*/
public Version resolve(VersionProvider provider) throws VersionNotFoundException {
return resolve(provider, new HashSet<>());
return resolve(provider, new HashSet<>()).setResolved(true);
}
protected Version resolve(VersionProvider provider, Set<String> resolvedSoFar) throws VersionNotFoundException {
@@ -159,7 +169,9 @@ public class Version implements Comparable<Version>, Validation {
// It is supposed to auto install an version in getVersion.
Version parent = provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar);
return new Version(id,
return new Version(
true,
id,
minecraftArguments == null ? parent.minecraftArguments : minecraftArguments,
Arguments.merge(parent.arguments, arguments),
mainClass == null ? parent.mainClass : mainClass,
@@ -167,7 +179,7 @@ public class Version implements Comparable<Version>, Validation {
jar == null ? parent.jar : jar,
assetIndex == null ? parent.assetIndex : assetIndex,
assets == null ? parent.assets : assets,
Lang.merge(parent.libraries, this.libraries),
Lang.merge(this.libraries, parent.libraries),
Lang.merge(parent.compatibilityRules, this.compatibilityRules),
downloads == null ? parent.downloads : downloads,
logging == null ? parent.logging : logging,
@@ -177,36 +189,40 @@ public class Version implements Comparable<Version>, Validation {
Math.max(minimumLauncherVersion, parent.minimumLauncherVersion));
}
public Version setResolved(boolean resolved) {
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setId(String id) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setMinecraftArguments(String minecraftArguments) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setArguments(Arguments arguments) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setMainClass(String mainClass) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setInheritsFrom(String inheritsFrom) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setJar(String jar) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setLibraries(List<Library> libraries) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
public Version setLogging(Map<DownloadType, LoggingInfo> logging) {
return new Version(id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
return new Version(resolved, id, minecraftArguments, arguments, mainClass, inheritsFrom, jar, assetIndex, assets, libraries, compatibilityRules, downloads, logging, type, time, releaseTime, minimumLauncherVersion);
}
@Override
@@ -56,7 +56,7 @@ public abstract class Launcher {
this.listener = listener;
this.daemon = daemon;
version = repository.getVersion(versionId).resolve(repository);
version = repository.getResolvedVersion(versionId);
}
/**
@@ -56,6 +56,7 @@ final class Log4jHandler extends Thread {
private final PipedInputStream inputStream = Lang.invoke(() -> new PipedInputStream(outputStream));
private final AtomicBoolean interrupted = new AtomicBoolean(false);
private final List<String> logs = new LinkedList<>();
private boolean broken = false;
public Log4jHandler(BiConsumer<String, Log4jLevel> callback) {
this.callback = callback;
@@ -95,20 +96,28 @@ final class Log4jHandler extends Thread {
public Future<?> newLine(String log) {
return Schedulers.computation().schedule(() -> {
try {
String line = (log + OperatingSystem.LINE_SEPARATOR)
String line = log
.replace("<![CDATA[", "")
.replace("]]>", "")
.replace("log4j:Event", "log4j_Event")
.replace("<log4j:Message>", "<log4j_Message><![CDATA[")
.replace("</log4j:Message>", "]]></log4j_Message>")
.replace("log4j:Throwable", "log4j_Throwable");
.replace("log4j:", "log4j_")
.replace("<log4j_Message>", "<log4j_Message><![CDATA[")
.replace("</log4j_Message>", "]]></log4j_Message>")
.replace("<log4j_Throwable>", "<log4j_Throwable><![CDATA[")
.replace("</log4j_Throwable>", "]]></log4j_Throwable>");
logs.add(line);
byte[] bytes = line.getBytes(Charsets.UTF_8);
if (broken)
System.out.println(line);
byte[] bytes = (line + OperatingSystem.LINE_SEPARATOR).getBytes(Charsets.UTF_8);
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
// Ignoring IOException, including read end dead.
Logging.LOG.log(Level.WARNING, "An error occurred when writing console lines", e);
if (!broken) {
Logging.LOG.log(Level.WARNING, "An error occurred when writing console lines", e);
logs.forEach(System.out::println);
broken = true;
}
}
});
}