app/logic/SnapshotApi.scala (61 lines of code) (raw):

package logic import helpers.Loggable import models.{Attempt, AttemptError, Snapshot, SnapshotId} import play.api.libs.json.{JsValue, Json} import software.amazon.awssdk.core.ResponseInputStream import software.amazon.awssdk.services.s3.S3Client import software.amazon.awssdk.services.s3.model._ import scala.jdk.CollectionConverters._ import scala.concurrent.ExecutionContext.Implicits.global import scala.io.Source import scala.util.control.NonFatal class SnapshotApi(s3Client: S3Client) extends Loggable { def listForId(bucket: String, id: String): List[SnapshotId] = listSnapshots(bucket, id) def getRawSnapshot(bucket: String, snapshotId: SnapshotId) = getObjectContentRaw(snapshotId.key, bucket) def getSnapshot(bucket: String, snapshotId: SnapshotId): Attempt[Option[Snapshot]] = { getObjectContentJson(snapshotId.key, bucket).map { _.map(Snapshot(snapshotId, _)) } } def getRawSnapshotInfo(bucket: String, snapshotId: SnapshotId) = getObjectContentRaw(snapshotId.infoKey, bucket) def getSnapshotInfo(bucket: String, snapshotId: SnapshotId): Attempt[Option[JsValue]] = getObjectContentJson(snapshotId.infoKey, bucket) private def getObject(key: String, bucketName: String): Attempt[Option[ResponseInputStream[GetObjectResponse]]] = { try { Attempt.Right(Some(s3Client.getObject(GetObjectRequest.builder().bucket(bucketName).key(key).build()))) } catch { case _:NoSuchKeyException => Attempt.Right(None) case NonFatal(e) => logger.warn(s"Unexpected error whilst getting object $bucketName:$key", e) Attempt.Left(AttemptError(s"Couldn't retrieve object: ${e.getMessage}")) } } // Get object contents and ensure stream is closed private def getObjectContentRaw(key: String, bucketName: String): Attempt[Option[String]] = { getObject(key, bucketName).map { _.map { obj => try { Source.fromInputStream(obj, "UTF-8").mkString } finally { obj.close() } } } } private def getObjectContentJson(key: String, bucketName: String): Attempt[Option[JsValue]] = { getObject(key, bucketName).map { _.map { obj => try { Json.parse(obj) } finally { obj.close() } } } } private def listSnapshots(bucket: String, id: String): List[SnapshotId] = { logger.info(s"Looking for snapshots of $id in $bucket") val request = ListObjectsRequest.builder().bucket(bucket).prefix(id).build() val listing: ListObjectsResponse = s3Client.listObjects(request) val objectKeys = listing.contents().asScala.map(_.key()).toList logger.info(s"Found ${objectKeys.size} versions") objectKeys.flatMap { k => SnapshotId.fromKey(k) }.distinct } }