Skip to content

Commit f762534

Browse files
Zip Store (#37)
* add ZipStore tests diff --git c/src/main/java/dev/zarr/zarrjava/store/ZipStore.java i/src/main/java/dev/zarr/zarrjava/store/ZipStore.java new file mode 100644 index 0000000..054917f --- /dev/null +++ i/src/main/java/dev/zarr/zarrjava/store/ZipStore.java @@ -0,0 +1,72 @@ +package dev.zarr.zarrjava.store; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +public class ZipStore implements Store, Store.ListableStore { + @nonnull + private final Path path; + + public ZipStore(@nonnull Path path) { + this.path = path; + } + + public ZipStore(@nonnull String path) { + this.path = Paths.get(path); + } + + + @OverRide + public Stream<String> list(String[] keys) { + return Stream.empty(); + } + + @OverRide + public boolean exists(String[] keys) { + return false; + } + + @nullable + @OverRide + public ByteBuffer get(String[] keys) { + return null; + } + + @nullable + @OverRide + public ByteBuffer get(String[] keys, long start) { + return null; + } + + @nullable + @OverRide + public ByteBuffer get(String[] keys, long start, long end) { + return null; + } + + @OverRide + public void set(String[] keys, ByteBuffer bytes) { + + } + + @OverRide + public void delete(String[] keys) { + + } + + @nonnull + @OverRide + public StoreHandle resolve(String... keys) { + return new StoreHandle(this, keys); + } + + @OverRide + public String toString() { + return this.path.toUri().toString().replaceAll("\\/$", ""); + } + +} diff --git c/src/test/java/dev/zarr/zarrjava/Utils.java i/src/test/java/dev/zarr/zarrjava/Utils.java new file mode 100644 index 0000000..0026200 --- /dev/null +++ i/src/test/java/dev/zarr/zarrjava/Utils.java @@ -0,0 +1,40 @@ +package dev.zarr.zarrjava; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class Utils { + + static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException { + if (fileToZip.isHidden()) { + return; + } + if (fileToZip.isDirectory()) { + if (fileName.endsWith("/")) { + zipOut.putNextEntry(new ZipEntry(fileName)); + zipOut.closeEntry(); + } else { + zipOut.putNextEntry(new ZipEntry(fileName + "/")); + zipOut.closeEntry(); + } + File[] children = fileToZip.listFiles(); + for (File childFile : children) { + zipFile(childFile, fileName + "/" + childFile.getName(), zipOut); + } + return; + } + FileInputStream fis = new FileInputStream(fileToZip); + ZipEntry zipEntry = new ZipEntry(fileName); + zipOut.putNextEntry(zipEntry); + byte[] bytes = new byte[1024]; + int length; + while ((length = fis.read(bytes)) >= 0) { + zipOut.write(bytes, 0, length); + } + fis.close(); + } + +} diff --git c/src/test/java/dev/zarr/zarrjava/ZarrStoreTest.java i/src/test/java/dev/zarr/zarrjava/ZarrStoreTest.java index 4a369c9..c7d2ab4 100644 --- c/src/test/java/dev/zarr/zarrjava/ZarrStoreTest.java +++ i/src/test/java/dev/zarr/zarrjava/ZarrStoreTest.java @@ -7,16 +7,22 @@ import dev.zarr.zarrjava.v3.*; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.CsvSource; import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.util.Arrays; import java.util.stream.Stream; +import java.nio.file.Path; +import java.util.zip.ZipOutputStream; +import static dev.zarr.zarrjava.Utils.zipFile; import static dev.zarr.zarrjava.v3.Node.makeObjectMapper; public class ZarrStoreTest extends ZarrTest { @@ -132,4 +138,72 @@ public class ZarrStoreTest extends ZarrTest { Assertions.assertEquals("test group", attrs.getString("description")); } + + @test + public void testZipStore() throws ZarrException, IOException { + Path sourceDir = TESTOUTPUT.resolve("testZipStore"); + Path targetDir = TESTOUTPUT.resolve("testZipStore.zip"); + FilesystemStore fsStore = new FilesystemStore(sourceDir); + writeTestGroupV3(fsStore, true); + + FileOutputStream fos = new FileOutputStream(targetDir.toFile()); + ZipOutputStream zipOut = new ZipOutputStream(fos); + + File fileToZip = new File(sourceDir.toUri()); + zipFile(fileToZip, fileToZip.getName(), zipOut); + zipOut.close(); + fos.close(); + + ZipStore zipStore = new ZipStore(targetDir); + assertIsTestGroupV3(Group.open(zipStore.resolve()), true); + } + + static Stream<Store> localStores() { + return Stream.of( +// new ConcurrentMemoryStore(), + new FilesystemStore(TESTOUTPUT.resolve("testLocalStoresFS")), + new ZipStore(TESTOUTPUT.resolve("testLocalStoresZIP.zip")) + ); + } + + @ParameterizedTest + @MethodSource("localStores") + public void testLocalStores(Store store) throws IOException, ZarrException { + boolean useParallel = true; + Group group = writeTestGroupV3(store, useParallel); + assertIsTestGroupV3(group, useParallel); + } + + int[] testData(){ + int[] testData = new int[1024 * 1024]; + Arrays.setAll(testData, p -> p); + return testData; + } + + Group writeTestGroupV3(Store store, boolean useParallel) throws ZarrException, IOException { + StoreHandle storeHandle = store.resolve(); + + Group group = Group.create(storeHandle); + Array array = group.createArray("array", b -> b + .withShape(1024, 1024) + .withDataType(DataType.UINT32) + .withChunkShape(5, 5) + ); + array.write(ucar.ma2.Array.factory(ucar.ma2.DataType.UINT, new int[]{1024, 1024}, testData()), useParallel); + group.createGroup("subgroup"); + group.setAttributes(new Attributes(b -> b.set("some", "value"))); + return group; + } + + void assertIsTestGroupV3(Group group, boolean useParallel) throws ZarrException { + Stream<dev.zarr.zarrjava.core.Node> nodes = group.list(); + Assertions.assertEquals(2, nodes.count()); + Array array = (Array) group.get("array"); + Assertions.assertNotNull(array); + ucar.ma2.Array result = array.read(useParallel); + Assertions.assertArrayEquals(testData(), (int[]) result.get1DJavaArray(ucar.ma2.DataType.UINT)); + Attributes attrs = group.metadata().attributes; + Assertions.assertNotNull(attrs); + Assertions.assertEquals("value", attrs.getString("some")); + } } * refactor and unify outputs of Store.list * read zip store * Bump to 0.0.6 to trigger release There are apparently cases where release: [created] leads to the deploy not being triggered. Attempting an expansion to [created, published]. diff --git c/pom.xml i/pom.xml index f4c1091..9c9d45d 100644 --- c/pom.xml +++ i/pom.xml @@ -6,7 +6,7 @@ <groupId>dev.zarr</groupId> <artifactId>zarr-java</artifactId> - <version>0.0.9</version> + <version>0.0.6</version> <name>zarr-java</name> @@ -123,6 +123,17 @@ </dependency> </dependencies> + <distributionManagement> + <snapshotRepository> + <id>ossrh</id> + <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url> + </snapshotRepository> + <repository> + <id>ossrh</id> + <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url> + </repository> + </distributionManagement> + <repositories> <repository> <id>unidata-all</id> @@ -221,16 +232,6 @@ </execution> </executions> </plugin> - <plugin> - <groupId>org.sonatype.central</groupId> - <artifactId>central-publishing-maven-plugin</artifactId> - <version>0.8.0</version> - <extensions>true</extensions> - <configuration> - <publishingServerId>central</publishingServerId> - <tokenAuth>true</tokenAuth> - </configuration> - </plugin> </plugins> </build> </project> * Bump to 0.0.7 * Bump to 0.0.8 Use new sonatype plugin for upload * Bump to 0.0.9 * write buffer of zip store * use apache commons compress for zip file read and write * set Zip64Mode.AsNeeded * test Zipped OME-Zarr requirements * Sort zarr.json files in breadth-first order within BufferedZipStore * manually read zip comment * refactor read zip comment * test zip store with v2 * use com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream instead of own implementation * add ReadOnlyZipStore * fix ReadOnlyZipStore for zips with 1. leading slashes in paths 2. no sizes in entry headers * add BufferedZipStore parameter flushOnWrite * fix testMemoryStore * default flushOnWrite to false * fix s3 store get range * add store.getInputStream * add store.getSize * improve performance of ReadOnlyZipStore.getArchiveComment * inherit zipstores from common parent and reduce buffers in memory in loadBuffer * fix s3 test buckets * reduce getArchiveComment store requests * format * fix merge * improve Group::list performance * fix ReadOnlyZipStore::list * less requests to store on read * fix FilesystemStore::list * fix MemoryStore::list * fix ReadOnlyZipStore::list * test Store::list for local stores * less store.exists() for v2.Group.list() * add S3Mock for tests * fallback to chunkHandle.exists() for non listable stores * fix ZarrV2Test::testGroup * downgrade s3mock version to match java-version 11 * downgrade s3mock version to match java-version 11 * run S3 Mock with docker * S3Mock tests only on linux * fix ZarrV3Test::testGroup for windows, mac * refactor and add missing store tests * fix s3Store::list * fix ReadOnlyZipStore::list * add tests * fix tests with path collision * reformat code * improve httpStore testing * change store::list to list no directories add store::listChildren containing direct children including directories * adjust test for store::list * test store::exist, list, listChildren * test store::get with start and end * test store::delete * fix ReadOnlyZipStoreTest.testReadFromBufferedZipStore and S3StoreTest>StoreTest.testExists * fix S3StoreTest.testList * test store.get with start argument * BufferedZipStore auto closable * s3 tests opt-in instead of opt-out * cache key existence and sizes in ReadOnlyZipStore * remove listing existingKeys from array read --------- Co-authored-by: Josh Moore <josh@openmicroscopy.org>
1 parent 664c37f commit f762534

30 files changed

Lines changed: 2241 additions & 226 deletions

.github/workflows/ci.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ jobs:
1414
os: [ ubuntu-latest, windows-latest, macos-latest ]
1515
fail-fast: false
1616
runs-on: ${{ matrix.os }}
17+
services:
18+
s3mock:
19+
# This service will only start if the runner is Linux
20+
image: ${{ matrix.os == 'ubuntu-latest' && 'adobe/s3mock:3.11.0' || '' }}
21+
ports:
22+
- 9090:9090
23+
env:
24+
initialBuckets: zarr-test-bucket
1725
defaults:
1826
run:
1927
shell: bash
@@ -49,8 +57,12 @@ jobs:
4957
- name: Test
5058
env:
5159
MAVEN_OPTS: "-Xmx6g"
52-
run: mvn --no-transfer-progress test -DargLine="-Xmx6g"
53-
60+
run: |
61+
if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
62+
mvn --no-transfer-progress test -DargLine="-Xmx6g" -DrunS3Tests=true
63+
else
64+
mvn --no-transfer-progress test -DargLine="-Xmx6g"
65+
fi
5466
- name: Assemble JAR
5567
run: mvn package -DskipTests
5668

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@ build/
4545
/main.py
4646
/pyproject.toml
4747
/uv.lock
48-
**/__pycache__
48+
**/__pycache__
49+
/dependency-reduced-pom.xml

pom.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@
121121
<version>4.13.1</version>
122122
<scope>test</scope>
123123
</dependency>
124+
<dependency>
125+
<groupId>org.apache.commons</groupId>
126+
<artifactId>commons-compress</artifactId>
127+
<version>1.28.0</version>
128+
</dependency>
124129
</dependencies>
125130

126131
<repositories>
@@ -139,6 +144,10 @@
139144
<version>3.2.5</version>
140145
<configuration>
141146
<useSystemClassLoader>false</useSystemClassLoader>
147+
<environmentVariables>
148+
<!-- Force Testcontainers to use a newer Docker API version supported by your local Daemon -->
149+
<DOCKER_API_VERSION>1.44</DOCKER_API_VERSION>
150+
</environmentVariables>
142151
</configuration>
143152
</plugin>
144153
<plugin>

src/main/java/dev/zarr/zarrjava/core/Array.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dev.zarr.zarrjava.ZarrException;
44
import dev.zarr.zarrjava.core.codec.CodecPipeline;
55
import dev.zarr.zarrjava.store.FilesystemStore;
6+
import dev.zarr.zarrjava.store.Store;
67
import dev.zarr.zarrjava.store.StoreHandle;
78
import dev.zarr.zarrjava.utils.IndexingUtils;
89
import dev.zarr.zarrjava.utils.MultiArrayUtils;
@@ -16,6 +17,9 @@
1617
import java.nio.file.Path;
1718
import java.nio.file.Paths;
1819
import java.util.Arrays;
20+
import java.util.List;
21+
import java.util.Set;
22+
import java.util.stream.Collectors;
1923
import java.util.stream.Stream;
2024

2125
public abstract class Array extends AbstractNode {
@@ -306,9 +310,9 @@ public ucar.ma2.Array read(final long[] offset, final int[] shape, final boolean
306310

307311
final String[] chunkKeys = metadata.chunkKeyEncoding().encodeChunkKey(chunkCoords);
308312
final StoreHandle chunkHandle = storeHandle.resolve(chunkKeys);
309-
if (!chunkHandle.exists()) {
310-
return;
311-
}
313+
314+
if (!chunkHandle.exists()) return;
315+
312316
if (codecPipeline.supportsPartialDecode()) {
313317
final ucar.ma2.Array chunkArray = codecPipeline.decodePartial(chunkHandle,
314318
Utils.toLongArray(chunkProjection.chunkOffset), chunkProjection.shape);

src/main/java/dev/zarr/zarrjava/core/Group.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import java.io.IOException;
1010
import java.nio.file.Path;
1111
import java.nio.file.Paths;
12-
import java.util.Objects;
1312
import java.util.stream.Stream;
1413

1514
public abstract class Group extends AbstractNode {
@@ -64,20 +63,15 @@ public static Group open(String path) throws IOException, ZarrException {
6463
}
6564

6665
@Nullable
67-
public abstract Node get(String key) throws ZarrException, IOException;
66+
public abstract Node get(String[] key) throws ZarrException, IOException;
6867

69-
public Stream<Node> list() {
70-
return storeHandle.list()
71-
.map(key -> {
72-
try {
73-
return get(key);
74-
} catch (Exception e) {
75-
throw new RuntimeException(e);
76-
}
77-
})
78-
.filter(Objects::nonNull);
68+
@Nullable
69+
public Node get(String key) throws ZarrException, IOException {
70+
return get(new String[]{key});
7971
}
8072

73+
public abstract Stream<Node> list();
74+
8175
public Node[] listAsArray() {
8276
try (Stream<Node> nodeStream = list()) {
8377
return nodeStream.toArray(Node[]::new);

0 commit comments

Comments
 (0)