Skip to content

Commit

Permalink
Adding translation/translate submodule,
Browse files Browse the repository at this point in the history
Updating submodules.
TranslationLoader to check if extracted translator's JS code needs to be updated and updates it.
bundle_translation.py zips translator's JS code and puts it into project's assets folder.
Updating android.yml to support bundling of translation during CI build.

Upping versionCode to 59
  • Loading branch information
Dima-Android committed May 1, 2024
1 parent 9219fdc commit 44382a5
Show file tree
Hide file tree
Showing 22 changed files with 1,447 additions and 24 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ jobs:
- name: Execute bundle_translators.py
run: python3 scripts/bundle_translators.py

- name: Grant execute permission for bundle_translation.py
run: chmod +x scripts/bundle_translation.py

- name: Execute bundle_translation.py
run: python3 scripts/bundle_translation.py

- name: Grant execute permission for gradlew
run: chmod +x gradlew

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "translators"]
path = translators
url = https://github.com/zotero/translators.git
[submodule "translation/translate"]
path = translation/translate
url = https://github.com/zotero/translate.git
2 changes: 1 addition & 1 deletion app/src/main/assets/timestamp.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1713182643
1714480514
Binary file removed app/src/main/assets/translator.zip
Binary file not shown.
18 changes: 9 additions & 9 deletions app/src/main/java/org/zotero/android/architecture/Defaults.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ open class Defaults @Inject constructor(
private val activeLineWidth = "activeLineWidth"
private val activeEraserSize = "activeEraserSize"
private val shareExtensionIncludeTags = "shareExtensionIncludeTags"
private val shouldUpdateTranslator = "shouldUpdateTranslator"

private val lastTimestamp = "lastTimestamp"
private val lastTranslationCommitHash = "lastTranslationCommitHash"
private val lastTranslatorCommitHash = "lastTranslatorCommitHash"
private val lastTranslatorDeleted = "lastTranslatorDeleted"
private val lastStylesCommitHash = "lastStylesCommitHash"
Expand Down Expand Up @@ -257,14 +257,6 @@ open class Defaults @Inject constructor(
return sharedPreferences.getBoolean(wasPspdfkitInitialized, false)
}

fun setShouldUpdateTranslator(newValue: Boolean) {
sharedPreferences.edit { putBoolean(shouldUpdateTranslator, newValue) }
}

fun shouldUpdateTranslator(): Boolean {
return sharedPreferences.getBoolean(shouldUpdateTranslator, true)
}

fun setLastTimestamp(newValue: Long) {
sharedPreferences.edit { putLong(lastTimestamp, newValue) }
}
Expand All @@ -281,6 +273,14 @@ open class Defaults @Inject constructor(
return sharedPreferences.getString(lastTranslatorCommitHash, "") ?: ""
}

fun setLastTranslationCommitHash(newValue: String) {
sharedPreferences.edit { putString(lastTranslationCommitHash, newValue) }
}

fun getLastTranslationCommitHash(): String {
return sharedPreferences.getString(lastTranslationCommitHash, "") ?: ""
}

fun getLastTranslatorDeleted(): Long {
return sharedPreferences.getLong(lastTranslatorDeleted, 0L)
}
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/org/zotero/android/sync/Controllers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.zotero.android.attachmentdownloader.AttachmentDownloader
import org.zotero.android.database.DbWrapper
import org.zotero.android.files.FileStore
import org.zotero.android.screens.share.backgroundprocessor.BackgroundUploadProcessor
import org.zotero.android.translator.loader.TranslationLoader
import org.zotero.android.translator.loader.TranslatorsLoader
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -44,6 +45,7 @@ class Controllers @Inject constructor(
private val debugLogging: DebugLogging,
private val crashReporter: CrashReporter,
private val translatorsLoader: TranslatorsLoader,
private val translationLoader: TranslationLoader,
private val context: Context,
) {
private var sessionCancellable: Job? = null
Expand Down Expand Up @@ -75,7 +77,7 @@ class Controllers @Inject constructor(
private fun updateTranslatorAndTranslatorItems() {
coroutineScope.launch {
try {
translatorsLoader.updateTranslatorIfNeeded()
translationLoader.updateTranslationIfNeeded()
translatorsLoader.updateTranslatorItemsIfNeeded()
} catch (e: Exception) {
Timber.e(e, "Failed to update Translator or translation items")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package org.zotero.android.translator.loader

import android.content.Context
import org.apache.commons.io.IOUtils
import org.zotero.android.architecture.Defaults
import org.zotero.android.files.FileStore
import org.zotero.android.helpers.Unzipper
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class TranslationLoader @Inject constructor(
private val context: Context,
private val defaults: Defaults,
private val unzipper: Unzipper,
private val fileStore: FileStore,
) {
enum class UpdateType(val i: Int) {
manual(1),
initial(2),
startup(3),
notification(4),
shareExtension(5);
}

sealed class Error : Exception() {
data class bundleLoading(val exception: Exception) : Error()
object bundleMissing : Error()

val isBundleLoadingError: Boolean
get() {
return when (this) {
is bundleLoading -> {
true
}

else -> {
false
}
}
}
}

fun updateTranslationIfNeeded() {
_update()
}

private fun _update() {
val type: UpdateType =
if (defaults.getLastTimestamp() == 0L) {
UpdateType.initial
} else {
UpdateType.startup
}

Timber.i("TranslationLoader: update translation JS")
try {
checkFolderIntegrity(type = type)
updateFromBundle()
defaults.setLastTimestamp(System.currentTimeMillis() / 1000)
} catch (error: Exception) {
process(error = error, updateType = type)
}
}

private fun _updateTranslationFromBundle(forceUpdate: Boolean) {
val hash = loadLastTranslationCommitHash()
Timber.i("TranslationLoader: should update translation from bundle, forceUpdate=$forceUpdate; oldHash=${defaults.getLastTranslationCommitHash()}; newHash=$hash")
if (!forceUpdate && defaults.getLastTranslationCommitHash() == hash) {
return
}
Timber.i("TranslationLoader: update translation from bundle")
updateTranslation()

defaults.setLastTranslationCommitHash(hash)
}

private fun updateTranslation() {
unzipper.unzipStream(
zipInputStream = context.assets.open("translator.zip"),
location = fileStore.translatorDirectory().absolutePath
)
}

private fun loadLastTranslationCommitHash(): String {
return loadFromBundle(resource = "translation_commit_hash.txt", map = { it })
}


private inline fun <reified Result> loadFromBundle(
resource: String,
map: (String) -> Result
): Result {
try {
val inputStream = context.assets.open(resource)
val rawValue = IOUtils.toString(inputStream)
return map(rawValue.trim().trim { it == '\n' })
} catch (e: Exception) {
Timber.e(e)
throw Error.bundleMissing
}
}

private fun updateFromBundle() {
try {
_updateTranslationFromBundle(forceUpdate = false)
val timestamp = loadLastTimestamp()
if (timestamp > defaults.getLastTimestamp()) {
defaults.setLastTimestamp(timestamp)
return
} else {
return
}

} catch (error: Exception) {
Timber.e(error, "TranslatorsLoader: can't update from bundle")
throw Error.bundleLoading(error)
}
}

private fun loadLastTimestamp(): Long {
return loadFromBundle(resource = "timestamp.txt", map = {
try {
return it.toLong()
} catch (e: Exception) {
Timber.e(e)
throw Error.bundleMissing
}
})
}

private fun checkFolderIntegrity(type: UpdateType) {
try {
if (!fileStore.translatorDirectory().exists()) {
if (type != UpdateType.initial) {
Timber.e("TranslationLoader: translation directory was missing!")
}
fileStore.translatorDirectory().mkdirs()
}

if (type == UpdateType.initial) {
return
}

val fileCount = fileStore.translatorDirectory().listFiles()?.size ?: 0

if (fileCount != 0) {
return
}

defaults.setLastTimestamp(0L)
defaults.setLastTranslationCommitHash("")
} catch (error: Exception) {
Timber.e(error, "TranslationLoader: unable to restore folder integrity")
throw error
}

}

private fun process(error: Exception, updateType: UpdateType) {
Timber.e(error, "TranslatorsLoader: error")

val isBundleLoadingError = (error as? Error)?.isBundleLoadingError == true
if (!isBundleLoadingError) {
return
}
//TODO show bundle load error dialog

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import org.zotero.android.architecture.coroutines.Dispatchers
import org.zotero.android.database.DbWrapper
import org.zotero.android.database.requests.SyncTranslatorsDbRequest
import org.zotero.android.files.FileStore
import org.zotero.android.helpers.Unzipper
import org.zotero.android.screens.share.data.TranslatorMetadata
import timber.log.Timber
import java.io.File
Expand All @@ -30,7 +29,6 @@ class TranslatorsLoader @Inject constructor(
private val context: Context,
private val gson: Gson,
private val defaults: Defaults,
private val unzipper: Unzipper,
private val itemsUnzipper: TranslatorItemsUnzipper,
private val fileStore: FileStore,
@BundleDataDb
Expand Down Expand Up @@ -245,16 +243,6 @@ class TranslatorsLoader @Inject constructor(
return null
}

fun updateTranslatorIfNeeded() {
if (defaults.shouldUpdateTranslator()) {
unzipper.unzipStream(
zipInputStream = context.assets.open("translator.zip"),
location = fileStore.translatorDirectory().absolutePath
)
defaults.setShouldUpdateTranslator(false)
}
}

private fun loadIndex(): List<TranslatorMetadata> {
val inputStream = context.assets.open("translators/index.json")
val type =
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/BuildConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ object BuildConfig {
const val compileSdkVersion = 34
const val targetSdk = 33

val versionCode = 58 // Must be updated on every build
val versionCode = 59 // Must be updated on every build
val version = Version(
major = 1,
minor = 0,
Expand Down
34 changes: 34 additions & 0 deletions scripts/bundle_translation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import json
import os
import re
import shutil
import subprocess
import time

def commit_hash_from_submodules(array):
for line in array:
if line.startswith("translation/translate"):
return line.split()[1]

# Get bundle directory
bundle_dir = os.path.join(os.path.abspath("."), "app" + os.sep + "src" + os.sep + "main" + os.sep + "assets")

if not os.path.isdir(bundle_dir):
os.mkdir(bundle_dir)

# Get translation directory
translators_dir = os.path.join(os.path.abspath("."), "translation")

if not os.path.isdir(translators_dir):
raise Exception(translators_dir + " is not a directory. Call update_bundled_data.py first.")

# Store last commit hash from translation submodule
submodules = subprocess.check_output(["git", "submodule", "foreach", "--recursive", "echo $path `git rev-parse HEAD`"]).decode("utf-8").splitlines()
commit_hash = commit_hash_from_submodules(submodules)

with open(os.path.join(bundle_dir, "translation_commit_hash.txt"), "w") as f:
f.write(str(commit_hash))

# Zip translator
os.chdir(translators_dir)
subprocess.check_call(['zip', '-r', os.path.join(bundle_dir, "translator.zip"), "."])
Loading

0 comments on commit 44382a5

Please sign in to comment.