导出日志时允许重名文件 (#4733)

This commit is contained in:
Glavo
2026-01-23 22:41:07 +08:00
committed by GitHub
parent 165d77df30
commit 95eeae4c42
3 changed files with 49 additions and 22 deletions

View File

@@ -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);

View File

@@ -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<String> 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<String> 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();
}

View File

@@ -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<String> 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;
@@ -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<String> 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();
}