Răsfoiți Sursa

Add ability to create manual backups with private preferences too

arkon 1 an în urmă
părinte
comite
ccec5c3efe

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

@@ -154,14 +154,6 @@ private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel
 
     @Immutable
     data class State(
-        val options: BackupOptions = BackupOptions(
-            libraryEntries = true,
-            categories = true,
-            chapters = true,
-            tracking = true,
-            history = true,
-            appSettings = false,
-            sourceSettings = false,
-        ),
+        val options: BackupOptions = BackupOptions(),
     )
 }

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

@@ -19,6 +19,8 @@ import com.hippo.unifile.UniFile
 import eu.kanade.tachiyomi.data.backup.BackupNotifier
 import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
 import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.util.lang.asBooleanArray
+import eu.kanade.tachiyomi.util.lang.asDataClass
 import eu.kanade.tachiyomi.util.system.cancelNotification
 import eu.kanade.tachiyomi.util.system.isRunning
 import eu.kanade.tachiyomi.util.system.setForegroundSafely
@@ -47,9 +49,8 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
 
         setForegroundSafely()
 
-        val options = inputData.getBooleanArray(OPTIONS_KEY)
-            ?.let { BackupOptions.fromBooleanArray(it) }
-            ?: BackupOptions.AutomaticDefaults
+        val options: BackupOptions = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()
+            ?: BackupOptions()
 
         return try {
             val location = BackupCreator(context, isAutoBackup).backup(uri, options)
@@ -118,7 +119,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
             val inputData = workDataOf(
                 IS_AUTO_BACKUP_KEY to false,
                 LOCATION_URI_KEY to uri.toString(),
-                OPTIONS_KEY to options.toBooleanArray(),
+                OPTIONS_KEY to options.asBooleanArray(),
             )
             val request = OneTimeWorkRequestBuilder<BackupCreateJob>()
                 .addTag(TAG_MANUAL)

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt

@@ -131,13 +131,13 @@ class BackupCreator(
     private fun backupAppPreferences(options: BackupOptions): List<BackupPreference> {
         if (!options.appSettings) return emptyList()
 
-        return preferenceBackupCreator.backupAppPreferences()
+        return preferenceBackupCreator.backupAppPreferences(includePrivatePreferences = options.privateSettings)
     }
 
     private fun backupSourcePreferences(options: BackupOptions): List<BackupSourcePreferences> {
         if (!options.sourceSettings) return emptyList()
 
-        return preferenceBackupCreator.backupSourcePreferences()
+        return preferenceBackupCreator.backupSourcePreferences(includePrivatePreferences = options.privateSettings)
     }
 
     companion object {

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

@@ -12,75 +12,52 @@ data class BackupOptions(
     val history: Boolean = true,
     val appSettings: Boolean = true,
     val sourceSettings: Boolean = true,
+    val privateSettings: Boolean = false,
 ) {
-    fun toBooleanArray() = booleanArrayOf(
-        libraryEntries,
-        categories,
-        chapters,
-        tracking,
-        history,
-        appSettings,
-        sourceSettings,
-    )
 
     companion object {
-        val AutomaticDefaults = BackupOptions(
-            libraryEntries = true,
-            categories = true,
-            chapters = true,
-            tracking = true,
-            history = true,
-            appSettings = true,
-            sourceSettings = true,
-        )
-
-        fun fromBooleanArray(booleanArray: BooleanArray) = BackupOptions(
-            libraryEntries = booleanArray[0],
-            categories = booleanArray[1],
-            chapters = booleanArray[2],
-            tracking = booleanArray[3],
-            history = booleanArray[4],
-            appSettings = booleanArray[5],
-            sourceSettings = booleanArray[6],
-        )
-
-        val entries = persistentListOf<BackupOptionEntry>(
-            BackupOptionEntry(
+        val entries = persistentListOf(
+            Entry(
                 label = MR.strings.categories,
                 getter = BackupOptions::categories,
                 setter = { options, enabled -> options.copy(categories = enabled) },
             ),
-            BackupOptionEntry(
+            Entry(
                 label = MR.strings.chapters,
                 getter = BackupOptions::chapters,
                 setter = { options, enabled -> options.copy(chapters = enabled) },
             ),
-            BackupOptionEntry(
+            Entry(
                 label = MR.strings.track,
                 getter = BackupOptions::tracking,
                 setter = { options, enabled -> options.copy(tracking = enabled) },
             ),
-            BackupOptionEntry(
+            Entry(
                 label = MR.strings.history,
                 getter = BackupOptions::history,
                 setter = { options, enabled -> options.copy(history = enabled) },
             ),
-            BackupOptionEntry(
+            Entry(
                 label = MR.strings.app_settings,
                 getter = BackupOptions::appSettings,
                 setter = { options, enabled -> options.copy(appSettings = enabled) },
             ),
-            BackupOptionEntry(
+            Entry(
                 label = MR.strings.source_settings,
                 getter = BackupOptions::sourceSettings,
                 setter = { options, enabled -> options.copy(sourceSettings = enabled) },
             ),
+            Entry(
+                label = MR.strings.private_settings,
+                getter = BackupOptions::privateSettings,
+                setter = { options, enabled -> options.copy(privateSettings = enabled) },
+            ),
         )
     }
-}
 
-data class BackupOptionEntry(
-    val label: StringResource,
-    val getter: (BackupOptions) -> Boolean,
-    val setter: (BackupOptions, Boolean) -> BackupOptions,
-)
+    data class Entry(
+        val label: StringResource,
+        val getter: (BackupOptions) -> Boolean,
+        val setter: (BackupOptions, Boolean) -> BackupOptions,
+    )
+}

+ 14 - 6
app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt

@@ -22,26 +22,27 @@ class PreferenceBackupCreator(
     private val preferenceStore: PreferenceStore = Injekt.get(),
 ) {
 
-    fun backupAppPreferences(): List<BackupPreference> {
+    fun backupAppPreferences(includePrivatePreferences: Boolean): List<BackupPreference> {
         return preferenceStore.getAll().toBackupPreferences()
+            .withPrivatePreferences(includePrivatePreferences)
     }
 
-    fun backupSourcePreferences(): List<BackupSourcePreferences> {
+    fun backupSourcePreferences(includePrivatePreferences: Boolean): List<BackupSourcePreferences> {
         return sourceManager.getCatalogueSources()
             .filterIsInstance<ConfigurableSource>()
             .map {
                 BackupSourcePreferences(
                     it.preferenceKey(),
-                    it.sourcePreferences().all.toBackupPreferences(),
+                    it.sourcePreferences().all.toBackupPreferences()
+                        .withPrivatePreferences(includePrivatePreferences),
                 )
             }
     }
 
     @Suppress("UNCHECKED_CAST")
     private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
-        return this.filterKeys {
-            !Preference.isAppState(it) && !Preference.isPrivate(it)
-        }
+        return this
+            .filterKeys { !Preference.isAppState(it) }
             .mapNotNull { (key, value) ->
                 when (value) {
                     is Int -> BackupPreference(key, IntPreferenceValue(value))
@@ -56,4 +57,11 @@ class PreferenceBackupCreator(
                 }
             }
     }
+
+    private fun List<BackupPreference>.withPrivatePreferences(include: Boolean) =
+        if (include) {
+            this
+        } else {
+            this.filter { !Preference.isPrivate(it.key) }
+        }
 }

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

@@ -13,6 +13,8 @@ import androidx.work.WorkerParameters
 import androidx.work.workDataOf
 import eu.kanade.tachiyomi.data.backup.BackupNotifier
 import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.util.lang.asBooleanArray
+import eu.kanade.tachiyomi.util.lang.asDataClass
 import eu.kanade.tachiyomi.util.system.cancelNotification
 import eu.kanade.tachiyomi.util.system.isRunning
 import eu.kanade.tachiyomi.util.system.setForegroundSafely
@@ -30,8 +32,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
 
     override suspend fun doWork(): Result {
         val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
-        val options = inputData.getBooleanArray(OPTIONS_KEY)
-            ?.let { RestoreOptions.fromBooleanArray(it) }
+        val options: RestoreOptions? = inputData.getBooleanArray(OPTIONS_KEY)?.asDataClass()
 
         if (uri == null || options == null) {
             return Result.failure()
@@ -84,7 +85,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
             val inputData = workDataOf(
                 LOCATION_URI_KEY to uri.toString(),
                 SYNC_KEY to sync,
-                OPTIONS_KEY to options.toBooleanArray(),
+                OPTIONS_KEY to options.asBooleanArray(),
             )
             val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
                 .addTag(TAG)

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

@@ -4,14 +4,4 @@ data class RestoreOptions(
     val appSettings: Boolean = true,
     val sourceSettings: Boolean = true,
     val library: Boolean = true,
-) {
-    fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library)
-
-    companion object {
-        fun fromBooleanArray(booleanArray: BooleanArray) = RestoreOptions(
-            appSettings = booleanArray[0],
-            sourceSettings = booleanArray[1],
-            library = booleanArray[2],
-        )
-    }
-}
+)

+ 18 - 0
app/src/main/java/eu/kanade/tachiyomi/util/lang/BooleanArrayExtensions.kt

@@ -0,0 +1,18 @@
+package eu.kanade.tachiyomi.util.lang
+
+import kotlin.reflect.KProperty1
+import kotlin.reflect.full.declaredMemberProperties
+import kotlin.reflect.full.primaryConstructor
+
+fun <T : Any> T.asBooleanArray(): BooleanArray {
+    return this::class.declaredMemberProperties
+        .filterIsInstance<KProperty1<T, Boolean>>()
+        .map { it.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())
+}

+ 1 - 0
i18n/src/commonMain/resources/MR/base/strings.xml

@@ -504,6 +504,7 @@
     <string name="backup_choice">What do you want to backup?</string>
     <string name="app_settings">App settings</string>
     <string name="source_settings">Source settings</string>
+    <string name="private_settings">Include sensitive settings (e.g., tracker login tokens)</string>
     <string name="creating_backup">Creating backup</string>
     <string name="creating_backup_error">Backup failed</string>
     <string name="missing_storage_permission">Storage permissions not granted</string>