Improvements to ZipPackIndex

- Allow it to work on channels that don't support mapping
- Skip indexing folders that are not part of a pack type
This commit is contained in:
embeddedt 2026-05-23 21:44:14 -04:00
parent 62dbbea083
commit 74f76f7305
No known key found for this signature in database
GPG Key ID: A69433EC199B5613

View File

@ -9,7 +9,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
@ -74,8 +73,8 @@ public class ZipPackIndex {
// Fields
// -------------------------------------------------------------------------
/** Memory-mapped central directory. May be null for empty/invalid zips. */
private final MappedByteBuffer cdBuffer;
/** Central directory buffer (memory-mapped or heap-allocated fallback). May be null for empty/invalid zips. */
private final ByteBuffer cdBuffer;
/** Root of the directory tree, always non-null (may be empty but frozen). */
private final DirNode root;
@ -87,14 +86,14 @@ public class ZipPackIndex {
* Build an index from the zip at the given path. Does not open a {@link ZipFile}
* and does not keep a reference to one; the caller owns all {@link ZipFile} lifecycle.
*
* @throws IOException if the file cannot be read or its central directory cannot be mapped
* @throws IOException if the file cannot be read or its central directory cannot be parsed
*/
public ZipPackIndex(Path zipPath) throws IOException {
this.cdBuffer = mmapCentralDirectory(zipPath);
this.cdBuffer = readCentralDirectory(zipPath);
this.root = buildTree();
}
private static MappedByteBuffer mmapCentralDirectory(Path filePath) throws IOException {
private static ByteBuffer readCentralDirectory(Path filePath) throws IOException {
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
long fileSize = channel.size();
if (fileSize < EOCD_SIZE) return null;
@ -138,13 +137,24 @@ public class ZipPackIndex {
throw new IOException("Invalid central directory range");
}
// Try memory-mapping first; fall back to a heap copy if the OS refuses.
try {
MappedByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, cdOffset, cdSize);
ByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, cdOffset, cdSize);
buf.order(ByteOrder.LITTLE_ENDIAN);
return buf;
} catch (RuntimeException e) {
throw new IOException("Failed to map central directory", e);
} catch (Exception ignored) {
// mmap unavailable (e.g. some Linux mount flags, container restrictions);
// read the central directory into a heap buffer instead.
}
ByteBuffer buf = ByteBuffer.allocate((int) cdSize);
buf.order(ByteOrder.LITTLE_ENDIAN);
while (buf.hasRemaining()) {
int n = channel.read(buf, cdOffset + buf.position());
if (n < 0) throw new IOException("Truncated central directory during heap read");
}
buf.flip();
return buf;
}
}
@ -155,6 +165,11 @@ public class ZipPackIndex {
return treeRoot;
}
// Computed here (not statically) so that any loader-injected PackType values
// registered after class-load are included.
Set<String> packTypeDirs = new HashSet<>();
for (PackType type : PackType.values()) packTypeDirs.add(type.getDirectory());
// Accumulate file offsets per DirNode before compacting to int[]
IdentityHashMap<DirNode, List<Integer>> fileOffsets = new IdentityHashMap<>();
@ -180,6 +195,10 @@ public class ZipPackIndex {
if (!name.isEmpty()) {
String[] parts = name.split("/");
if (!packTypeDirs.contains(parts[0])) {
pos += recordLen;
continue;
}
DirNode current = treeRoot;
int dirDepth = isDirectory ? parts.length : parts.length - 1;
for (int i = 0; i < dirDepth; i++) {