From 24fd2cf521cc1391a1cc00ccfdb159ad4957212b Mon Sep 17 00:00:00 2001 From: Glavo Date: Sun, 7 Sep 2025 22:31:44 +0800 Subject: [PATCH] =?UTF-8?q?Fix=20#4409:=20=E4=BF=AE=E5=A4=8D=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E5=8E=86=E5=8F=B2=E6=97=A5=E5=BF=97=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=BA=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98=20(#4411?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/ui/main/SettingsPage.java | 101 +++++++++++++----- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index 60aa5dd57..f937da70d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -35,17 +35,15 @@ import org.jackhuang.hmcl.upgrade.UpdateHandler; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.Locales; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.io.IOUtils; import org.tukaani.xz.XZInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -132,6 +130,42 @@ public final class SettingsPage extends SettingsView { UpdateHandler.updateFrom(target); } + /// This method guarantees to close both `input` and the current zip entry. + /// + /// If no exception occurs, this method returns `true`; + /// If an exception occurs while reading from `input`, this method returns `false`; + /// If an exception occurs while writing to `output`, this method will throw it as is. + private static boolean exportLogFile(ZipOutputStream output, + Path file, // For logging + String entryName, + InputStream input, + byte[] buffer) throws IOException { + //noinspection TryFinallyCanBeTryWithResources + try { + output.putNextEntry(new ZipEntry(entryName)); + int read; + while (true) { + try { + read = input.read(buffer); + if (read <= 0) + return true; + } catch (Throwable ex) { + LOG.warning("Failed to decompress log file " + file, ex); + return false; + } + + output.write(buffer, 0, read); + } + } finally { + try { + input.close(); + } catch (Throwable ex) { + LOG.warning("Failed to close log file " + file, ex); + } + output.closeEntry(); + } + } + @Override protected void onExportLogs() { thread(() -> { @@ -152,45 +186,54 @@ public final class SettingsPage extends SettingsView { LOG.info("Exporting latest logs to " + outputFile); - Path tempFile = Files.createTempFile("hmcl-decompress-log-", ".txt"); - try (var tempChannel = FileChannel.open(tempFile, StandardOpenOption.READ, StandardOpenOption.WRITE); - var os = Files.newOutputStream(outputFile); + byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + try (var os = Files.newOutputStream(outputFile); var zos = new ZipOutputStream(os)) { for (Path path : recentLogFiles) { - String extension = FileUtils.getExtension(path); - decompress: - if ("gz".equalsIgnoreCase(extension) || "xz".equalsIgnoreCase(extension)) { - try (InputStream fis = Files.newInputStream(path); - InputStream uncompressed = "gz".equalsIgnoreCase(extension) - ? new GZIPInputStream(fis) - : new XZInputStream(fis)) { - uncompressed.transferTo(Channels.newOutputStream(tempChannel)); - } catch (IOException e) { - LOG.warning("Failed to decompress log: " + path, e); - break decompress; + 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; } - zos.putNextEntry(new ZipEntry(StringUtils.substringBeforeLast(FileUtils.getName(path), '.'))); - Channels.newInputStream(tempChannel).transferTo(zos); - zos.closeEntry(); - tempChannel.truncate(0); + String entryName = StringUtils.substringBeforeLast(fileName, "."); + if (input != null && exportLogFile(zos, path, entryName, input, buffer)) + continue; + } + + // 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; } - zos.putNextEntry(new ZipEntry(FileUtils.getName(path))); - Files.copy(path, zos); - zos.closeEntry(); + exportLogFile(zos, path, fileName, input, buffer); } zos.putNextEntry(new ZipEntry("hmcl-latest.log")); LOG.exportLogs(zos); zos.closeEntry(); - } finally { - try { - Files.deleteIfExists(tempFile); - } catch (IOException ignored) { - } } } } catch (IOException e) {