feat(storage): add FilesystemArtifactStore (one impl of ArtifactStore)

This commit is contained in:
hsiegeln
2026-04-27 14:48:42 +02:00
parent 9c115f892e
commit bc8bd590a6
2 changed files with 129 additions and 0 deletions

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}