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 #5411: Introduce asset download script [Blocked: #5416] #4885

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
93eadd6
First commit of new asset download script.
BenHenning Feb 28, 2023
4000d11
Merge branch 'develop' into introduce-asset-download-script
BenHenning Feb 28, 2023
734b13a
Support latest version of new Android controller.
BenHenning Mar 14, 2023
7b2e1b8
Fixes a bunch of stuff in new download script.
BenHenning May 31, 2023
e8f60f0
Prepare for 0.11 release.
BenHenning Jun 1, 2023
de0aaf0
Add support for decoding base64 event log strings.
BenHenning Jun 1, 2023
78dad17
Localisation updates from https://translatewiki.net.
translatewiki Jun 1, 2023
524607b
Merge branch 'translatewiki-prs' into finalize-release-0.11
BenHenning Jun 1, 2023
5d2712a
Pull latest pcm translations.
BenHenning Jun 1, 2023
509d05a
Add support for Nigerian Pidgin (Haija).
BenHenning Jun 1, 2023
702b711
Reduce & correct output when building AABs.
BenHenning Jun 1, 2023
898a94c
Merge branch 'develop' into finalize-release-0.11
BenHenning Jun 1, 2023
a088fca
Bunch of fixes.
BenHenning Jun 2, 2023
e98d83a
Enable spotlights & some other items.
BenHenning Jun 2, 2023
c5aeb48
Fixed a few things.
BenHenning Jun 2, 2023
dc4317c
Fix tests.
BenHenning Jun 3, 2023
5feac86
Follow up CI fixes and cleanup.
BenHenning Jun 3, 2023
a89b183
Merge branch 'finalize-release-0.11' into introduce-asset-download-sc…
BenHenning Jun 3, 2023
9093d2c
Add support for Nigerian Pidgin.
BenHenning Jun 3, 2023
d739210
Fix broken Gradle tests.
BenHenning Jun 3, 2023
eb2b935
Lots of big changes to the download script.
BenHenning Jun 5, 2023
ee8cd21
Fix several things.
BenHenning Jun 5, 2023
cbae5d8
Bunch of small changes.
BenHenning Jun 5, 2023
0de2a15
Merge remote-tracking branch 'origin/finalize-release-0.11' into fina…
BenHenning Jun 5, 2023
7c2b859
Merge branch 'finalize-release-0.11' into introduce-asset-download-sc…
BenHenning Jun 5, 2023
2a8c1f4
Merge branch 'develop' into finalize-release-0.11
BenHenning Jun 5, 2023
e9fc289
Merge branch 'finalize-release-0.11' into introduce-asset-download-sc…
BenHenning Jun 5, 2023
bd5fb45
Fix broken tests + other cleanups.
BenHenning Jun 5, 2023
3bb1e9c
Merge branch 'finalize-release-0.11' into introduce-asset-download-sc…
BenHenning Jun 5, 2023
83cf204
Fix broken CI tests.
BenHenning Jun 5, 2023
07b2ebf
Merge branch 'develop' into finalize-release-0.11
BenHenning Jun 5, 2023
a19e624
Address reviewer comments.
BenHenning Jun 5, 2023
18b9cd3
Lighten up spotlights background more.
BenHenning Jun 5, 2023
0017533
Merge branch 'develop' into finalize-release-0.11
BenHenning Jun 6, 2023
b04fd63
Merge branch 'finalize-release-0.11' into introduce-asset-download-sc…
BenHenning Jun 6, 2023
e21b268
Merge branch 'upgrade-to-kotlin1.6' into introduce-asset-download-script
BenHenning Jun 6, 2023
66f6e2a
Fix download script.
BenHenning Jun 6, 2023
d3e2fd2
Fix pipeline issues & add image conversion.
BenHenning Jun 9, 2023
3b330b8
Post-merge fixes.
BenHenning Jun 15, 2023
1dd7123
Fixes two pipeline issues.
BenHenning Jun 15, 2023
402a48d
Fix remote API definitions.
BenHenning Dec 23, 2023
31fded9
Merge commit '8c81c98d8bdeaca167190b99df793efafa7cf9fd' into finalize…
BenHenning Mar 4, 2024
7ec608b
Merge branch 'finalize-release-0.11' into introduce-asset-download-sc…
BenHenning Mar 4, 2024
dc43ac8
Merge branch 'upgrade-to-kotlin1.6' into introduce-asset-download-script
BenHenning Mar 5, 2024
a029ca6
Fix a few things.
BenHenning Mar 14, 2024
d80cb1e
Add additional invalid tags.
seanlip Mar 15, 2024
2b080fb
Revert Gif auto-conversion.
BenHenning Mar 19, 2024
198d6ce
Update BUILD.bazel
BenHenning Apr 23, 2024
7b58642
Update script Kotlin compatibility.
BenHenning Apr 26, 2024
f00b7c4
Create output/cache dirs if needed.
BenHenning Apr 26, 2024
b325afc
Enable Moshi reflection as a stopgap.
BenHenning Apr 26, 2024
31d5f16
Enable reflection in another adapter.
BenHenning Apr 26, 2024
ffcbf5d
Add note for further script improvements.
BenHenning Apr 28, 2024
a2e8019
Split download script.
BenHenning May 3, 2024
43512f6
Merge branch 'migrate-to-newer-bazel-and-kotlin' into introduce-asset…
BenHenning May 27, 2024
b2cf155
Merge branch 'introduce-asset-script-gae-proto-endpoint-impl' into in…
BenHenning May 27, 2024
c081e57
Post-merge fixes.
BenHenning May 27, 2024
e4e9002
Lint fixes.
BenHenning May 27, 2024
23b36d9
Add support for classrooms.
BenHenning Jun 21, 2024
6b1d81c
Merge branch 'introduce-asset-script-gae-proto-endpoint-impl' into in…
BenHenning Jun 21, 2024
6e87842
Merge branch 'integrate-multiple-classrooms-support' into introduce-a…
BenHenning Jun 21, 2024
41a048e
Post-merge fixes.
BenHenning Jun 21, 2024
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
37 changes: 34 additions & 3 deletions scripts/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,40 @@ kt_jvm_binary(
],
)

# Note that this is intentionally not test-only since it's used by the app build pipeline. Also,
# this apparently needs to be a java_binary to set up runfiles correctly when executed within a
# Starlark rule as a tool.
java_binary(
name = "download_lesson_list",
testonly = True,
# Hide warnings that come from https://github.com/square/retrofit/issues/3341.
jvm_flags = [
"--add-opens",
"java.base/java.lang.invoke=ALL-UNNAMED",
],
main_class = "org.oppia.android.scripts.assets.DownloadLessonListKt",
visibility = ["//visibility:public"], # TODO: Revert this when the script no longer needs to be referenced on develop.
runtime_deps = [
"//scripts/src/java/org/oppia/android/scripts/assets:download_lesson_list_lib",
],
)

java_binary(
name = "download_lessons",
testonly = True,
# Hide warnings that come from https://github.com/square/retrofit/issues/3341.
jvm_flags = [
"--add-opens",
"java.base/java.lang.invoke=ALL-UNNAMED",
"-Xmx16g", # Image conversion can require a lot of RAM.
],
main_class = "org.oppia.android.scripts.assets.DownloadLessonsKt",
visibility = ["//visibility:public"], # TODO: Revert this when the script no longer needs to be referenced on develop.
runtime_deps = [
"//scripts/src/java/org/oppia/android/scripts/assets:download_lessons_lib",
],
)

# Note that this & the other binaries below are intentionally not test-only since they're used by
# the app build pipeline. Also, this apparently needs to be a java_binary to set up runfiles
# correctly when executed within a Starlark rule as a tool.
java_binary(
name = "transform_android_manifest",
main_class = "org.oppia.android.scripts.build.TransformAndroidManifestKt",
Expand Down
55 changes: 55 additions & 0 deletions scripts/src/java/org/oppia/android/scripts/assets/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Libraries corresponding to asset transformation & download scripts.
"""

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

kt_jvm_library(
name = "download_lesson_list_lib",
testonly = True,
srcs = ["DownloadLessonList.kt"],
visibility = ["//scripts:oppia_script_binary_visibility"],
deps = [
"//scripts/src/java/org/oppia/android/scripts/common:script_background_coroutine_dispatcher",
"//scripts/src/java/org/oppia/android/scripts/gae",
"//scripts/src/java/org/oppia/android/scripts/gae:gae_json_impl",
"//scripts/src/java/org/oppia/android/scripts/gae/proto:proto_version_provider",
"//scripts/src/java/org/oppia/android/scripts/proto:download_list_versions_java_proto",
],
)

kt_jvm_library(
name = "download_lessons_lib",
testonly = True,
srcs = ["DownloadLessons.kt"],
visibility = ["//scripts:oppia_script_binary_visibility"],
deps = [
":dto_proto_to_legacy_proto_converter",
":image_repairer",
"//scripts/src/java/org/oppia/android/scripts/gae",
"//scripts/src/java/org/oppia/android/scripts/gae:gae_json_impl",
"//scripts/src/java/org/oppia/android/scripts/gae/proto:proto_version_provider",
],
)

kt_jvm_library(
name = "dto_proto_to_legacy_proto_converter",
testonly = True,
srcs = ["DtoProtoToLegacyProtoConverter.kt"],
deps = [
"//model/src/main/proto:exploration_java_proto",
"//model/src/main/proto:interaction_object_java_proto",
"//model/src/main/proto:languages_java_proto",
"//model/src/main/proto:math_java_proto",
"//model/src/main/proto:topic_java_proto",
"//model/src/main/proto:translation_java_proto",
"//third_party:oppia_proto_api_java_protos",
],
)

kt_jvm_library(
name = "image_repairer",
testonly = True,
srcs = ["ImageRepairer.kt"],
deps = ["//third_party:com_github_weisj_jsvg"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package org.oppia.android.scripts.assets

import com.google.protobuf.TextFormat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher
import org.oppia.android.scripts.gae.GaeAndroidEndpoint
import org.oppia.android.scripts.gae.GaeAndroidEndpointJsonImpl
import org.oppia.android.scripts.gae.gcs.GcsService
import org.oppia.android.scripts.gae.proto.ImageDownloader
import org.oppia.android.scripts.gae.proto.ProtoVersionProvider
import org.oppia.android.scripts.proto.DownloadListVersions
import org.oppia.android.scripts.proto.DownloadListVersions.ChapterInfo
import org.oppia.android.scripts.proto.DownloadListVersions.SkillInfo
import org.oppia.android.scripts.proto.DownloadListVersions.StoryInfo
import org.oppia.android.scripts.proto.DownloadListVersions.SubtopicInfo
import org.oppia.android.scripts.proto.DownloadListVersions.TopicInfo
import org.oppia.proto.v1.api.AndroidClientContextDto
import org.oppia.proto.v1.api.TopicListRequestDto
import org.oppia.proto.v1.api.TopicListResponseDto
import org.oppia.proto.v1.api.TopicListResponseDto.AvailableTopicDto.AvailabilityTypeCase.DOWNLOADABLE_TOPIC
import org.oppia.proto.v1.structure.ChapterSummaryDto
import org.oppia.proto.v1.structure.DownloadableTopicSummaryDto
import org.oppia.proto.v1.structure.LanguageType
import org.oppia.proto.v1.structure.StorySummaryDto
import org.oppia.proto.v1.structure.SubtopicSummaryDto
import java.io.File

// TODO: hook up to language configs for prod/dev language restrictions.
// TODO: Consider using better argument parser so that dev env vals can be defaulted.
// TODO: verify that images aren't changed after upload, but this needs to be confirmed (that is, if they need to be changed a new image is added to GCS, instead).
fun main(vararg args: String) {
check(args.size == 6) {
"Expected use: bazel run //scripts:download_lesson_list <base_url> <gcs_base_url>" +
" <gcs_bucket> </path/to/api/secret.file> </path/to/output_list.textproto>" +
" </path/to/api/debug/dir>"
}

val baseUrl = args[0]
val gcsBaseUrl = args[1]
val gcsBucket = args[2]
val apiSecretPath = args[3]
val outputFilePath = args[4]
val apiDebugPath = args[5]
val apiSecretFile = File(apiSecretPath).absoluteFile.normalize().also {
check(it.exists() && it.isFile) { "Expected API secret file to exist: $apiSecretPath." }
}
val outputFile = File(outputFilePath).absoluteFile.normalize()
val apiDebugDir = File(apiDebugPath).absoluteFile.normalize().also {
check(if (!it.exists()) it.mkdirs() else it.isDirectory) {
"Expected API debug directory to exist or to be creatable: $apiDebugPath."
}
}

val apiSecret = apiSecretFile.readText().trim()

ScriptBackgroundCoroutineDispatcher().use { scriptBgDispatcher ->
val downloader = LessonListDownloader(
baseUrl, gcsBaseUrl, gcsBucket, apiSecret, apiDebugDir, scriptBgDispatcher
)
runBlocking { downloader.downloadLessonListAsync(outputFile).await() }
}
}

class LessonListDownloader(
gaeBaseUrl: String,
gcsBaseUrl: String,
gcsBucket: String,
apiSecret: String,
private val apiDebugDir: File,
private val scriptBgDispatcher: ScriptBackgroundCoroutineDispatcher
) {
private val gcsService by lazy { GcsService(gcsBaseUrl, gcsBucket) }
private val imageDownloader by lazy { ImageDownloader(gcsService, scriptBgDispatcher) }
private val androidEndpoint: GaeAndroidEndpoint by lazy {
GaeAndroidEndpointJsonImpl(
apiSecret,
gaeBaseUrl,
apiDebugDir,
forceCacheLoad = false,
scriptBgDispatcher,
imageDownloader,
forcedVersions = null // Always load latest when creating the pin versions list.
)
}

fun downloadLessonListAsync(lessonListOutputFile: File): Deferred<Unit> {
return CoroutineScope(scriptBgDispatcher).async {
println("Config: Using ${apiDebugDir.path}/ for storing API responses (for debugging).")

val listResponse = downloadTopicListResponseDto()
println()

println("Writing captured lesson structure versions to:")
println(lessonListOutputFile.path)
withContext(Dispatchers.IO) {
lessonListOutputFile.outputStream().bufferedWriter().use {
TextFormat.printer().print(listResponse.captureVersions(), it)
}
}
}
}

private suspend fun downloadTopicListResponseDto(): TopicListResponseDto {
val defaultLanguage = LanguageType.ENGLISH
val requestedLanguages = setOf(
LanguageType.ARABIC,
LanguageType.BRAZILIAN_PORTUGUESE,
LanguageType.NIGERIAN_PIDGIN
)
val listRequest = TopicListRequestDto.newBuilder().apply {
protoVersion = ProtoVersionProvider.createLatestTopicListProtoVersion()
clientContext = CLIENT_CONTEXT
compatibilityContext = ProtoVersionProvider.createCompatibilityContext()
// No structures are considered already downloaded. TODO: Integrate with local files cache?
requestedDefaultLanguage = defaultLanguage
// addAllRequiredAdditionalLanguages(requestedLanguages)
addAllSupportedAdditionalLanguages(requestedLanguages)
}.build()

println()
val listContentMessage = "Sending topic list download request"
val extraDotsThatCanFitForList = CONSOLE_COLUMN_COUNT - listContentMessage.length
var lastDotCount = 0
print(listContentMessage)
val listResponse =
androidEndpoint.fetchTopicListAsync(listRequest) { finishCount, totalCount ->
val dotCount = (extraDotsThatCanFitForList * finishCount) / totalCount
val dotsToAdd = dotCount - lastDotCount
if (dotsToAdd > 0) {
print(".".repeat(dotsToAdd))
lastDotCount = dotCount
}
}.await()
println()

return listResponse
}

private companion object {
private val CLIENT_CONTEXT = AndroidClientContextDto.newBuilder().apply {
appVersionName = checkNotNull(LessonListDownloader::class.qualifiedName)
appVersionCode = 0
}.build()
private const val CONSOLE_COLUMN_COUNT = 80

private const val PLACE_VALUES_ID = "iX9kYCjnouWN"
private const val ADDITION_AND_SUBTRACTION_ID = "sWBXKH4PZcK6"
private const val MULTIPLICATION_ID = "C4fqwrvqWpRm"
private const val DIVISION_ID = "qW12maD4hiA8"
private const val EXPRESSIONS_AND_EQUATIONS_ID = "dLmjjMDbCcrf"
private const val FRACTIONS_ID = "0abdeaJhmfPm"
private const val RATIOS_ID = "5g0nxGUmx5J5"

private val fractionsDependencies by lazy {
setOf(ADDITION_AND_SUBTRACTION_ID, MULTIPLICATION_ID, DIVISION_ID)
}
private val ratiosDependencies by lazy {
setOf(ADDITION_AND_SUBTRACTION_ID, MULTIPLICATION_ID, DIVISION_ID)
}
private val additionAndSubtractionDependencies by lazy { setOf(PLACE_VALUES_ID) }
private val multiplicationDependencies by lazy { setOf(ADDITION_AND_SUBTRACTION_ID) }
private val divisionDependencies by lazy { setOf(MULTIPLICATION_ID) }
private val placeValuesDependencies by lazy { setOf<String>() }
private val expressionsAndEquationsDependencies by lazy {
setOf(ADDITION_AND_SUBTRACTION_ID, MULTIPLICATION_ID, DIVISION_ID)
}

// TODO: Migrate deps over to the data coming from GAE (since it *is* present).
private val topicDependenciesTable by lazy {
mapOf(
FRACTIONS_ID to fractionsDependencies,
RATIOS_ID to ratiosDependencies,
ADDITION_AND_SUBTRACTION_ID to additionAndSubtractionDependencies,
MULTIPLICATION_ID to multiplicationDependencies,
DIVISION_ID to divisionDependencies,
PLACE_VALUES_ID to placeValuesDependencies,
EXPRESSIONS_AND_EQUATIONS_ID to expressionsAndEquationsDependencies,
)
}

private fun TopicListResponseDto.captureVersions(): DownloadListVersions {
val downloadableTopics = availableTopicsList.filter { availableTopic ->
availableTopic.availabilityTypeCase == DOWNLOADABLE_TOPIC
}.map { it.downloadableTopic.topicSummary }
val topicInfos = downloadableTopics.map { it.captureVersions() }

// Ensure that duplicate skill structures are actually the same for a given ID.
val allReferencedSkills =
downloadableTopics.flatMap { it.referencedSkillsList }.groupBy { it.id }
val uniqueReferencedSkills = allReferencedSkills.mapValues { (skillId, dupedSkills) ->
val distinctSkills = dupedSkills.distinct()
check(distinctSkills.size == 1) {
"Expected all references to skill $skillId to be the same skill structure."
}
return@mapValues distinctSkills.single()
}

val skillInfos = uniqueReferencedSkills.map { (skillId, skillSummary) ->
SkillInfo.newBuilder().apply {
this.id = skillId
this.contentVersion = skillSummary.contentVersion
}.build()
}
return DownloadListVersions.newBuilder().apply {
addAllTrackedTopicInfo(topicInfos)
addAllTrackedSkillInfo(skillInfos)
}.build()
}

private fun DownloadableTopicSummaryDto.captureVersions(): TopicInfo {
return TopicInfo.newBuilder().apply {
this.id = this@captureVersions.id
this.contentVersion = this@captureVersions.contentVersion
addAllStoryInfo(this@captureVersions.storySummariesList.map { it.captureVersions() })
addAllSubtopicInfo(this@captureVersions.subtopicSummariesList.map { it.captureVersion() })
}.build()
}

private fun StorySummaryDto.captureVersions(): StoryInfo {
return StoryInfo.newBuilder().apply {
this.id = this@captureVersions.id
this.contentVersion = this@captureVersions.contentVersion
addAllChapterInfo(this@captureVersions.chaptersList.map { it.captureVersion() })
}.build()
}

private fun ChapterSummaryDto.captureVersion(): ChapterInfo {
return ChapterInfo.newBuilder().apply {
this.explorationId = this@captureVersion.explorationId
this.explorationContentVersion = this@captureVersion.contentVersion
}.build()
}

private fun SubtopicSummaryDto.captureVersion(): SubtopicInfo {
return SubtopicInfo.newBuilder().apply {
this.index = this@captureVersion.index
this.contentVersion = this@captureVersion.contentVersion
}.build()
}
}
}
Loading
Loading