backend/app/services/ObjectStorage.scala (81 lines of code) (raw):
package services
import com.amazonaws.HttpMethod
import com.amazonaws.services.s3.model.{DeleteObjectsRequest, ListObjectsRequest, ObjectListing, S3ObjectSummary}
import java.io.InputStream
import java.nio.file.Path
import model.ObjectMetadata
import org.joda.time.DateTime
import utils.attempt.{Failure, IllegalStateFailure, UnknownFailure}
import utils.aws.{AwsErrors, S3Client}
import java.util.Date
import scala.jdk.CollectionConverters._
import scala.util.control.NonFatal
trait ObjectStorage {
def create(key: String, path: Path, mimeType: Option[String] = None): Either[Failure, Unit]
def get(key: String): Either[Failure, InputStream]
def getSignedUrl(key: String): Either[Failure, String]
def getUploadSignedUrl(key: String): Either[Failure, String]
def getMetadata(key: String): Either[Failure, ObjectMetadata]
def delete(key: String): Either[Failure, Unit]
def deleteMultiple(key: Set[String]): Either[Failure, Unit]
def list(prefix: String): Either[Failure, List[String]]
}
class S3ObjectStorage private(client: S3Client, bucket: String) extends ObjectStorage {
def create(key: String, path: Path, mimeType: Option[String] = None): Either[Failure, Unit] = run {
client.putLargeObject(bucket, key, contentType = mimeType, path)
()
}
def get(key: String): Either[Failure, InputStream] = {
run(client.aws.getObject(bucket, key).getObjectContent)
}
def getSignedUrl(key: String): Either[Failure, String] = {
val thisTimeTomorrow = new DateTime().plusDays(1)
run(client.aws.generatePresignedUrl(bucket, key,thisTimeTomorrow.toDate).toString)
}
def getUploadSignedUrl(key: String): Either[Failure, String] = {
val thisTimeTomorrow = new DateTime().plusDays(1)
run(client.aws.generatePresignedUrl(bucket, key, thisTimeTomorrow.toDate, HttpMethod.PUT).toString)
}
def getMetadata(key: String): Either[Failure, ObjectMetadata] = run {
val stats = client.aws.getObjectMetadata(bucket, key)
ObjectMetadata(stats.getContentLength, stats.getContentType)
}
def delete(key: String): Either[Failure, Unit] = {
run(client.aws.deleteObject(bucket, key))
}
def deleteMultiple(keys: Set[String]): Either[Failure, Unit] = {
val request = new DeleteObjectsRequest(bucket).withKeys(keys.toSeq: _*)
run(client.aws.deleteObjects(request))
}
def list(prefix: String): Either[Failure, List[String]] = {
val request = new ListObjectsRequest()
.withBucketName(bucket)
.withPrefix(prefix)
run {
client.aws.listObjects(request)
.getObjectSummaries
.asScala
.toList
.map(_.getKey)
}
}
private def run[T](fn: => T): Either[Failure, T] = try {
Right(fn)
} catch {
case NonFatal(err) =>
Left(AwsErrors.exceptionToFailure.lift(err).getOrElse(UnknownFailure(err)))
}
}
object S3ObjectStorage {
def apply(client: S3Client, bucket: String): Either[Failure, S3ObjectStorage] = {
try {
if (!client.doesBucketExist(bucket)) {
Left(IllegalStateFailure(s"Bucket $bucket does not exist"))
} else {
Right(new S3ObjectStorage(client, bucket))
}
} catch {
case ex: Exception => Left(UnknownFailure(ex))
}
}
}