From 95eeae4c426366cb629b1cffc7e8f7a38bbcb82a Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 23 Jan 2026 22:41:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=BC=E5=87=BA=E6=97=A5=E5=BF=97=E6=97=B6?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E9=87=8D=E5=90=8D=E6=96=87=E4=BB=B6=20(#4733?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/game/LogExporter.java | 2 +- .../jackhuang/hmcl/ui/main/SettingsPage.java | 25 ++++++++--- .../org/jackhuang/hmcl/util/io/Zipper.java | 44 ++++++++++++------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java index c21b7cb7e..3ace4df05 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java @@ -64,7 +64,7 @@ public final class LogExporter { } return CompletableFuture.runAsync(() -> { - try (Zipper zipper = new Zipper(zipFile)) { + try (Zipper zipper = new Zipper(zipFile, true)) { processLogs(runDirectory.resolve("liteconfig"), "*.log", "liteconfig", zipper, logMatcher); processLogs(runDirectory.resolve("logs"), "*.log", "logs", zipper, logMatcher); processLogs(runDirectory, "*.log", "runDirectory", zipper, logMatcher); 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 4b9a5a405..1ad646e66 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 @@ -63,9 +63,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -330,6 +328,19 @@ public final class SettingsPage extends ScrollPane { UpdateHandler.updateFrom(target); } + private static String getEntryName(Set entryNames, String name) { + if (entryNames.add(name)) { + return name; + } + + for (long i = 1; ; i++) { + String newName = name + "." + i; + if (entryNames.add(newName)) { + return newName; + } + } + } + /// This method guarantees to close both `input` and the current zip entry. /// /// If no exception occurs, this method returns `true`; @@ -389,6 +400,8 @@ public final class SettingsPage extends ScrollPane { try (var os = Files.newOutputStream(outputFile); var zos = new ZipOutputStream(os)) { + Set entryNames = new HashSet<>(); + for (Path path : recentLogFiles) { String fileName = FileUtils.getName(path); String extension = StringUtils.substringAfterLast(fileName, '.'); @@ -410,7 +423,7 @@ public final class SettingsPage extends ScrollPane { input = null; } - String entryName = StringUtils.substringBeforeLast(fileName, "."); + String entryName = getEntryName(entryNames, StringUtils.substringBeforeLast(fileName, ".")); if (input != null && exportLogFile(zos, path, entryName, input, buffer)) continue; } @@ -427,10 +440,10 @@ public final class SettingsPage extends ScrollPane { continue; } - exportLogFile(zos, path, fileName, input, buffer); + exportLogFile(zos, path, getEntryName(entryNames, fileName), input, buffer); } - zos.putNextEntry(new ZipEntry("hmcl-latest.log")); + zos.putNextEntry(new ZipEntry(getEntryName(entryNames, "hmcl-latest.log"))); LOG.exportLogs(zos); zos.closeEntry(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java index 51e8220c9..80e2bfd35 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/Zipper.java @@ -25,6 +25,8 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipException; @@ -39,13 +41,15 @@ public final class Zipper implements Closeable { private final ZipOutputStream zos; private final byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; + private final Set entryNames; public Zipper(Path zipFile) throws IOException { - this(zipFile, StandardCharsets.UTF_8); + this(zipFile, false); } - public Zipper(Path zipFile, Charset encoding) throws IOException { - this.zos = new ZipOutputStream(Files.newOutputStream(zipFile), encoding); + public Zipper(Path zipFile, boolean allowDuplicateEntry) throws IOException { + this.zos = new ZipOutputStream(Files.newOutputStream(zipFile), StandardCharsets.UTF_8); + this.entryNames = allowDuplicateEntry ? new HashSet<>() : null; } private static String normalize(String path) { @@ -57,6 +61,20 @@ public final class Zipper implements Closeable { return path; } + private ZipEntry newEntry(String name) throws IOException { + if (entryNames == null || name.endsWith("/") || entryNames.add(name)) + return new ZipEntry(name); + + for (int i = 1; i < 10; i++) { + String newName = name + "." + i; + if (entryNames.add(newName)) { + return new ZipEntry(newName); + } + } + + throw new ZipException("duplicate entry: " + name); + } + private static String resolve(String dir, String file) { if (dir.isEmpty()) return file; if (file.isEmpty()) return dir; @@ -71,7 +89,7 @@ public final class Zipper implements Closeable { /** * Compress all the files in sourceDir * - * @param source the file in basePath to be compressed + * @param source the file in basePath to be compressed * @param targetDir the path of the directory in this zip file. */ public void putDirectory(Path source, String targetDir) throws IOException { @@ -81,9 +99,9 @@ public final class Zipper implements Closeable { /** * Compress all the files in sourceDir * - * @param source the file in basePath to be compressed + * @param source the file in basePath to be compressed * @param targetDir the path of the directory in this zip file. - * @param filter returns false if you do not want that file or directory + * @param filter returns false if you do not want that file or directory */ public void putDirectory(Path source, String targetDir, ExceptionalPredicate filter) throws IOException { String root = normalize(targetDir); @@ -118,16 +136,12 @@ public final class Zipper implements Closeable { }); } - public void putFile(File file, String path) throws IOException { - putFile(file.toPath(), path); - } - public void putFile(Path file, String path) throws IOException { path = normalize(path); BasicFileAttributes attrs = Files.readAttributes(file, BasicFileAttributes.class); - ZipEntry entry = new ZipEntry(attrs.isDirectory() ? path + "/" : path); + ZipEntry entry = newEntry(attrs.isDirectory() ? path + "/" : path); entry.setCreationTime(attrs.creationTime()); entry.setLastAccessTime(attrs.lastAccessTime()); entry.setLastModifiedTime(attrs.lastModifiedTime()); @@ -149,13 +163,13 @@ public final class Zipper implements Closeable { } public void putStream(InputStream in, String path) throws IOException { - zos.putNextEntry(new ZipEntry(normalize(path))); + zos.putNextEntry(newEntry(normalize(path))); IOUtils.copyTo(in, zos, buffer); zos.closeEntry(); } public OutputStream putStream(String path) throws IOException { - zos.putNextEntry(new ZipEntry(normalize(path))); + zos.putNextEntry(newEntry(normalize(path))); return new OutputStream() { public void write(int b) throws IOException { zos.write(b); @@ -180,7 +194,7 @@ public final class Zipper implements Closeable { } public void putLines(Stream lines, String path) throws IOException { - zos.putNextEntry(new ZipEntry(normalize(path))); + zos.putNextEntry(newEntry(normalize(path))); try { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zos)); @@ -205,7 +219,7 @@ public final class Zipper implements Closeable { } public void putTextFile(String text, Charset encoding, String path) throws IOException { - zos.putNextEntry(new ZipEntry(normalize(path))); + zos.putNextEntry(newEntry(normalize(path))); zos.write(text.getBytes(encoding)); zos.closeEntry(); }