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