Forráskód Böngészése

Add app settings to backups

This should be compatible with Aniyomi's implementation.
Related to #1857

Co-authored-by: jmir1 <[email protected]>
arkon 1 éve
szülő
commit
72024aa44a

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

@@ -147,6 +147,7 @@ object SettingsBackupScreen : SearchableSettings {
                 BackupConst.BACKUP_CHAPTER to R.string.chapters,
                 BackupConst.BACKUP_TRACK to R.string.track,
                 BackupConst.BACKUP_HISTORY to R.string.history,
+                BackupConst.BACKUP_APP_PREFS to R.string.app_settings,
             )
         }
         val flags = remember { choices.keys.toMutableStateList() }

+ 8 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt

@@ -4,11 +4,18 @@ package eu.kanade.tachiyomi.data.backup
 internal object BackupConst {
     const val BACKUP_CATEGORY = 0x1
     const val BACKUP_CATEGORY_MASK = 0x1
+
     const val BACKUP_CHAPTER = 0x2
     const val BACKUP_CHAPTER_MASK = 0x2
+
     const val BACKUP_HISTORY = 0x4
     const val BACKUP_HISTORY_MASK = 0x4
+
     const val BACKUP_TRACK = 0x8
     const val BACKUP_TRACK_MASK = 0x8
-    const val BACKUP_ALL = 0xF
+
+    const val BACKUP_APP_PREFS = 0x10
+    const val BACKUP_APP_PREFS_MASK = 0x10
+
+    const val BACKUP_ALL = 0x1F
 }

+ 34 - 3
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt

@@ -6,6 +6,8 @@ import android.net.Uri
 import com.hippo.unifile.UniFile
 import eu.kanade.domain.chapter.model.copyFrom
 import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_APP_PREFS
+import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_APP_PREFS_MASK
 import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
 import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
 import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER
@@ -18,8 +20,15 @@ import eu.kanade.tachiyomi.data.backup.models.Backup
 import eu.kanade.tachiyomi.data.backup.models.BackupCategory
 import eu.kanade.tachiyomi.data.backup.models.BackupHistory
 import eu.kanade.tachiyomi.data.backup.models.BackupManga
+import eu.kanade.tachiyomi.data.backup.models.BackupPreference
 import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
 import eu.kanade.tachiyomi.data.backup.models.BackupSource
+import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
 import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper
 import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper
 import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper
@@ -30,6 +39,7 @@ import logcat.LogPriority
 import okio.buffer
 import okio.gzip
 import okio.sink
+import tachiyomi.core.preference.PreferenceStore
 import tachiyomi.core.util.system.logcat
 import tachiyomi.data.DatabaseHandler
 import tachiyomi.data.Manga_sync
@@ -61,6 +71,7 @@ class BackupManager(
     private val getCategories: GetCategories = Injekt.get()
     private val getFavorites: GetFavorites = Injekt.get()
     private val getHistory: GetHistory = Injekt.get()
+    private val preferenceStore: PreferenceStore = Injekt.get()
 
     internal val parser = ProtoBuf
 
@@ -81,6 +92,7 @@ class BackupManager(
             backupCategories(flags),
             emptyList(),
             prepExtensionInfoForSync(databaseManga),
+            backupAppPreferences(flags),
         )
 
         var file: UniFile? = null
@@ -133,7 +145,7 @@ class BackupManager(
         }
     }
 
-    fun prepExtensionInfoForSync(mangas: List<Manga>): List<BackupSource> {
+    private fun prepExtensionInfoForSync(mangas: List<Manga>): List<BackupSource> {
         return mangas
             .asSequence()
             .map(Manga::source)
@@ -148,7 +160,7 @@ class BackupManager(
      *
      * @return list of [BackupCategory] to be backed up
      */
-    suspend fun backupCategories(options: Int): List<BackupCategory> {
+    private suspend fun backupCategories(options: Int): List<BackupCategory> {
         // Check if user wants category information in backup
         return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
             getCategories.await()
@@ -159,7 +171,7 @@ class BackupManager(
         }
     }
 
-    suspend fun backupMangas(mangas: List<Manga>, flags: Int): List<BackupManga> {
+    private suspend fun backupMangas(mangas: List<Manga>, flags: Int): List<BackupManga> {
         return mangas.map {
             backupManga(it, flags)
         }
@@ -219,6 +231,25 @@ class BackupManager(
         return mangaObject
     }
 
+    @Suppress("UNCHECKED_CAST")
+    private fun backupAppPreferences(flags: Int): List<BackupPreference> {
+        if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList()
+
+        return preferenceStore.getAll().mapNotNull { (key, value) ->
+            when (value) {
+                is Int -> BackupPreference(key, IntPreferenceValue(value))
+                is Long -> BackupPreference(key, LongPreferenceValue(value))
+                is Float -> BackupPreference(key, FloatPreferenceValue(value))
+                is String -> BackupPreference(key, StringPreferenceValue(value))
+                is Boolean -> BackupPreference(key, BooleanPreferenceValue(value))
+                is Set<*> -> (value as? Set<String>)?.let {
+                    BackupPreference(key, StringSetPreferenceValue(it))
+                }
+                else -> null
+            }
+        }
+    }
+
     internal suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas): Manga {
         var updatedManga = manga.copy(id = dbManga._id)
         updatedManga = updatedManga.copyFrom(dbManga)

+ 51 - 2
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt

@@ -7,13 +7,20 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.models.BackupCategory
 import eu.kanade.tachiyomi.data.backup.models.BackupHistory
 import eu.kanade.tachiyomi.data.backup.models.BackupManga
+import eu.kanade.tachiyomi.data.backup.models.BackupPreference
 import eu.kanade.tachiyomi.data.backup.models.BackupSource
+import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue
+import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue
 import eu.kanade.tachiyomi.util.BackupUtil
 import eu.kanade.tachiyomi.util.system.createFileInCacheDir
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.isActive
+import tachiyomi.core.preference.PreferenceStore
 import tachiyomi.domain.chapter.model.Chapter
-import tachiyomi.domain.chapter.repository.ChapterRepository
 import tachiyomi.domain.manga.interactor.FetchInterval
 import tachiyomi.domain.manga.model.Manga
 import tachiyomi.domain.track.model.Track
@@ -30,8 +37,8 @@ class BackupRestorer(
     private val notifier: BackupNotifier,
 ) {
     private val updateManga: UpdateManga = Injekt.get()
-    private val chapterRepository: ChapterRepository = Injekt.get()
     private val fetchInterval: FetchInterval = Injekt.get()
+    private val preferenceStore: PreferenceStore = Injekt.get()
 
     private var now = ZonedDateTime.now()
     private var currentFetchWindow = fetchInterval.getWindow(now)
@@ -106,6 +113,8 @@ class BackupRestorer(
         currentFetchWindow = fetchInterval.getWindow(now)
 
         return coroutineScope {
+            restoreAppPreferences(backup.backupPreferences)
+
             // Restore individual manga
             backup.backupManga.forEach {
                 if (!isActive) {
@@ -115,6 +124,7 @@ class BackupRestorer(
                 restoreManga(it, backup.backupCategories, sync)
             }
             // TODO: optionally trigger online library + tracker update
+
             true
         }
     }
@@ -200,6 +210,45 @@ class BackupRestorer(
         backupManager.restoreTracking(manga, tracks)
     }
 
+    private fun restoreAppPreferences(preferences: List<BackupPreference>) {
+        val prefs = preferenceStore.getAll()
+
+        preferences.forEach { (key, value) ->
+            when (value) {
+                is IntPreferenceValue -> {
+                    if (prefs[key] is Int?) {
+                        preferenceStore.getInt(key).set(value.value)
+                    }
+                }
+                is LongPreferenceValue -> {
+                    if (prefs[key] is Long?) {
+                        preferenceStore.getLong(key).set(value.value)
+                    }
+                }
+                is FloatPreferenceValue -> {
+                    if (prefs[key] is Float?) {
+                        preferenceStore.getFloat(key).set(value.value)
+                    }
+                }
+                is StringPreferenceValue -> {
+                    if (prefs[key] is String?) {
+                        preferenceStore.getString(key).set(value.value)
+                    }
+                }
+                is BooleanPreferenceValue -> {
+                    if (prefs[key] is Boolean?) {
+                        preferenceStore.getBoolean(key).set(value.value)
+                    }
+                }
+                is StringSetPreferenceValue -> {
+                    if (prefs[key] is Set<*>?) {
+                        preferenceStore.getStringSet(key).set(value.value)
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Called to update dialog in [BackupConst]
      *

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt

@@ -11,9 +11,9 @@ import java.util.Locale
 data class Backup(
     @ProtoNumber(1) val backupManga: List<BackupManga>,
     @ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
-    // Bump by 100 to specify this is a 0.x value
     @ProtoNumber(100) var backupBrokenSources: List<BrokenBackupSource> = emptyList(),
     @ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
+    @ProtoNumber(104) var backupPreferences: List<BackupPreference> = emptyList(),
 ) {
 
     companion object {

+ 0 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt

@@ -9,7 +9,6 @@ class BackupCategory(
     @ProtoNumber(1) var name: String,
     @ProtoNumber(2) var order: Long = 0,
     // @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
-    // Bump by 100 to specify this is a 0.x value
     @ProtoNumber(100) var flags: Long = 0,
 ) {
     fun getCategory(): Category {

+ 31 - 0
app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt

@@ -0,0 +1,31 @@
+package eu.kanade.tachiyomi.data.backup.models
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.protobuf.ProtoNumber
+
+@Serializable
+data class BackupPreference(
+    @ProtoNumber(1) val key: String,
+    @ProtoNumber(2) val value: PreferenceValue,
+)
+
+@Serializable
+sealed class PreferenceValue
+
+@Serializable
+data class IntPreferenceValue(val value: Int) : PreferenceValue()
+
+@Serializable
+data class LongPreferenceValue(val value: Long) : PreferenceValue()
+
+@Serializable
+data class FloatPreferenceValue(val value: Float) : PreferenceValue()
+
+@Serializable
+data class StringPreferenceValue(val value: String) : PreferenceValue()
+
+@Serializable
+data class BooleanPreferenceValue(val value: Boolean) : PreferenceValue()
+
+@Serializable
+data class StringSetPreferenceValue(val value: Set<String>) : PreferenceValue()

+ 8 - 1
core/src/main/java/tachiyomi/core/preference/AndroidPreference.kt

@@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
+import tachiyomi.core.util.system.logcat
 
 sealed class AndroidPreference<T>(
     private val preferences: SharedPreferences,
@@ -29,7 +30,13 @@ sealed class AndroidPreference<T>(
     }
 
     override fun get(): T {
-        return read(preferences, key, defaultValue)
+        return try {
+            read(preferences, key, defaultValue)
+        } catch (e: ClassCastException) {
+            logcat { "Invalid value for $key; deleting" }
+            delete()
+            defaultValue
+        }
     }
 
     override fun set(value: T) {

+ 4 - 0
core/src/main/java/tachiyomi/core/preference/AndroidPreferenceStore.kt

@@ -60,6 +60,10 @@ class AndroidPreferenceStore(
             deserializer = deserializer,
         )
     }
+
+    override fun getAll(): Map<String, *> {
+        return sharedPreferences.all ?: emptyMap<String, Any>()
+    }
 }
 
 private val SharedPreferences.keyFlow

+ 2 - 0
core/src/main/java/tachiyomi/core/preference/PreferenceStore.kt

@@ -20,6 +20,8 @@ interface PreferenceStore {
         serializer: (T) -> String,
         deserializer: (String) -> T,
     ): Preference<T>
+
+    fun getAll(): Map<String, *>
 }
 
 inline fun <reified T : Enum<T>> PreferenceStore.getEnum(

+ 1 - 0
i18n/src/main/res/values/strings.xml

@@ -493,6 +493,7 @@
     </plurals>
     <string name="backup_in_progress">Backup is already in progress</string>
     <string name="backup_choice">What do you want to backup?</string>
+    <string name="app_settings">App settings</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>