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 part of #5411: Introduce asset script GAE GCS endpoint [Blocked: #5412] #5413

Closed
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
23 changes: 23 additions & 0 deletions scripts/src/java/org/oppia/android/scripts/gae/gcs/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
Library for providing the endpoint functionality to inspect and download assets from Google Cloud
Storage.
"""

load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

kt_jvm_library(
name = "api",
testonly = True,
srcs = [
"GcsEndpointApi.kt",
"GcsService.kt",
],
visibility = [
"//scripts/src/java/org/oppia/android/scripts/gae:__subpackages__",
],
deps = [
"//third_party:com_squareup_retrofit2_converter-moshi",
"//third_party:com_squareup_retrofit2_retrofit",
"//third_party:org_jetbrains_kotlinx_kotlinx-coroutines-core",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.oppia.android.scripts.gae.gcs

import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Path
import retrofit2.http.Streaming

interface GcsEndpointApi {
@GET("{gcs_bucket}/{entity_type}/{entity_id}/assets/{image_type}/{image_filename}")
@Headers("Content-Type:application/octet-stream")
@Streaming
fun fetchImageData(
@Path("gcs_bucket") gcsBucket: String,
@Path("entity_type") entityType: String,
@Path("entity_id") entityId: String,
@Path("image_type") imageType: String,
@Path("image_filename") imageFilename: String
): Call<ResponseBody>
}
105 changes: 105 additions & 0 deletions scripts/src/java/org/oppia/android/scripts/gae/gcs/GcsService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.oppia.android.scripts.gae.gcs

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import okhttp3.Request
import retrofit2.Call
import retrofit2.Response
import retrofit2.Retrofit

class GcsService(private val baseUrl: String, private val gcsBucket: String) {
private val retrofit by lazy { Retrofit.Builder().baseUrl(baseUrl).build() }
private val apiService by lazy { retrofit.create(GcsEndpointApi::class.java) }

fun fetchImageContentLengthAsync(
imageContainerType: ImageContainerType,
imageType: ImageType,
entityId: String,
imageFilename: String
): Deferred<Long?> {
return apiService.fetchImageData(
gcsBucket,
imageContainerType.httpRepresentation,
entityId,
imageType.httpRepresentation,
imageFilename
).resolveAsync(
transform = { request, response ->
checkNotNull(response.body()) {
"Failed to receive body for request: $request."
}.use { it.contentLength() }
},
default = { _, _, -> null }
// default = { request, response ->
// error("Failed to call: $request. Encountered failure:\n$response")
// }
)
}

fun fetchImageContentDataAsync(
imageContainerType: ImageContainerType,
imageType: ImageType,
entityId: String,
imageFilename: String
): Deferred<ByteArray?> {
return apiService.fetchImageData(
gcsBucket,
imageContainerType.httpRepresentation,
entityId,
imageType.httpRepresentation,
imageFilename
).resolveAsync(
transform = { request, response ->
checkNotNull(response.body()) { "Failed to receive body for request: $request." }.use {
it.byteStream().readBytes()
}
},
default = { _, _ -> null }
// default = { request, response ->
// error("Failed to call: $request. Encountered failure:\n$response")
// }
)
}

fun computeImageUrl(
imageContainerType: ImageContainerType,
imageType: ImageType,
entityId: String,
imageFilename: String
): String {
val containerTypeHttpRep = imageContainerType.httpRepresentation
val imgTypeHttpRep = imageType.httpRepresentation
return "${baseUrl.removeSuffix("/")}/$gcsBucket/$containerTypeHttpRep/$entityId/assets" +
"/$imgTypeHttpRep/$imageFilename"
}

enum class ImageContainerType(val httpRepresentation: String) {
EXPLORATION(httpRepresentation = "exploration"),
SKILL(httpRepresentation = "skill"),
TOPIC(httpRepresentation = "topic"),
STORY(httpRepresentation = "story")
}

enum class ImageType(val httpRepresentation: String) {
HTML_IMAGE(httpRepresentation = "image"),
THUMBNAIL(httpRepresentation = "thumbnail")
}

private companion object {
private fun <I, O> Call<I>.resolveAsync(
transform: (Request, Response<I>) -> O,
default: (Request, Response<I>) -> O
): Deferred<O> {
// Use the I/O dispatcher for blocking HTTP operations (since it's designed to handle blocking
// operations that might otherwise stall a coroutine dispatcher).
return CoroutineScope(Dispatchers.IO).async {
val result = execute()
return@async if (result.isSuccessful) {
transform(request(), result)
} else default(request(), result)
}
}
}
}
Loading