Fix backup/restore of category related preferences (#1726)

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
Cuong-Tran 2025-02-25 05:04:39 +07:00 committed by GitHub
parent 1a5b4c2804
commit e1724d1aa0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 135 additions and 55 deletions

View File

@ -25,6 +25,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Fix Bangumi and MAL tracking 401 errors due to Mihon sending expired credentials ([@MajorTanya](https://github.com/MajorTanya)) ([#1681](https://github.com/mihonapp/mihon/pull/1681), [#1682](https://github.com/mihonapp/mihon/pull/1682)) - Fix Bangumi and MAL tracking 401 errors due to Mihon sending expired credentials ([@MajorTanya](https://github.com/MajorTanya)) ([#1681](https://github.com/mihonapp/mihon/pull/1681), [#1682](https://github.com/mihonapp/mihon/pull/1682))
- Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup ([@MajorTanya](https://github.com/MajorTanya)) ([#1684](https://github.com/mihonapp/mihon/pull/1684)) - Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup ([@MajorTanya](https://github.com/MajorTanya)) ([#1684](https://github.com/mihonapp/mihon/pull/1684))
- Fix App's preferences referencing deleted categories ([@cuong-tran](https://github.com/cuong-tran)) ([#1734](https://github.com/mihonapp/mihon/pull/1734)) - Fix App's preferences referencing deleted categories ([@cuong-tran](https://github.com/cuong-tran)) ([#1734](https://github.com/mihonapp/mihon/pull/1734))
- Fix backup/restore of category related preferences ([@cuong-tran](https://github.com/cuong-tran)) ([#1726](https://github.com/mihonapp/mihon/pull/1726))
### Other ### Other
- Add zoned "Current time" to debug info and include year & timezone in logcat output ([@MajorTanya](https://github.com/MajorTanya)) ([#1672](https://github.com/mihonapp/mihon/pull/1672)) - Add zoned "Current time" to debug info and include year & timezone in logcat output ([@MajorTanya](https://github.com/MajorTanya)) ([#1672](https://github.com/mihonapp/mihon/pull/1672))

View File

@ -8,6 +8,7 @@ import tachiyomi.domain.category.model.Category
class BackupCategory( class BackupCategory(
@ProtoNumber(1) var name: String, @ProtoNumber(1) var name: String,
@ProtoNumber(2) var order: Long = 0, @ProtoNumber(2) var order: Long = 0,
@ProtoNumber(3) var id: Long = 0,
// @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x // @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
@ProtoNumber(100) var flags: Long = 0, @ProtoNumber(100) var flags: Long = 0,
) { ) {
@ -21,6 +22,7 @@ class BackupCategory(
val backupCategoryMapper = { category: Category -> val backupCategoryMapper = { category: Category ->
BackupCategory( BackupCategory(
id = category.id,
name = category.name, name = category.name,
order = category.order, order = category.order,
flags = category.flags, flags = category.flags,

View File

@ -91,7 +91,7 @@ class BackupRestorer(
restoreCategories(backup.backupCategories) restoreCategories(backup.backupCategories)
} }
if (options.appSettings) { if (options.appSettings) {
restoreAppPreferences(backup.backupPreferences) restoreAppPreferences(backup.backupPreferences, backup.backupCategories.takeIf { options.categories })
} }
if (options.sourceSettings) { if (options.sourceSettings) {
restoreSourcePreferences(backup.backupSourcePreferences) restoreSourcePreferences(backup.backupSourcePreferences)
@ -140,9 +140,15 @@ class BackupRestorer(
} }
} }
private fun CoroutineScope.restoreAppPreferences(preferences: List<BackupPreference>) = launch { private fun CoroutineScope.restoreAppPreferences(
preferences: List<BackupPreference>,
categories: List<BackupCategory>?,
) = launch {
ensureActive() ensureActive()
preferenceRestorer.restoreApp(preferences) preferenceRestorer.restoreApp(
preferences,
categories,
)
restoreProgress += 1 restoreProgress += 1
notifier.showRestoreProgress( notifier.showRestoreProgress(

View File

@ -1,7 +1,9 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers package eu.kanade.tachiyomi.data.backup.restore.restorers
import android.content.Context import android.content.Context
import android.util.Log
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue
@ -14,66 +16,122 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.source.sourcePreferences import eu.kanade.tachiyomi.source.sourcePreferences
import tachiyomi.core.common.preference.AndroidPreferenceStore import tachiyomi.core.common.preference.AndroidPreferenceStore
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.plusAssign
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.service.LibraryPreferences
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class PreferenceRestorer( class PreferenceRestorer(
private val context: Context, private val context: Context,
private val getCategories: GetCategories = Injekt.get(),
private val preferenceStore: PreferenceStore = Injekt.get(), private val preferenceStore: PreferenceStore = Injekt.get(),
) { ) {
suspend fun restoreApp(
fun restoreApp(preferences: List<BackupPreference>) { preferences: List<BackupPreference>,
restorePreferences(preferences, preferenceStore) backupCategories: List<BackupCategory>?,
) {
restorePreferences(
preferences,
preferenceStore,
backupCategories,
)
LibraryUpdateJob.setupTask(context) LibraryUpdateJob.setupTask(context)
BackupCreateJob.setupTask(context) BackupCreateJob.setupTask(context)
} }
fun restoreSource(preferences: List<BackupSourcePreferences>) { suspend fun restoreSource(preferences: List<BackupSourcePreferences>) {
preferences.forEach { preferences.forEach {
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey)) val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
restorePreferences(it.prefs, sourcePrefs) restorePreferences(it.prefs, sourcePrefs)
} }
} }
private fun restorePreferences( private suspend fun restorePreferences(
toRestore: List<BackupPreference>, toRestore: List<BackupPreference>,
preferenceStore: PreferenceStore, preferenceStore: PreferenceStore,
backupCategories: List<BackupCategory>? = null,
) { ) {
val allCategories = if (backupCategories != null) getCategories.await() else emptyList()
val categoriesByName = allCategories.associateBy { it.name }
val backupCategoriesById = backupCategories?.associateBy { it.id.toString() }.orEmpty()
val prefs = preferenceStore.getAll() val prefs = preferenceStore.getAll()
toRestore.forEach { (key, value) -> toRestore.forEach { (key, value) ->
when (value) { try {
is IntPreferenceValue -> { when (value) {
if (prefs[key] is Int?) { is IntPreferenceValue -> {
preferenceStore.getInt(key).set(value.value) if (prefs[key] is Int?) {
} val newValue = if (key == LibraryPreferences.DEFAULT_CATEGORY_PREF_KEY) {
} backupCategoriesById[value.value.toString()]
is LongPreferenceValue -> { ?.let { categoriesByName[it.name]?.id?.toInt() }
if (prefs[key] is Long?) { } else {
preferenceStore.getLong(key).set(value.value) value.value
} }
}
is FloatPreferenceValue -> { newValue?.let { preferenceStore.getInt(key).set(it) }
if (prefs[key] is Float?) { }
preferenceStore.getFloat(key).set(value.value) }
} is LongPreferenceValue -> {
} if (prefs[key] is Long?) {
is StringPreferenceValue -> { preferenceStore.getLong(key).set(value.value)
if (prefs[key] is String?) { }
preferenceStore.getString(key).set(value.value) }
} is FloatPreferenceValue -> {
} if (prefs[key] is Float?) {
is BooleanPreferenceValue -> { preferenceStore.getFloat(key).set(value.value)
if (prefs[key] is Boolean?) { }
preferenceStore.getBoolean(key).set(value.value) }
} is StringPreferenceValue -> {
} if (prefs[key] is String?) {
is StringSetPreferenceValue -> { preferenceStore.getString(key).set(value.value)
if (prefs[key] is Set<*>?) { }
preferenceStore.getStringSet(key).set(value.value) }
is BooleanPreferenceValue -> {
if (prefs[key] is Boolean?) {
preferenceStore.getBoolean(key).set(value.value)
}
}
is StringSetPreferenceValue -> {
if (prefs[key] is Set<*>?) {
val restored = restoreCategoriesPreference(
key,
value.value,
preferenceStore,
backupCategoriesById,
categoriesByName,
)
if (!restored) preferenceStore.getStringSet(key).set(value.value)
}
} }
} }
} catch (e: Exception) {
Log.e("PreferenceRestorer", "Failed to restore preference <$key>", e)
} }
} }
} }
private fun restoreCategoriesPreference(
key: String,
value: Set<String>,
preferenceStore: PreferenceStore,
backupCategoriesById: Map<String, BackupCategory>,
categoriesByName: Map<String, Category>,
): Boolean {
val categoryPreferences = LibraryPreferences.categoryPreferenceKeys + DownloadPreferences.categoryPreferenceKeys
if (key !in categoryPreferences) return false
val ids = value.mapNotNull {
backupCategoriesById[it]?.name?.let { name ->
categoriesByName[name]?.id?.toString()
}
}
if (ids.isNotEmpty()) {
preferenceStore.getStringSet(key) += ids
}
return true
}
} }

View File

@ -57,6 +57,10 @@ operator fun <T> Preference<Set<T>>.plusAssign(item: T) {
set(get() + item) set(get() + item)
} }
operator fun <T> Preference<Set<T>>.plusAssign(items: Iterable<T>) {
set(get() + items)
}
operator fun <T> Preference<Set<T>>.minusAssign(item: T) { operator fun <T> Preference<Set<T>>.minusAssign(item: T) {
set(get() - item) set(get() - item)
} }

View File

@ -26,22 +26,25 @@ class DownloadPreferences(
fun removeBookmarkedChapters() = preferenceStore.getBoolean("pref_remove_bookmarked", false) fun removeBookmarkedChapters() = preferenceStore.getBoolean("pref_remove_bookmarked", false)
fun removeExcludeCategories() = preferenceStore.getStringSet( fun removeExcludeCategories() = preferenceStore.getStringSet(REMOVE_EXCLUDE_CATEGORIES_PREF_KEY, emptySet())
"remove_exclude_categories",
emptySet(),
)
fun downloadNewChapters() = preferenceStore.getBoolean("download_new", false) fun downloadNewChapters() = preferenceStore.getBoolean("download_new", false)
fun downloadNewChapterCategories() = preferenceStore.getStringSet( fun downloadNewChapterCategories() = preferenceStore.getStringSet(DOWNLOAD_NEW_CATEGORIES_PREF_KEY, emptySet())
"download_new_categories",
emptySet(),
)
fun downloadNewChapterCategoriesExclude() = preferenceStore.getStringSet( fun downloadNewChapterCategoriesExclude() =
"download_new_categories_exclude", preferenceStore.getStringSet(DOWNLOAD_NEW_CATEGORIES_EXCLUDE_PREF_KEY, emptySet())
emptySet(),
)
fun downloadNewUnreadChaptersOnly() = preferenceStore.getBoolean("download_new_unread_chapters_only", false) fun downloadNewUnreadChaptersOnly() = preferenceStore.getBoolean("download_new_unread_chapters_only", false)
companion object {
private const val REMOVE_EXCLUDE_CATEGORIES_PREF_KEY = "remove_exclude_categories"
private const val DOWNLOAD_NEW_CATEGORIES_PREF_KEY = "download_new_categories"
private const val DOWNLOAD_NEW_CATEGORIES_EXCLUDE_PREF_KEY = "download_new_categories_exclude"
val categoryPreferenceKeys = setOf(
REMOVE_EXCLUDE_CATEGORIES_PREF_KEY,
DOWNLOAD_NEW_CATEGORIES_PREF_KEY,
DOWNLOAD_NEW_CATEGORIES_EXCLUDE_PREF_KEY,
)
}
} }

View File

@ -109,7 +109,7 @@ class LibraryPreferences(
// region Category // region Category
fun defaultCategory() = preferenceStore.getInt("default_category", -1) fun defaultCategory() = preferenceStore.getInt(DEFAULT_CATEGORY_PREF_KEY, -1)
fun lastUsedCategory() = preferenceStore.getInt(Preference.appStateKey("last_used_category"), 0) fun lastUsedCategory() = preferenceStore.getInt(Preference.appStateKey("last_used_category"), 0)
@ -119,12 +119,9 @@ class LibraryPreferences(
fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false) fun categorizedDisplaySettings() = preferenceStore.getBoolean("categorized_display", false)
fun updateCategories() = preferenceStore.getStringSet("library_update_categories", emptySet()) fun updateCategories() = preferenceStore.getStringSet(LIBRARY_UPDATE_CATEGORIES_PREF_KEY, emptySet())
fun updateCategoriesExclude() = preferenceStore.getStringSet( fun updateCategoriesExclude() = preferenceStore.getStringSet(LIBRARY_UPDATE_CATEGORIES_EXCLUDE_PREF_KEY, emptySet())
"library_update_categories_exclude",
emptySet(),
)
// endregion // endregion
@ -206,5 +203,14 @@ class LibraryPreferences(
const val MANGA_HAS_UNREAD = "manga_fully_read" const val MANGA_HAS_UNREAD = "manga_fully_read"
const val MANGA_NON_READ = "manga_started" const val MANGA_NON_READ = "manga_started"
const val MANGA_OUTSIDE_RELEASE_PERIOD = "manga_outside_release_period" const val MANGA_OUTSIDE_RELEASE_PERIOD = "manga_outside_release_period"
const val DEFAULT_CATEGORY_PREF_KEY = "default_category"
private const val LIBRARY_UPDATE_CATEGORIES_PREF_KEY = "library_update_categories"
private const val LIBRARY_UPDATE_CATEGORIES_EXCLUDE_PREF_KEY = "library_update_categories_exclude"
val categoryPreferenceKeys = setOf(
DEFAULT_CATEGORY_PREF_KEY,
LIBRARY_UPDATE_CATEGORIES_PREF_KEY,
LIBRARY_UPDATE_CATEGORIES_EXCLUDE_PREF_KEY,
)
} }
} }