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:
parent
62dbbea083
commit
74f76f7305
|
|
@ -9,7 +9,6 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.MappedByteBuffer;
|
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
@ -74,8 +73,8 @@ public class ZipPackIndex {
|
||||||
// Fields
|
// Fields
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Memory-mapped central directory. May be null for empty/invalid zips. */
|
/** Central directory buffer (memory-mapped or heap-allocated fallback). May be null for empty/invalid zips. */
|
||||||
private final MappedByteBuffer cdBuffer;
|
private final ByteBuffer cdBuffer;
|
||||||
/** Root of the directory tree, always non-null (may be empty but frozen). */
|
/** Root of the directory tree, always non-null (may be empty but frozen). */
|
||||||
private final DirNode root;
|
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}
|
* 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.
|
* 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 {
|
public ZipPackIndex(Path zipPath) throws IOException {
|
||||||
this.cdBuffer = mmapCentralDirectory(zipPath);
|
this.cdBuffer = readCentralDirectory(zipPath);
|
||||||
this.root = buildTree();
|
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)) {
|
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
|
||||||
long fileSize = channel.size();
|
long fileSize = channel.size();
|
||||||
if (fileSize < EOCD_SIZE) return null;
|
if (fileSize < EOCD_SIZE) return null;
|
||||||
|
|
@ -138,13 +137,24 @@ public class ZipPackIndex {
|
||||||
throw new IOException("Invalid central directory range");
|
throw new IOException("Invalid central directory range");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try memory-mapping first; fall back to a heap copy if the OS refuses.
|
||||||
try {
|
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);
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
return buf;
|
return buf;
|
||||||
} catch (RuntimeException e) {
|
} catch (Exception ignored) {
|
||||||
throw new IOException("Failed to map central directory", e);
|
// 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;
|
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[]
|
// Accumulate file offsets per DirNode before compacting to int[]
|
||||||
IdentityHashMap<DirNode, List<Integer>> fileOffsets = new IdentityHashMap<>();
|
IdentityHashMap<DirNode, List<Integer>> fileOffsets = new IdentityHashMap<>();
|
||||||
|
|
||||||
|
|
@ -180,6 +195,10 @@ public class ZipPackIndex {
|
||||||
|
|
||||||
if (!name.isEmpty()) {
|
if (!name.isEmpty()) {
|
||||||
String[] parts = name.split("/");
|
String[] parts = name.split("/");
|
||||||
|
if (!packTypeDirs.contains(parts[0])) {
|
||||||
|
pos += recordLen;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
DirNode current = treeRoot;
|
DirNode current = treeRoot;
|
||||||
int dirDepth = isDirectory ? parts.length : parts.length - 1;
|
int dirDepth = isDirectory ? parts.length : parts.length - 1;
|
||||||
for (int i = 0; i < dirDepth; i++) {
|
for (int i = 0; i < dirDepth; i++) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user