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

Use Sqldelight for translations #2485

Merged
merged 1 commit into from
Dec 9, 2023
Merged
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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ dependencies {
implementation project(path: ':common:recitation')
implementation project(path: ':common:search')
implementation project(path: ':common:toolbar')
implementation project(path: ':common:translation')
implementation project(path: ':common:upgrade')
implementation project(path: ':common:ui:core')

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.quran.labs.androidquran.common

import com.quran.mobile.translation.model.LocalTranslation

class LocalTranslationDisplaySort : Comparator<LocalTranslation> {
override fun compare(first: LocalTranslation, second: LocalTranslation): Int {
return first.displayOrder.compareTo(second.displayOrder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ data class Translation(val id: Int,
val saveTo: String,
val languageCode: String,
val translator: String? = "",
@Json(name = "translatorForeign") val translatorNameLocalized: String? = "",
val displayOrder: Int = -1) {
@Json(name = "translatorForeign") val translatorNameLocalized: String? = "") {

fun withSchema(schema: Int) = copy(minimumVersion = schema)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.quran.labs.androidquran.dao.translation

import com.quran.mobile.translation.model.LocalTranslation

data class TranslationItem @JvmOverloads constructor(val translation: Translation,
val localVersion: Int = 0,
val displayOrder: Int = -1) : TranslationRowData {
Expand All @@ -16,7 +18,22 @@ data class TranslationItem @JvmOverloads constructor(val translation: Translatio

fun withTranslationRemoved() = this.copy(localVersion = 0)

fun withTranslationVersion(version: Int) = this.copy(localVersion = version)

fun withDisplayOrder(newDisplayOrder: Int) = this.copy(displayOrder = newDisplayOrder)

fun withLocalVersionAndDisplayOrder(newVersion: Int, displayOrder: Int) = this.copy(localVersion = newVersion, displayOrder = displayOrder)

fun asLocalTranslation(): LocalTranslation {
return LocalTranslation(
id = translation.id.toLong(),
filename = translation.fileName,
name = translation.displayName,
translator = translation.translator,
translatorForeign = translation.translatorNameLocalized,
url = translation.fileUrl,
languageCode = translation.languageCode,
version = localVersion,
minimumVersion = translation.minimumVersion,
displayOrder = displayOrder
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@
import android.net.Uri;
import android.provider.BaseColumns;

import androidx.annotation.NonNull;

import com.quran.data.core.QuranInfo;
import com.quran.labs.androidquran.BuildConfig;
import com.quran.labs.androidquran.QuranApplication;
import com.quran.labs.androidquran.R;
import com.quran.labs.androidquran.common.LocalTranslation;
import com.quran.labs.androidquran.database.DatabaseHandler;
import com.quran.labs.androidquran.database.DatabaseUtils;
import com.quran.labs.androidquran.database.TranslationsDBAdapter;
import com.quran.labs.androidquran.util.QuranFileUtils;
import com.quran.labs.androidquran.util.QuranUtils;
import com.quran.mobile.translation.model.LocalTranslation;

import java.util.List;

import javax.inject.Inject;

import androidx.annotation.NonNull;
import timber.log.Timber;

public class QuranDataProvider extends ContentProvider {
Expand Down Expand Up @@ -107,7 +108,7 @@ private Cursor search(String query) {
}

private List<LocalTranslation> getAvailableTranslations() {
return translationsDBAdapter.getTranslations();
return translationsDBAdapter.legacyGetTranslations();
}

private Cursor getSuggestions(String query) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,163 +1,91 @@
package com.quran.labs.androidquran.database

import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.util.SparseArray
import androidx.annotation.WorkerThread
import com.quran.labs.androidquran.common.LocalTranslation
import com.quran.labs.androidquran.dao.translation.TranslationItem
import com.quran.labs.androidquran.database.TranslationsDBHelper.TranslationsTable
import com.quran.labs.androidquran.util.QuranFileUtils
import com.quran.mobile.di.qualifier.ApplicationContext
import timber.log.Timber
import java.util.Collections
import com.quran.mobile.translation.data.TranslationsDataSource
import com.quran.mobile.translation.model.LocalTranslation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class TranslationsDBAdapter @Inject constructor(
@ApplicationContext private val context: Context,
adapter: TranslationsDBHelper,
private val dataSource: TranslationsDataSource,
private val quranFileUtils: QuranFileUtils
) {
private val db: SQLiteDatabase = adapter.writableDatabase

@Volatile
private var cachedTranslations: List<LocalTranslation>? = null
fun getTranslations(): Flow<List<LocalTranslation>> {
return dataSource.translations()
.filterNotNull()
.map { translations ->
translations.filter { quranFileUtils.hasTranslation(context, it.filename) }
}
}

var lastWriteTime: Long = 0
private set
@WorkerThread
fun legacyGetTranslations(): List<LocalTranslation> {
return runBlocking { getTranslations().first() }
}

@WorkerThread
fun getTranslationsHash(): SparseArray<LocalTranslation> {
val result = SparseArray<LocalTranslation>()
for (item in getTranslations()) {
result.put(item.id, item)
for (item in legacyGetTranslations()) {
result.put(item.id.toInt(), item)
}
return result
}

// intentional, since cachedTranslations can be replaced by another thread, causing the check
// to be true, but the cached object returned to be null (or to change).
@WorkerThread
fun getTranslations(): List<LocalTranslation> {
// intentional, since cachedTranslations can be replaced by another thread, causing the check
// to be true, but the cached object returned to be null (or to change).
val cached = cachedTranslations
if (!cached.isNullOrEmpty()) {
return cached
}
var items: MutableList<LocalTranslation> = ArrayList()
var cursor: Cursor? = null
try {
cursor = db.query(TranslationsTable.TABLE_NAME,
null, null, null, null, null,
TranslationsTable.ID + " ASC")

while (cursor.moveToNext()) {
val id = cursor.getInt(0)
val name = cursor.getString(1)
val translator = cursor.getString(2)
val translatorForeign = cursor.getString(3)
val filename = cursor.getString(4)
val url = cursor.getString(5)
val languageCode = cursor.getString(6)
val version = cursor.getInt(7)
val minimumVersion = cursor.getInt(8)
val displayOrder = cursor.getInt(9)

if (quranFileUtils.hasTranslation(context, filename)) {
items.add(
LocalTranslation(
id, filename, name, translator,
translatorForeign, url, languageCode, version, minimumVersion, displayOrder
)
)
}
}
} finally {
cursor?.close()
}
suspend fun deleteTranslationByFileName(filename: String) {
dataSource.removeTranslation(filename)
}

items = Collections.unmodifiableList(items)
if (items.size > 0) {
cachedTranslations = items
@WorkerThread
fun legacyDeleteTranslationByFileName(filename: String) {
runBlocking {
deleteTranslationByFileName(filename)
}
return items
}

fun deleteTranslationByFile(filename: String) {
db.execSQL("DELETE FROM " + TranslationsTable.TABLE_NAME + " WHERE " +
TranslationsTable.FILENAME + " = ?", arrayOf<Any>(filename))
@WorkerThread
fun legacyWriteTranslationUpdates(updates: List<TranslationItem>): Boolean {
return runBlocking { writeTranslationUpdates(updates) }
}

fun writeTranslationUpdates(updates: List<TranslationItem>): Boolean {
var result = true
db.beginTransaction()
suspend fun writeTranslationUpdates(updates: List<TranslationItem>): Boolean {
val (available, unavailable) = updates.partition { it.exists() }

try {
var cachedNextOrder = -1
for (item in updates) {
if (item.exists()) {
var displayOrder = 0
val translation = item.translation
if (item.displayOrder > -1) {
displayOrder = item.displayOrder
} else {
var cursor: Cursor? = null
if (cachedNextOrder == -1) {
try {
// get next highest display order
cursor = db.query(
TranslationsTable.TABLE_NAME, arrayOf(TranslationsTable.DISPLAY_ORDER),
null, null, null, null,
TranslationsTable.DISPLAY_ORDER + " DESC",
"1"
)
if (cursor != null && cursor.moveToFirst()) {
cachedNextOrder = cursor.getInt(0) + 1
displayOrder = cachedNextOrder++
}
} finally {
cursor?.close()
}
} else {
displayOrder = cachedNextOrder++
}
}

val values = ContentValues()
values.put(TranslationsTable.ID, translation.id)
values.put(TranslationsTable.NAME, translation.displayName)
values.put(TranslationsTable.TRANSLATOR, translation.translator)
values.put(TranslationsTable.TRANSLATOR_FOREIGN,
translation.translatorNameLocalized)
values.put(TranslationsTable.FILENAME, translation.fileName)
values.put(TranslationsTable.URL, translation.fileUrl)
values.put(TranslationsTable.LANGUAGE_CODE, translation.languageCode)
values.put(TranslationsTable.VERSION, item.localVersion)
values.put(TranslationsTable.MINIMUM_REQUIRED_VERSION, translation.minimumVersion)
values.put(TranslationsTable.DISPLAY_ORDER, displayOrder)
val needNextOrder = available.any { it.displayOrder == -1 }
val nextOrder = if (needNextOrder) {
dataSource.maximumDisplayOrder().toInt() + 1
} else {
(available.maxOfOrNull { it.displayOrder } ?: 0) + 1
}

db.replace(TranslationsTable.TABLE_NAME, null, values)
val items = if (needNextOrder) {
var nextOrderNumber = nextOrder
available.map { item ->
if (item.displayOrder == -1) {
item.copy(displayOrder = nextOrderNumber++)
} else {
db.delete(TranslationsTable.TABLE_NAME,
TranslationsTable.ID + " = " + item.translation.id, null)
item
}
}
db.setTransactionSuccessful()

lastWriteTime = System.currentTimeMillis()
// clear the cached translations
cachedTranslations = null
} catch (e: Exception) {
result = false
Timber.d(e, "error writing translation updates")
} finally {
db.endTransaction()
} else {
available
}

return result
dataSource.updateTranslations(items.map { it.asLocalTranslation() })
dataSource.removeTranslationsById(unavailable.map { it.translation.id.toLong() })

return true
}
}
Loading