Browse Source

Don't use reflection for handling backup options as boolean array

Wasn't working correctly in release build, _probably_ because of R8 despite kotlin-reflect
shipping with Proguard rules and us already keeping all Tachiyomi classes.
arkon 1 year ago
parent
commit
6ab8e1e73d

+ 0 - 1
app/src/main/java/eu/kanade/presentation/more/settings/screen/data/RestoreBackupScreen.kt

@@ -32,7 +32,6 @@ import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
 import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
 import eu.kanade.tachiyomi.util.system.DeviceUtil
 import kotlinx.coroutines.flow.update
-import tachiyomi.core.util.lang.anyEnabled
 import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.LabeledCheckbox
 import tachiyomi.presentation.core.components.LazyColumnWithAction

+ 1 - 3
app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreateJob.kt

@@ -24,8 +24,6 @@ import eu.kanade.tachiyomi.util.system.isRunning
 import eu.kanade.tachiyomi.util.system.setForegroundSafely
 import eu.kanade.tachiyomi.util.system.workManager
 import logcat.LogPriority
-import tachiyomi.core.util.lang.asBooleanArray
-import tachiyomi.core.util.lang.asDataClass
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.backup.service.BackupPreferences
 import tachiyomi.domain.storage.service.StorageManager
@@ -49,7 +47,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
 
         setForegroundSafely()
 
-        val options: BackupOptions = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()
+        val options = inputData.getBooleanArray(OPTIONS_KEY)?.let { BackupOptions.fromBooleanArray(it) }
             ?: BackupOptions()
 
         return try {

+ 22 - 0
app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupOptions.kt

@@ -15,6 +15,17 @@ data class BackupOptions(
     val privateSettings: Boolean = false,
 ) {
 
+    fun asBooleanArray() = booleanArrayOf(
+        libraryEntries,
+        categories,
+        chapters,
+        tracking,
+        history,
+        appSettings,
+        sourceSettings,
+        privateSettings,
+    )
+
     companion object {
         val libraryOptions = persistentListOf(
             Entry(
@@ -66,6 +77,17 @@ data class BackupOptions(
                 enabled = { it.appSettings || it.sourceSettings },
             ),
         )
+
+        fun fromBooleanArray(array: BooleanArray) = BackupOptions(
+            libraryEntries = array[0],
+            categories = array[1],
+            chapters = array[2],
+            tracking = array[3],
+            history = array[4],
+            appSettings = array[5],
+            sourceSettings = array[6],
+            privateSettings = array[7],
+        )
     }
 
     data class Entry(

+ 1 - 3
app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt

@@ -20,8 +20,6 @@ import eu.kanade.tachiyomi.util.system.workManager
 import kotlinx.coroutines.CancellationException
 import logcat.LogPriority
 import tachiyomi.core.i18n.stringResource
-import tachiyomi.core.util.lang.asBooleanArray
-import tachiyomi.core.util.lang.asDataClass
 import tachiyomi.core.util.system.logcat
 import tachiyomi.i18n.MR
 
@@ -32,7 +30,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
 
     override suspend fun doWork(): Result {
         val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
-        val options: RestoreOptions? = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()
+        val options = inputData.getBooleanArray(OPTIONS_KEY)?.let { RestoreOptions.fromBooleanArray(it) }
 
         if (uri == null || options == null) {
             return Result.failure()

+ 14 - 0
app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt

@@ -10,6 +10,14 @@ data class RestoreOptions(
     val sourceSettings: Boolean = true,
 ) {
 
+    fun asBooleanArray() = booleanArrayOf(
+        library,
+        appSettings,
+        sourceSettings,
+    )
+
+    fun anyEnabled() = library || appSettings || sourceSettings
+
     companion object {
         val options = persistentListOf(
             Entry(
@@ -28,6 +36,12 @@ data class RestoreOptions(
                 setter = { options, enabled -> options.copy(sourceSettings = enabled) },
             ),
         )
+
+        fun fromBooleanArray(array: BooleanArray) = RestoreOptions(
+            library = array[0],
+            appSettings = array[1],
+            sourceSettings = array[2],
+        )
     }
 
     data class Entry(

+ 0 - 1
core/build.gradle.kts

@@ -33,7 +33,6 @@ dependencies {
 
     implementation(libs.unifile)
 
-    implementation(kotlinx.reflect)
     api(kotlinx.coroutines.core)
     api(kotlinx.serialization.json)
     api(kotlinx.serialization.json.okio)

+ 0 - 26
core/src/main/java/tachiyomi/core/util/lang/BooleanDataClassExtensions.kt

@@ -1,26 +0,0 @@
-package tachiyomi.core.util.lang
-
-import kotlin.reflect.KProperty1
-import kotlin.reflect.full.declaredMemberProperties
-import kotlin.reflect.full.primaryConstructor
-
-fun <T : Any> T.asBooleanArray(): BooleanArray {
-    val constructorParams = this::class.primaryConstructor!!.parameters.map { it.name }
-    val properties = this::class.declaredMemberProperties
-        .filterIsInstance<KProperty1<T, Boolean>>()
-    return constructorParams
-        .map { param -> properties.find { it.name == param }!!.get(this) }
-        .toBooleanArray()
-}
-
-inline fun <reified T : Any> BooleanArray.asDataClass(): T {
-    val properties = T::class.declaredMemberProperties.filterIsInstance<KProperty1<T, Boolean>>()
-    require(properties.size == this.size) { "Boolean array size does not match data class property count" }
-    return T::class.primaryConstructor!!.call(*this.toTypedArray())
-}
-
-fun <T : Any> T.anyEnabled(): Boolean {
-    return this::class.declaredMemberProperties
-        .filterIsInstance<KProperty1<T, Boolean>>()
-        .any { it.get(this) }
-}

+ 0 - 63
core/src/test/kotlin/tachiyomi/core/util/lang/BooleanDataClassExtensionsTest.kt

@@ -1,63 +0,0 @@
-package tachiyomi.core.util.lang
-
-import org.junit.jupiter.api.Assertions.assertArrayEquals
-import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Assertions.assertFalse
-import org.junit.jupiter.api.Assertions.assertTrue
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.junit.jupiter.api.parallel.Execution
-import org.junit.jupiter.api.parallel.ExecutionMode
-
-@Execution(ExecutionMode.CONCURRENT)
-class BooleanDataClassExtensionsTest {
-
-    @Test
-    fun `asBooleanArray converts data class to boolean array`() {
-        assertArrayEquals(booleanArrayOf(true, false), TestClass(foo = true, bar = false).asBooleanArray())
-        assertArrayEquals(booleanArrayOf(false, true), TestClass(foo = false, bar = true).asBooleanArray())
-    }
-
-    @Test
-    fun `asBooleanArray throws error for invalid data classes`() {
-        assertThrows<ClassCastException> {
-            InvalidTestClass(foo = true, bar = "").asBooleanArray()
-        }
-    }
-
-    @Test
-    fun `asDataClass converts from boolean array`() {
-        assertEquals(booleanArrayOf(true, false).asDataClass<TestClass>(), TestClass(foo = true, bar = false))
-        assertEquals(booleanArrayOf(false, true).asDataClass<TestClass>(), TestClass(foo = false, bar = true))
-    }
-
-    @Test
-    fun `asDataClass throws error for invalid boolean array`() {
-        assertThrows<IllegalArgumentException> {
-            booleanArrayOf(true).asDataClass<TestClass>()
-        }
-    }
-
-    @Test
-    fun `anyEnabled returns based on if any boolean property is enabled`() {
-        assertTrue(TestClass(foo = false, bar = true).anyEnabled())
-        assertFalse(TestClass(foo = false, bar = false).anyEnabled())
-    }
-
-    @Test
-    fun `anyEnabled throws error for invalid class`() {
-        assertThrows<ClassCastException> {
-            InvalidTestClass(foo = true, bar = "").anyEnabled()
-        }
-    }
-
-    data class TestClass(
-        val foo: Boolean,
-        val bar: Boolean,
-    )
-
-    data class InvalidTestClass(
-        val foo: Boolean,
-        val bar: String,
-    )
-}