优化 ArchiveFileTree (#4177)

This commit is contained in:
Glavo 2025-08-02 19:33:44 +08:00 committed by GitHub
parent 6230b436ad
commit 9969dc60c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 134 additions and 20 deletions

View File

@ -66,7 +66,7 @@ public final class CompressingUtils {
cd.reset();
byte[] ba = entry.getRawName();
int clen = (int)(ba.length * cd.maxCharsPerByte());
int clen = (int) (ba.length * cd.maxCharsPerByte());
if (clen == 0) continue;
if (clen <= cb.capacity())
cb.clear();
@ -129,7 +129,19 @@ public final class CompressingUtils {
}
public static ZipArchiveReader openZipFile(Path zipFile) throws IOException {
return new ZipArchiveReader(Files.newByteChannel(zipFile));
ZipArchiveReader zipReader = new ZipArchiveReader(Files.newByteChannel(zipFile));
Charset suitableEncoding;
try {
suitableEncoding = findSuitableEncoding(zipReader);
if (suitableEncoding == StandardCharsets.UTF_8)
return zipReader;
} catch (Throwable e) {
IOUtils.closeQuietly(zipReader, e);
throw e;
}
zipReader.close();
return new ZipArchiveReader(Files.newByteChannel(zipFile), suitableEncoding);
}
public static ZipArchiveReader openZipFile(Path zipFile, Charset charset) throws IOException {
@ -222,8 +234,8 @@ public final class CompressingUtils {
*
* @param zipFile the zip file
* @param name the location of the text in zip file, something like A/B/C/D.txt
* @throws IOException if the file is not a valid zip file.
* @return the plain text content of given file.
* @throws IOException if the file is not a valid zip file.
*/
public static String readTextZipEntry(File zipFile, String name) throws IOException {
try (ZipArchiveReader s = new ZipArchiveReader(zipFile.toPath())) {
@ -236,8 +248,8 @@ public final class CompressingUtils {
*
* @param zipFile the zip file
* @param name the location of the text in zip file, something like A/B/C/D.txt
* @throws IOException if the file is not a valid zip file.
* @return the plain text content of given file.
* @throws IOException if the file is not a valid zip file.
*/
public static String readTextZipEntry(ZipArchiveReader zipFile, String name) throws IOException {
return IOUtils.readFullyAsString(zipFile.getInputStream(zipFile.getEntry(name)));
@ -248,8 +260,8 @@ public final class CompressingUtils {
*
* @param zipFile the zip file
* @param name the location of the text in zip file, something like A/B/C/D.txt
* @throws IOException if the file is not a valid zip file.
* @return the plain text content of given file.
* @throws IOException if the file is not a valid zip file.
*/
public static String readTextZipEntry(Path zipFile, String name, Charset encoding) throws IOException {
try (ZipArchiveReader s = openZipFile(zipFile, encoding)) {

View File

@ -19,6 +19,9 @@ package org.jackhuang.hmcl.util.tree;
import kala.compress.archivers.ArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import java.io.Closeable;
import java.io.IOException;
@ -48,7 +51,7 @@ public abstract class ArchiveFileTree<F, E extends ArchiveEntry> implements Clos
}
protected final F file;
protected final Dir<E> root = new Dir<>();
protected final Dir<E> root = new Dir<>("");
public ArchiveFileTree(F file) {
this.file = file;
@ -62,7 +65,7 @@ public abstract class ArchiveFileTree<F, E extends ArchiveEntry> implements Clos
return root;
}
public void addEntry(E entry) throws IOException {
protected void addEntry(E entry) throws IOException {
String[] path = entry.getName().split("/");
Dir<E> dir = root;
@ -78,7 +81,7 @@ public abstract class ArchiveFileTree<F, E extends ArchiveEntry> implements Clos
throw new IOException("A file and a directory have the same name: " + entry.getName());
}
dir = dir.subDirs.computeIfAbsent(item, name -> new Dir<>());
dir = dir.subDirs.computeIfAbsent(item, Dir::new);
}
if (entry.isDirectory()) {
@ -113,16 +116,33 @@ public abstract class ArchiveFileTree<F, E extends ArchiveEntry> implements Clos
public abstract void close() throws IOException;
public static final class Dir<E extends ArchiveEntry> {
E entry;
private final String name;
private E entry;
final Map<String, Dir<E>> subDirs = new HashMap<>();
final Map<String, E> files = new HashMap<>();
public Map<String, Dir<E>> getSubDirs() {
public Dir(String name) {
this.name = name;
}
public boolean isRoot() {
return name.isEmpty();
}
public @NotNull String getName() {
return name;
}
public @Nullable E getEntry() {
return entry;
}
public @NotNull @UnmodifiableView Map<String, Dir<E>> getSubDirs() {
return subDirs;
}
public Map<String, E> getFiles() {
public @NotNull @UnmodifiableView Map<String, E> getFiles() {
return files;
}
}

View File

@ -19,6 +19,7 @@ package org.jackhuang.hmcl.util.tree;
import kala.compress.archivers.zip.ZipArchiveEntry;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.jackhuang.hmcl.util.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
@ -27,24 +28,29 @@ import java.io.InputStream;
* @author Glavo
*/
public final class ZipFileTree extends ArchiveFileTree<ZipArchiveReader, ZipArchiveEntry> {
private final boolean closeReader;
public ZipFileTree(ZipArchiveReader file) throws IOException {
this(file, true);
}
public ZipFileTree(ZipArchiveReader file, boolean closeReader) throws IOException {
super(file);
this.closeReader = closeReader;
try {
for (ZipArchiveEntry zipArchiveEntry : file.getEntries()) {
addEntry(zipArchiveEntry);
}
} catch (Throwable e) {
try {
file.close();
} catch (Throwable e2) {
e.addSuppressed(e2);
}
if (closeReader)
IOUtils.closeQuietly(file, e);
throw e;
}
}
@Override
public void close() throws IOException {
if (closeReader)
file.close();
}

View File

@ -0,0 +1,76 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.tree;
import kala.compress.archivers.zip.ZipArchiveReader;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Glavo
*/
public final class ZipFileTreeTest {
private static Path getTestFile(String name) {
try {
return Path.of(ZipFileTreeTest.class.getResource("/zip/" + name).toURI());
} catch (URISyntaxException | NullPointerException e) {
throw new AssertionError("Resource not found: " + name, e);
}
}
@Test
public void testClose() throws IOException {
Path testFile = getTestFile("utf-8.zip");
try (var channel = FileChannel.open(testFile, StandardOpenOption.READ)) {
var reader = new ZipArchiveReader(channel);
try (var ignored = new ZipFileTree(reader, false)) {
}
assertTrue(channel.isOpen());
try (var ignored = new ZipFileTree(reader)) {
}
assertFalse(channel.isOpen());
}
}
@Test
public void test() throws IOException {
Path testFile = getTestFile("utf-8.zip");
try (var tree = new ZipFileTree(new ZipArchiveReader(testFile))) {
var root = tree.getRoot();
assertEquals(2, root.getFiles().size());
assertEquals(0, root.getSubDirs().size());
assertEquals("test.txt", root.getFiles().get("test.txt").getName());
assertEquals("中文.txt", root.getFiles().get("中文.txt").getName());
assertNull(root.getFiles().get("other.txt"));
}
}
}