Skip to content

Commit 2c7541f

Browse files
committed
cache key existence and sizes in ReadOnlyZipStore
1 parent bf52f26 commit 2c7541f

1 file changed

Lines changed: 70 additions & 25 deletions

File tree

src/main/java/dev/zarr/zarrjava/store/ReadOnlyZipStore.java

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import java.nio.ByteBuffer;
1313
import java.nio.file.Path;
1414
import java.nio.file.Paths;
15+
import java.util.LinkedHashMap;
1516
import java.util.LinkedHashSet;
17+
import java.util.Map;
1618
import java.util.Set;
1719
import java.util.stream.Stream;
1820

@@ -24,6 +26,10 @@
2426
*/
2527
public class ReadOnlyZipStore extends ZipStore {
2628

29+
private Map<String, Long> fileIndex;
30+
private Set<String> directoryIndex;
31+
private boolean isCached = false;
32+
2733
public ReadOnlyZipStore(@Nonnull StoreHandle underlyingStore) {
2834
super(underlyingStore);
2935
}
@@ -36,6 +42,33 @@ public ReadOnlyZipStore(@Nonnull String underlyingStorePath) {
3642
this(Paths.get(underlyingStorePath));
3743
}
3844

45+
private synchronized void ensureCache() {
46+
if (isCached) return;
47+
48+
fileIndex = new LinkedHashMap<>();
49+
directoryIndex = new LinkedHashSet<>();
50+
51+
InputStream inputStream = underlyingStore.getInputStream();
52+
if (inputStream == null) {
53+
isCached = true;
54+
return;
55+
}
56+
57+
try (ZipArchiveInputStream zis = new ZipArchiveInputStream(inputStream)) {
58+
ZipArchiveEntry entry;
59+
while ((entry = zis.getNextEntry()) != null) {
60+
String name = normalizeEntryName(entry.getName());
61+
if (entry.isDirectory()) {
62+
directoryIndex.add(name);
63+
} else {
64+
fileIndex.put(name, entry.getSize());
65+
}
66+
}
67+
} catch (IOException ignored) {
68+
}
69+
isCached = true;
70+
}
71+
3972
String resolveKeys(String[] keys) {
4073
return String.join("/", keys);
4174
}
@@ -46,7 +79,8 @@ String[] resolveEntryKeys(String entryKey) {
4679

4780
@Override
4881
public boolean exists(String[] keys) {
49-
return get(keys, 0, 0) != null;
82+
ensureCache();
83+
return fileIndex.containsKey(resolveKeys(keys));
5084
}
5185

5286
@Nullable
@@ -64,6 +98,12 @@ public ByteBuffer get(String[] keys, long start) {
6498
@Nullable
6599
@Override
66100
public ByteBuffer get(String[] keys, long start, long end) {
101+
ensureCache();
102+
String key = resolveKeys(keys);
103+
if (!fileIndex.containsKey(key)) {
104+
return null;
105+
}
106+
67107
InputStream inputStream = underlyingStore.getInputStream();
68108
if (inputStream == null) {
69109
return null;
@@ -128,52 +168,40 @@ public String toString() {
128168

129169
@Override
130170
public Stream<String[]> list(String[] prefixKeys) {
171+
ensureCache();
131172
Stream.Builder<String[]> builder = Stream.builder();
132-
InputStream inputStream = underlyingStore.getInputStream();
133-
if (inputStream == null) return builder.build();
134173

135174
String prefix = resolveKeys(prefixKeys);
136175
if (!prefix.isEmpty() && !prefix.endsWith("/")) {
137176
prefix += "/";
138177
}
139178

140-
try (ZipArchiveInputStream zis = new ZipArchiveInputStream(inputStream)) {
141-
ZipArchiveEntry entry;
142-
while ((entry = zis.getNextEntry()) != null) {
143-
String name = normalizeEntryName(entry.getName());
144-
if (name.startsWith(prefix) && !entry.isDirectory()) {
145-
builder.add(resolveEntryKeys(name.substring(prefix.length())));
146-
}
179+
for (String name : fileIndex.keySet()) {
180+
if (name.startsWith(prefix)) {
181+
builder.add(resolveEntryKeys(name.substring(prefix.length())));
147182
}
148-
} catch (IOException ignored) {
149183
}
150184
return builder.build();
151185
}
152186

153187
@Override
154188
public Stream<String> listChildren(String[] prefixKeys) {
189+
ensureCache();
155190
Set<String> children = new LinkedHashSet<>();
156-
InputStream inputStream = underlyingStore.getInputStream();
157-
if (inputStream == null) return Stream.empty();
158191

159192
String prefix = resolveKeys(prefixKeys);
160193
if (!prefix.isEmpty() && !prefix.endsWith("/")) {
161194
prefix += "/";
162195
}
163196

164-
try (ZipArchiveInputStream zis = new ZipArchiveInputStream(inputStream)) {
165-
ZipArchiveEntry entry;
166-
while ((entry = zis.getNextEntry()) != null) {
167-
String name = normalizeEntryName(entry.getName());
168-
169-
if (name.startsWith(prefix) && !name.equals(prefix)) {
170-
String relative = name.substring(prefix.length());
171-
String[] parts = relative.split("/");
172-
children.add(parts[0]);
173-
}
197+
String finalPrefix = prefix;
198+
Stream.concat(fileIndex.keySet().stream(), directoryIndex.stream()).forEach(name -> {
199+
if (name.startsWith(finalPrefix) && !name.equals(finalPrefix)) {
200+
String relative = name.substring(finalPrefix.length());
201+
String[] parts = relative.split("/");
202+
children.add(parts[0]);
174203
}
175-
} catch (IOException ignored) {
176-
}
204+
});
177205

178206
return children.stream();
179207
}
@@ -187,6 +215,12 @@ private String normalizeEntryName(String name) {
187215

188216
@Override
189217
public InputStream getInputStream(String[] keys, long start, long end) {
218+
ensureCache();
219+
String key = resolveKeys(keys);
220+
if (!fileIndex.containsKey(key)) {
221+
return null;
222+
}
223+
190224
InputStream baseStream = underlyingStore.getInputStream();
191225

192226
try {
@@ -221,6 +255,17 @@ public InputStream getInputStream(String[] keys, long start, long end) {
221255

222256
@Override
223257
public long getSize(String[] keys) {
258+
ensureCache();
259+
String key = resolveKeys(keys);
260+
Long cachedSize = fileIndex.get(key);
261+
if (cachedSize == null) {
262+
return -1;
263+
}
264+
if (cachedSize >= 0) {
265+
return cachedSize;
266+
}
267+
268+
// if size is not in header/cache, we fallback to reading
224269
InputStream inputStream = underlyingStore.getInputStream();
225270
if (inputStream == null) {
226271
throw new RuntimeException(new IOException("Underlying store input stream is null"));

0 commit comments

Comments
 (0)