Add app settings to backups
This should be compatible with Aniyomi's implementation. Related to #1857 Co-authored-by: jmir1 <jmir1@users.noreply.github.com>
This commit is contained in:
parent
9c688b08c0
commit
72024aa44a
11 changed files with 141 additions and 9 deletions
|
@ -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() }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
*
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
|
@ -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) {
|
||||
|
|
|
@ -60,6 +60,10 @@ class AndroidPreferenceStore(
|
|||
deserializer = deserializer,
|
||||
)
|
||||
}
|
||||
|
||||
override fun getAll(): Map<String, *> {
|
||||
return sharedPreferences.all ?: emptyMap<String, Any>()
|
||||
}
|
||||
}
|
||||
|
||||
private val SharedPreferences.keyFlow
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
|
Reference in a new issue