Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: resolve file content type based on file extension #128

Merged
merged 2 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/main/java/com/epam/aidial/core/storage/BlobStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.ContentMetadataBuilder;
import org.jclouds.io.payloads.BaseMutableContentMetadata;
import org.jclouds.s3.domain.ObjectMetadataBuilder;

import java.io.Closeable;
import java.util.List;
Expand All @@ -32,6 +33,11 @@
@Slf4j
public class BlobStorage implements Closeable {

// S3 implementation do not return a blob content type without additional head request.
// To avoid additional request for each blob in the listing we try to recognize blob content type by its extension.
// Default value is binary/octet-stream, see org.jclouds.s3.domain.ObjectMetadataBuilder
private static final String DEFAULT_CONTENT_TYPE = ObjectMetadataBuilder.create().build().getContentMetadata().getContentType();

private final BlobStoreContext storeContext;
private final BlobStore blobStore;
private final String bucketName;
Expand Down Expand Up @@ -171,8 +177,14 @@ private static FileMetadataBase buildFileMetadata(ResourceDescription resource,
resource.getBucketLocation(), metadata.getName());

return switch (metadata.getType()) {
case BLOB -> new FileMetadata(resultResource, metadata.getSize(),
((BlobMetadata) metadata).getContentMetadata().getContentType());
case BLOB -> {
String blobContentType = ((BlobMetadata) metadata).getContentMetadata().getContentType();
if (blobContentType != null && blobContentType.equals(DEFAULT_CONTENT_TYPE)) {
blobContentType = BlobStorageUtil.getContentType(metadata.getName());
}

yield new FileMetadata(resultResource, metadata.getSize(), blobContentType);
}
case FOLDER, RELATIVE_PATH -> new FolderMetadata(resultResource);
case CONTAINER -> throw new IllegalArgumentException("Can't list container");
};
Expand Down
66 changes: 66 additions & 0 deletions src/test/java/com/epam/aidial/core/FileApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,72 @@ public void testListFileWithFolder(Vertx vertx, VertxTestContext context) {
});
}

@Test
public void testListFileWithDefaultContentType(Vertx vertx, VertxTestContext context) {
Checkpoint checkpoint = context.checkpoint(3);
WebClient client = WebClient.create(vertx);

FolderMetadata emptyFolderResponse = new FolderMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
null, null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/", List.of());

FileMetadata expectedFileMetadata1 = new FileMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
"image.png", null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/image.png", 17, "binary/octet-stream");

FileMetadata expectedImageMetadata = new FileMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
"image.png", null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/image.png", 17, "image/png");
FolderMetadata expectedRootFolderMetadata = new FolderMetadata("7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt",
null, null, "7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/",
List.of(expectedImageMetadata));

Future.succeededFuture().compose((mapper) -> {
Promise<Void> promise = Promise.promise();
// verify no files
client.get(serverPort, "localhost", "/v1/files/metadata/7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/")
.putHeader("Api-key", "proxyKey2")
.as(BodyCodec.json(FolderMetadata.class))
.send(context.succeeding(response -> {
context.verify(() -> {
assertEquals(200, response.statusCode());
assertEquals(emptyFolderResponse, response.body());
checkpoint.flag();
promise.complete();
});
}));

return promise.future();
}).compose((mapper) -> {
Promise<Void> promise = Promise.promise();
// upload test file1
client.put(serverPort, "localhost", "/v1/files/7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/image.png")
.putHeader("Api-key", "proxyKey2")
.as(BodyCodec.json(FileMetadata.class))
.sendMultipartForm(generateMultipartForm("filename", TEST_FILE_CONTENT, "binary/octet-stream"),
context.succeeding(response -> {
context.verify(() -> {
assertEquals(200, response.statusCode());
assertEquals(expectedFileMetadata1, response.body());
checkpoint.flag();
promise.complete();
});
})
);

return promise.future();
}).andThen((result) -> {
// verify uploaded files can be listed
client.get(serverPort, "localhost", "/v1/files/metadata/7G9WZNcoY26Vy9D7bEgbv6zqbJGfyDp9KZyEbJR4XMZt/")
.putHeader("Api-key", "proxyKey2")
.as(BodyCodec.string())
.send(context.succeeding(response -> {
context.verify(() -> {
assertEquals(200, response.statusCode());
assertEquals(ProxyUtil.MAPPER.writeValueAsString(expectedRootFolderMetadata), response.body());
checkpoint.flag();
});
}));
});
}

@Test
public void testFileDelete(Vertx vertx, VertxTestContext context) {
Checkpoint checkpoint = context.checkpoint(3);
Expand Down