feat(storage): add FilesystemArtifactStore (one impl of ArtifactStore)
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
package com.cameleer.server.app.storage;
|
||||||
|
|
||||||
|
import com.cameleer.server.core.storage.ArtifactCoordinates;
|
||||||
|
import com.cameleer.server.core.storage.ArtifactStore;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class FilesystemArtifactStore implements ArtifactStore {
|
||||||
|
|
||||||
|
private final Path root;
|
||||||
|
|
||||||
|
public FilesystemArtifactStore(String root) {
|
||||||
|
this.root = Path.of(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path pathOf(ArtifactCoordinates coords) {
|
||||||
|
return root.resolve(coords.filesystemKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String put(ArtifactCoordinates coords, InputStream bytes, long size) throws IOException {
|
||||||
|
Path target = pathOf(coords);
|
||||||
|
Files.createDirectories(target.getParent());
|
||||||
|
try (InputStream in = bytes) {
|
||||||
|
Files.copy(in, target, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
return target.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream get(ArtifactCoordinates coords) throws IOException {
|
||||||
|
return Files.newInputStream(pathOf(coords));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists(ArtifactCoordinates coords) {
|
||||||
|
return Files.exists(pathOf(coords));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(ArtifactCoordinates coords) throws IOException {
|
||||||
|
Path target = pathOf(coords);
|
||||||
|
Files.deleteIfExists(target);
|
||||||
|
// Sweep empty {appId}/v{n}/ then {appId}/ if now empty.
|
||||||
|
Path versionDir = target.getParent();
|
||||||
|
if (versionDir != null && Files.isDirectory(versionDir) && isEmpty(versionDir)) {
|
||||||
|
Files.delete(versionDir);
|
||||||
|
Path appDir = versionDir.getParent();
|
||||||
|
if (appDir != null && Files.isDirectory(appDir) && isEmpty(appDir)) {
|
||||||
|
Files.delete(appDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String locator(ArtifactCoordinates coords) {
|
||||||
|
return pathOf(coords).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEmpty(Path dir) throws IOException {
|
||||||
|
try (var entries = Files.list(dir)) {
|
||||||
|
return entries.findFirst().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.cameleer.server.app.storage;
|
||||||
|
|
||||||
|
import com.cameleer.server.core.storage.ArtifactCoordinates;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class FilesystemArtifactStoreTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void roundTripsBytes(@TempDir Path tmp) throws Exception {
|
||||||
|
FilesystemArtifactStore store = new FilesystemArtifactStore(tmp.toString());
|
||||||
|
ArtifactCoordinates c = new ArtifactCoordinates("default", UUID.randomUUID(), 1);
|
||||||
|
|
||||||
|
byte[] payload = "hello-jar".getBytes();
|
||||||
|
store.put(c, new ByteArrayInputStream(payload), payload.length);
|
||||||
|
|
||||||
|
assertThat(store.exists(c)).isTrue();
|
||||||
|
try (InputStream in = store.get(c)) {
|
||||||
|
assertThat(in.readAllBytes()).isEqualTo(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteRemovesFileAndEmptyParents(@TempDir Path tmp) throws Exception {
|
||||||
|
FilesystemArtifactStore store = new FilesystemArtifactStore(tmp.toString());
|
||||||
|
UUID appId = UUID.randomUUID();
|
||||||
|
ArtifactCoordinates c = new ArtifactCoordinates("default", appId, 1);
|
||||||
|
store.put(c, new ByteArrayInputStream("x".getBytes()), 1);
|
||||||
|
|
||||||
|
store.delete(c);
|
||||||
|
|
||||||
|
assertThat(store.exists(c)).isFalse();
|
||||||
|
// {tmp}/{appId}/v1/ should be gone too
|
||||||
|
assertThat(Files.exists(tmp.resolve(appId.toString()).resolve("v1"))).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteIsIdempotent(@TempDir Path tmp) throws Exception {
|
||||||
|
FilesystemArtifactStore store = new FilesystemArtifactStore(tmp.toString());
|
||||||
|
ArtifactCoordinates c = new ArtifactCoordinates("default", UUID.randomUUID(), 1);
|
||||||
|
store.delete(c); // no exception
|
||||||
|
assertThat(store.exists(c)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void locatorMatchesLegacyAbsolutePath(@TempDir Path tmp) {
|
||||||
|
FilesystemArtifactStore store = new FilesystemArtifactStore(tmp.toString());
|
||||||
|
UUID appId = UUID.fromString("11111111-1111-1111-1111-111111111111");
|
||||||
|
ArtifactCoordinates c = new ArtifactCoordinates("default", appId, 7);
|
||||||
|
assertThat(store.locator(c))
|
||||||
|
.isEqualTo(tmp.resolve("11111111-1111-1111-1111-111111111111/v7/app.jar").toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user