diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ba9e282c..49f10e4a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 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 backup/restore of category related preferences ([@cuong-tran](https://github.com/cuong-tran)) ([#1726](https://github.com/mihonapp/mihon/pull/1726)) ### 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)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt index df517e8ed..942b307c6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt @@ -8,6 +8,7 @@ import tachiyomi.domain.category.model.Category class BackupCategory( @ProtoNumber(1) var name: String, @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(100) var flags: Long = 0, ) { @@ -21,6 +22,7 @@ class BackupCategory( val backupCategoryMapper = { category: Category -> BackupCategory( + id = category.id, name = category.name, order = category.order, flags = category.flags, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index 43d7c3718..8f83247fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -91,7 +91,7 @@ class BackupRestorer( restoreCategories(backup.backupCategories) } if (options.appSettings) { - restoreAppPreferences(backup.backupPreferences) + restoreAppPreferences(backup.backupPreferences, backup.backupCategories.takeIf { options.categories }) } if (options.sourceSettings) { restoreSourcePreferences(backup.backupSourcePreferences) @@ -140,9 +140,15 @@ class BackupRestorer( } } - private fun CoroutineScope.restoreAppPreferences(preferences: List) = launch { + private fun CoroutineScope.restoreAppPreferences( + preferences: List, + categories: List?, + ) = launch { ensureActive() - preferenceRestorer.restoreApp(preferences) + preferenceRestorer.restoreApp( + preferences, + categories, + ) restoreProgress += 1 notifier.showRestoreProgress( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt index b70280c55..0417a6963 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceRestorer.kt @@ -1,7 +1,9 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers import android.content.Context +import android.util.Log 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.BackupSourcePreferences 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 tachiyomi.core.common.preference.AndroidPreferenceStore 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.api.get class PreferenceRestorer( private val context: Context, + private val getCategories: GetCategories = Injekt.get(), private val preferenceStore: PreferenceStore = Injekt.get(), ) { - - fun restoreApp(preferences: List) { - restorePreferences(preferences, preferenceStore) + suspend fun restoreApp( + preferences: List, + backupCategories: List?, + ) { + restorePreferences( + preferences, + preferenceStore, + backupCategories, + ) LibraryUpdateJob.setupTask(context) BackupCreateJob.setupTask(context) } - fun restoreSource(preferences: List) { + suspend fun restoreSource(preferences: List) { preferences.forEach { val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey)) restorePreferences(it.prefs, sourcePrefs) } } - private fun restorePreferences( + private suspend fun restorePreferences( toRestore: List, preferenceStore: PreferenceStore, + backupCategories: List? = 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() toRestore.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) + try { + when (value) { + is IntPreferenceValue -> { + if (prefs[key] is Int?) { + val newValue = if (key == LibraryPreferences.DEFAULT_CATEGORY_PREF_KEY) { + backupCategoriesById[value.value.toString()] + ?.let { categoriesByName[it.name]?.id?.toInt() } + } else { + value.value + } + + newValue?.let { preferenceStore.getInt(key).set(it) } + } + } + 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<*>?) { + 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, + preferenceStore: PreferenceStore, + backupCategoriesById: Map, + categoriesByName: Map, + ): 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 + } } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/preference/Preference.kt b/core/common/src/main/kotlin/tachiyomi/core/common/preference/Preference.kt index f75384491..e3ac0f1d3 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/preference/Preference.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/preference/Preference.kt @@ -57,6 +57,10 @@ operator fun Preference>.plusAssign(item: T) { set(get() + item) } +operator fun Preference>.plusAssign(items: Iterable) { + set(get() + items) +} + operator fun Preference>.minusAssign(item: T) { set(get() - item) } diff --git a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt index d7d523f84..7d24c181a 100644 --- a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt @@ -26,22 +26,25 @@ class DownloadPreferences( fun removeBookmarkedChapters() = preferenceStore.getBoolean("pref_remove_bookmarked", false) - fun removeExcludeCategories() = preferenceStore.getStringSet( - "remove_exclude_categories", - emptySet(), - ) + fun removeExcludeCategories() = preferenceStore.getStringSet(REMOVE_EXCLUDE_CATEGORIES_PREF_KEY, emptySet()) fun downloadNewChapters() = preferenceStore.getBoolean("download_new", false) - fun downloadNewChapterCategories() = preferenceStore.getStringSet( - "download_new_categories", - emptySet(), - ) + fun downloadNewChapterCategories() = preferenceStore.getStringSet(DOWNLOAD_NEW_CATEGORIES_PREF_KEY, emptySet()) - fun downloadNewChapterCategoriesExclude() = preferenceStore.getStringSet( - "download_new_categories_exclude", - emptySet(), - ) + fun downloadNewChapterCategoriesExclude() = + preferenceStore.getStringSet(DOWNLOAD_NEW_CATEGORIES_EXCLUDE_PREF_KEY, emptySet()) 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, + ) + } } diff --git a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt index 5a9d86182..68b4c7ae4 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -109,7 +109,7 @@ class LibraryPreferences( // 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) @@ -119,12 +119,9 @@ class LibraryPreferences( 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( - "library_update_categories_exclude", - emptySet(), - ) + fun updateCategoriesExclude() = preferenceStore.getStringSet(LIBRARY_UPDATE_CATEGORIES_EXCLUDE_PREF_KEY, emptySet()) // endregion @@ -206,5 +203,14 @@ class LibraryPreferences( const val MANGA_HAS_UNREAD = "manga_fully_read" const val MANGA_NON_READ = "manga_started" 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, + ) } }