-
Notifications
You must be signed in to change notification settings - Fork 74
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
Get file size from S3 after upload + fix upload #4952
Changes from 1 commit
4aa7b18
ccd837a
e5d889e
cb1b27b
a48b0fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package ch.epfl.bluebrain.nexus.delta.kernel | ||
|
||
object Hex { | ||
|
||
/** | ||
* Convert the array of bytes to a string of the hexadecimal values | ||
*/ | ||
def valueOf(value: Array[Byte]): String = value.map("%02x".format(_)).mkString | ||
|
||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ import io.circe.{Encoder, JsonObject} | |
* A digest value | ||
*/ | ||
sealed trait Digest extends Product with Serializable { | ||
def computed: Boolean | ||
def computed: Boolean = this != Digest.NotComputedDigest | ||
} | ||
|
||
object Digest { | ||
|
@@ -21,19 +21,23 @@ object Digest { | |
* @param value | ||
* the actual value of the digest of the file | ||
*/ | ||
final case class ComputedDigest(algorithm: DigestAlgorithm, value: String) extends Digest { | ||
override val computed: Boolean = true | ||
} | ||
final case class ComputedDigest(algorithm: DigestAlgorithm, value: String) extends Digest | ||
|
||
/** | ||
* A digest that does not yield a value because it is still being computed | ||
*/ | ||
final case object NotComputedDigest extends Digest { | ||
override val computed: Boolean = false | ||
} | ||
final case object NoDigest extends Digest | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is slightly different from the other one (NotComputedDigest) where we were expecting it to be computed in the future |
||
|
||
val none: Digest = NoDigest | ||
|
||
/** | ||
* A digest that does not yield a value because it is still being computed | ||
*/ | ||
final case object NotComputedDigest extends Digest | ||
|
||
implicit val digestEncoder: Encoder.AsObject[Digest] = Encoder.encodeJsonObject.contramapObject { | ||
case ComputedDigest(algorithm, value) => JsonObject("_algorithm" -> algorithm.asJson, "_value" -> value.asJson) | ||
case NotComputedDigest => JsonObject("_value" -> "".asJson) | ||
case NoDigest => JsonObject("_value" -> "".asJson) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,6 @@ | ||
package ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations | ||
|
||
import akka.http.scaladsl.model.{StatusCodes, Uri} | ||
import cats.data.NonEmptyList | ||
import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection | ||
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageType | ||
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri | ||
|
@@ -138,6 +137,14 @@ object StorageFileRejection { | |
s"File cannot be saved because it already exists on path '$path'." | ||
) | ||
|
||
/** | ||
* Rejection returned when a file can not be saved because content-length is not provided | ||
*/ | ||
final case object ContentLengthIsMissing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When doing a put with a stream and asking for a checksum, it seems that the content length is needed |
||
extends SaveFileRejection( | ||
s"Content length must be supplied." | ||
) | ||
|
||
/** | ||
* Rejection returned when a storage cannot save a file due to an unexpected reason | ||
*/ | ||
|
@@ -196,11 +203,6 @@ object StorageFileRejection { | |
sealed abstract class RegisterFileRejection(loggedDetails: String) extends StorageFileRejection(loggedDetails) | ||
|
||
object RegisterFileRejection { | ||
final case class MissingS3Attributes(missingAttributes: NonEmptyList[String]) | ||
extends RegisterFileRejection(s"Missing attributes from S3: ${missingAttributes.toList.mkString(", ")}") | ||
|
||
final case class InvalidContentType(received: String) | ||
extends RegisterFileRejection(s"Invalid content type returned from S3: $received") | ||
|
||
final case class InvalidPath(path: Uri.Path) | ||
extends RegisterFileRejection(s"An S3 path must contain at least the filename. Path was $path") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.s3 | ||
|
||
import akka.http.scaladsl.model.ContentType | ||
import ch.epfl.bluebrain.nexus.delta.kernel.Hex | ||
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.Digest | ||
import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.Digest.ComputedDigest | ||
import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.DigestAlgorithm | ||
import software.amazon.awssdk.services.s3.model.HeadObjectResponse | ||
|
||
import java.util.Base64 | ||
|
||
case class HeadObject(fileSize: Long, contentType: Option[ContentType], digest: Digest) | ||
|
||
object HeadObject { | ||
def apply(response: HeadObjectResponse): HeadObject = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Smart constructor to add more value to the class |
||
val contentType = Option(response.contentType()).flatMap { value => | ||
// It is highly likely for S3 to return an erroneous value here | ||
ContentType.parse(value).toOption | ||
} | ||
val digestValue = Option(response.checksumSHA256).map { encodedChecksum => | ||
Hex.valueOf(Base64.getDecoder.decode(encodedChecksum)) | ||
} | ||
val digest = digestValue.fold(Digest.none) { value => | ||
ComputedDigest(DigestAlgorithm.SHA256, value) | ||
} | ||
HeadObject( | ||
response.contentLength(), | ||
contentType, | ||
digest | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Common shared utility method