Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 34 additions & 19 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Log4jLevel;
Expand Down Expand Up @@ -273,10 +274,10 @@ private void showLogWindow() {
logWindow.show();
}

private void exportGameCrashInfo() {
private CompletableFuture<Path> exportGameCrashInfo() {
Path logFile = Paths.get("minecraft-exported-crash-info-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".zip").toAbsolutePath();

CompletableFuture.supplyAsync(() ->
return CompletableFuture.supplyAsync(() ->
logs.stream().map(Log::getLog).collect(Collectors.joining("\n")))
.thenComposeAsync(logs -> {
long processStartTime = managedProcess.getProcess().info()
Expand All @@ -301,20 +302,7 @@ private void exportGameCrashInfo() {
return false;
}
});
})
.handleAsync((result, exception) -> {
if (exception == null) {
FXUtils.showFileInExplorer(logFile);
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.success", logFile), i18n("message.success"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();
DialogUtils.show(stackPane, dialog);
} else {
LOG.warning("Failed to export game crash info", exception);
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception), i18n("message.error"), MessageDialogPane.MessageType.ERROR).ok(null).build();
DialogUtils.show(stackPane, dialog);
}

return null;
}, Schedulers.javafx());
}).thenApply(ignored -> logFile);
}

private final class View extends VBox {
Expand Down Expand Up @@ -444,8 +432,35 @@ private final class View extends VBox {
HBox toolBar = new HBox();
VBox.setMargin(toolBar, new Insets(0, 0, 4, 0));
{
JFXButton exportGameCrashInfoButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs"));
exportGameCrashInfoButton.setOnAction(e -> exportGameCrashInfo());
SpinnerPane exportButtonPane = new SpinnerPane();
exportButtonPane.getStyleClass().add("small-spinner-pane");

JFXButton exportButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs"));
exportButtonPane.setContent(exportButton);
exportButton.setOnAction(e -> {
exportButtonPane.showSpinner();
exportGameCrashInfo().whenCompleteAsync((result, exception) -> {
exportButtonPane.hideSpinner();

if (exception == null) {
FXUtils.showFileInExplorer(result);
var dialog = new MessageDialogPane.Builder(
i18n("settings.launcher.launcher_log.export.success", result),
i18n("message.success"),
MessageDialogPane.MessageType.SUCCESS
).ok(null).build();
DialogUtils.show(stackPane, dialog);
} else {
LOG.warning("Failed to export game crash info", exception);
var dialog = new MessageDialogPane.Builder(
i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception),
i18n("message.error"),
MessageDialogPane.MessageType.ERROR
).ok(null).build();
DialogUtils.show(stackPane, dialog);
}
}, Schedulers.javafx());
});

JFXButton logButton = FXUtils.newRaisedButton(i18n("logwindow.title"));
logButton.setOnAction(e -> showLogWindow());
Expand All @@ -457,7 +472,7 @@ private final class View extends VBox {
toolBar.setPadding(new Insets(8));
toolBar.setSpacing(8);
toolBar.getStyleClass().add("jfx-tool-bar");
toolBar.getChildren().setAll(exportGameCrashInfoButton, logButton, helpButton);
toolBar.getChildren().setAll(exportButtonPane, logButton, helpButton);
}

getChildren().setAll(titlePane, infoPane, moddedPane, gameDirPane, toolBar);
Expand Down
141 changes: 77 additions & 64 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXRadioButton;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
Expand All @@ -36,6 +35,7 @@
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
Expand All @@ -46,6 +46,7 @@
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import org.jackhuang.hmcl.util.AprilFools;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
Expand All @@ -61,12 +62,12 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
Expand Down Expand Up @@ -301,12 +302,31 @@ else if (locale.isSameLanguage(currentLocale))
if (LOG.getLogFile() == null)
openLogFolderButton.setDisable(true);

SpinnerPane exportLogPane = new SpinnerPane();

JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export"));
logButton.setOnAction(e -> onExportLogs());
exportLogPane.setContent(logButton);
logButton.setOnAction(e -> {
exportLogPane.showSpinner();
onExportLogs().whenCompleteAsync((result, exception) -> {
exportLogPane.hideSpinner();
if (exception == null) {
Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", result));
FXUtils.showFileInExplorer(result);
} else {
LOG.warning("Failed to export logs", exception);
Controllers.dialog(
i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception),
null,
MessageType.ERROR
);
}
}, Schedulers.javafx());
});

HBox buttonBox = new HBox();
buttonBox.setSpacing(10);
buttonBox.getChildren().addAll(openLogFolderButton, logButton);
buttonBox.getChildren().addAll(openLogFolderButton, exportLogPane);
BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);
debugPane.setRight(buttonBox);

Expand Down Expand Up @@ -378,86 +398,79 @@ private static boolean exportLogFile(ZipOutputStream output,
}
}

private void onExportLogs() {
thread(() -> {
private CompletableFuture<Path> onExportLogs() {
return CompletableFuture.supplyAsync(Lang.wrap(() -> {
String nameBase = "hmcl-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss"));
List<Path> recentLogFiles = LOG.findRecentLogFiles(5);

Path outputFile;
try {
if (recentLogFiles.isEmpty()) {
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".log");
if (recentLogFiles.isEmpty()) {
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".log");

LOG.info("Exporting latest logs to " + outputFile);
try (OutputStream output = Files.newOutputStream(outputFile)) {
LOG.exportLogs(output);
}
} else {
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".zip");

LOG.info("Exporting latest logs to " + outputFile);

byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
try (var os = Files.newOutputStream(outputFile);
var zos = new ZipOutputStream(os)) {

Set<String> entryNames = new HashSet<>();

for (Path path : recentLogFiles) {
String fileName = FileUtils.getName(path);
String extension = StringUtils.substringAfterLast(fileName, '.');

if ("gz".equals(extension) || "xz".equals(extension)) {
// If an exception occurs while decompressing the input file, we should
// ensure the input file and the current zip entry are closed,
// then copy the compressed file content as-is into a new entry in the zip file.

InputStream input = null;
try {
input = Files.newInputStream(path);
input = "gz".equals(extension)
? new GZIPInputStream(input)
: new XZInputStream(input);
} catch (Throwable ex) {
LOG.warning("Failed to open log file " + path, ex);
IOUtils.closeQuietly(input, ex);
input = null;
}

String entryName = getEntryName(entryNames, StringUtils.substringBeforeLast(fileName, "."));
if (input != null && exportLogFile(zos, path, entryName, input, buffer))
continue;
}
LOG.info("Exporting latest logs to " + outputFile);
try (OutputStream output = Files.newOutputStream(outputFile)) {
LOG.exportLogs(output);
}
} else {
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".zip");

LOG.info("Exporting latest logs to " + outputFile);

// Copy the log file content as-is into a new entry in the zip file.
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
try (var os = Files.newOutputStream(outputFile);
var zos = new ZipOutputStream(os)) {

Set<String> entryNames = new HashSet<>();

for (Path path : recentLogFiles) {
String fileName = FileUtils.getName(path);
String extension = StringUtils.substringAfterLast(fileName, '.');

if ("gz".equals(extension) || "xz".equals(extension)) {
// If an exception occurs while decompressing the input file, we should
// ensure the input file and the current zip entry are closed.
// ensure the input file and the current zip entry are closed,
// then copy the compressed file content as-is into a new entry in the zip file.

InputStream input;
InputStream input = null;
try {
input = Files.newInputStream(path);
input = "gz".equals(extension)
? new GZIPInputStream(input)
: new XZInputStream(input);
} catch (Throwable ex) {
LOG.warning("Failed to open log file " + path, ex);
continue;
IOUtils.closeQuietly(input, ex);
input = null;
}

exportLogFile(zos, path, getEntryName(entryNames, fileName), input, buffer);
String entryName = getEntryName(entryNames, StringUtils.substringBeforeLast(fileName, "."));
if (input != null && exportLogFile(zos, path, entryName, input, buffer))
continue;
}

zos.putNextEntry(new ZipEntry(getEntryName(entryNames, "hmcl-latest.log")));
LOG.exportLogs(zos);
zos.closeEntry();
// Copy the log file content as-is into a new entry in the zip file.
// If an exception occurs while decompressing the input file, we should
// ensure the input file and the current zip entry are closed.

InputStream input;
try {
input = Files.newInputStream(path);
} catch (Throwable ex) {
LOG.warning("Failed to open log file " + path, ex);
continue;
}

exportLogFile(zos, path, getEntryName(entryNames, fileName), input, buffer);
}

zos.putNextEntry(new ZipEntry(getEntryName(entryNames, "hmcl-latest.log")));
LOG.exportLogs(zos);
zos.closeEntry();
}
} catch (IOException e) {
LOG.warning("Failed to export logs", e);
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(e), null, MessageType.ERROR));
return;
}

Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", outputFile)));
FXUtils.showFileInExplorer(outputFile);
});
return outputFile;
}), Schedulers.io());
}

private void onSponsor() {
Expand Down