diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index c4e7c7d275..c060eb1d1a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -6,50 +6,17 @@ import co.touchlab.kermit.Logger import com.hippo.unifile.UniFile import dev.yokai.domain.storage.StorageManager import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.core.preference.Preference -import eu.kanade.tachiyomi.core.preference.PreferenceStore -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 -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER_MASK -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO_MASK -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA_MASK -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_SOURCE_PREFS -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_SOURCE_PREFS_MASK -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK -import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.BackupFileValidator +import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator +import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator +import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator +import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator import eu.kanade.tachiyomi.data.backup.models.Backup -import eu.kanade.tachiyomi.data.backup.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.models.BackupChapter -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.BackupSourcePreferences -import eu.kanade.tachiyomi.data.backup.models.BackupTracking -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.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.source.ConfigurableSource -import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.source.preferenceKey -import eu.kanade.tachiyomi.source.sourcePreferences -import eu.kanade.tachiyomi.ui.library.LibrarySort import eu.kanade.tachiyomi.util.system.e import kotlinx.serialization.protobuf.ProtoBuf import okio.buffer @@ -60,15 +27,18 @@ import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.io.FileOutputStream -class BackupCreator(val context: Context) { +class BackupCreator( + val context: Context, + private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(), + private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(), + private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(), + private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(), +) { - private val preferenceStore: PreferenceStore = Injekt.get() val parser = ProtoBuf private val db: DatabaseHelper = Injekt.get() - private val sourceManager: SourceManager = Injekt.get() private val preferences: PreferencesHelper = Injekt.get() - private val customMangaManager: CustomMangaManager = Injekt.get() - internal val storageManager: StorageManager by injectLazy() + private val storageManager: StorageManager by injectLazy() /** * Create backup Json file from database @@ -89,12 +59,12 @@ class BackupCreator(val context: Context) { } backup = Backup( - backupMangas(databaseManga, flags), - backupCategories(), + mangaBackupCreator.backupMangas(databaseManga, flags), + categoriesBackupCreator.backupCategories(), emptyList(), - backupExtensionInfo(databaseManga), - backupAppPreferences(flags), - backupSourcePreferences(flags), + sourcesBackupCreator.backupExtensionInfo(databaseManga), + preferenceBackupCreator.backupAppPreferences(flags), + preferenceBackupCreator.backupSourcePreferences(flags), ) } @@ -146,126 +116,4 @@ class BackupCreator(val context: Context) { throw e } } - - private fun backupMangas(mangas: List, flags: Int): List { - return mangas.map { - backupManga(it, flags) - } - } - - private fun backupExtensionInfo(mangas: List): List { - return mangas - .asSequence() - .map { it.source } - .distinct() - .map { sourceManager.getOrStub(it) } - .map { BackupSource.copyFrom(it) } - .toList() - } - - /** - * Backup the categories of library - * - * @return list of [BackupCategory] to be backed up - */ - private fun backupCategories(): List { - return db.getCategories() - .executeAsBlocking() - .map { BackupCategory.copyFrom(it) } - } - - /** - * Convert a manga to Json - * - * @param manga manga that gets converted - * @param options options for the backup - * @return [BackupManga] containing manga in a serializable form - */ - private fun backupManga(manga: Manga, options: Int): BackupManga { - // Entry for this manga - val mangaObject = BackupManga.copyFrom(manga, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null) - - // Check if user wants chapter information in backup - if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) { - // Backup all the chapters - val chapters = db.getChapters(manga).executeAsBlocking() - if (chapters.isNotEmpty()) { - mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) } - } - } - - // Check if user wants category information in backup - if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { - // Backup categories for this manga - val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking() - if (categoriesForManga.isNotEmpty()) { - mangaObject.categories = categoriesForManga.mapNotNull { it.order } - } - } - - // Check if user wants track information in backup - if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) { - val tracks = db.getTracks(manga).executeAsBlocking() - if (tracks.isNotEmpty()) { - mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) } - } - } - - // Check if user wants history information in backup - if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) { - val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking() - if (historyForManga.isNotEmpty()) { - val history = historyForManga.mapNotNull { history -> - val url = db.getChapter(history.chapter_id).executeAsBlocking()?.url - url?.let { BackupHistory(url, history.last_read, history.time_read) } - } - if (history.isNotEmpty()) { - mangaObject.history = history - } - } - } - - return mangaObject - } - - private fun backupAppPreferences(flags: Int): List { - if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList() - return preferenceStore.getAll().toBackupPreferences() - } - - private fun backupSourcePreferences(flags: Int): List { - if (flags and BACKUP_SOURCE_PREFS_MASK != BACKUP_SOURCE_PREFS) return emptyList() - return sourceManager.getOnlineSources() - .filterIsInstance() - .map { - BackupSourcePreferences( - it.preferenceKey(), - it.sourcePreferences().all.toBackupPreferences(), - ) - } - } - - @Suppress("UNCHECKED_CAST") - private fun Map.toBackupPreferences(): List { - return this.filterKeys { !Preference.isPrivate(it) } - .mapNotNull { (key, value) -> - // j2k fork differences - if (key == "library_sorting_mode" && value is Int) { - val stringValue = (LibrarySort.valueOf(value) ?: LibrarySort.Title).serialize() - return@mapNotNull BackupPreference(key, StringPreferenceValue(stringValue)) - } - // end j2k fork differences - 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)?.let { - BackupPreference(key, StringSetPreferenceValue(it)) - } - else -> null - } - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt new file mode 100644 index 0000000000..b04d3a812d --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/CategoriesBackupCreator.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class CategoriesBackupCreator( + private val db: DatabaseHelper = Injekt.get(), +) { + /** + * Backup the categories of library + * + * @return list of [BackupCategory] to be backed up + */ + fun backupCategories(): List { + return db.getCategories() + .executeAsBlocking() + .map { BackupCategory.copyFrom(it) } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt new file mode 100644 index 0000000000..c113e3104f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -0,0 +1,86 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +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 +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER_MASK +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO_MASK +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY_MASK +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK +import eu.kanade.tachiyomi.data.backup.models.BackupChapter +import eu.kanade.tachiyomi.data.backup.models.BackupHistory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupTracking +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.library.CustomMangaManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class MangaBackupCreator( + private val db: DatabaseHelper = Injekt.get(), + private val customMangaManager: CustomMangaManager = Injekt.get(), +) { + fun backupMangas(mangas: List, flags: Int): List { + return mangas.map { + backupManga(it, flags) + } + } + + /** + * Convert a manga to Json + * + * @param manga manga that gets converted + * @param options options for the backup + * @return [BackupManga] containing manga in a serializable form + */ + private fun backupManga(manga: Manga, options: Int): BackupManga { + // Entry for this manga + val mangaObject = BackupManga.copyFrom(manga, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null) + + // Check if user wants chapter information in backup + if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) { + // Backup all the chapters + val chapters = db.getChapters(manga).executeAsBlocking() + if (chapters.isNotEmpty()) { + mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) } + } + } + + // Check if user wants category information in backup + if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) { + // Backup categories for this manga + val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking() + if (categoriesForManga.isNotEmpty()) { + mangaObject.categories = categoriesForManga.mapNotNull { it.order } + } + } + + // Check if user wants track information in backup + if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) { + val tracks = db.getTracks(manga).executeAsBlocking() + if (tracks.isNotEmpty()) { + mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) } + } + } + + // Check if user wants history information in backup + if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) { + val historyForManga = db.getHistoryByMangaId(manga.id!!).executeAsBlocking() + if (historyForManga.isNotEmpty()) { + val history = historyForManga.mapNotNull { history -> + val url = db.getChapter(history.chapter_id).executeAsBlocking()?.url + url?.let { BackupHistory(url, history.last_read, history.time_read) } + } + if (history.isNotEmpty()) { + mangaObject.history = history + } + } + } + + return mangaObject + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt new file mode 100644 index 0000000000..edc226d53e --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/PreferenceBackupCreator.kt @@ -0,0 +1,69 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.core.preference.Preference +import eu.kanade.tachiyomi.core.preference.PreferenceStore +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_SOURCE_PREFS +import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_SOURCE_PREFS_MASK +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences +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.source.ConfigurableSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.preferenceKey +import eu.kanade.tachiyomi.source.sourcePreferences +import eu.kanade.tachiyomi.ui.library.LibrarySort +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class PreferenceBackupCreator( + private val sourceManager: SourceManager = Injekt.get(), + private val preferenceStore: PreferenceStore = Injekt.get(), +) { + fun backupAppPreferences(flags: Int): List { + if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList() + return preferenceStore.getAll().toBackupPreferences() + } + + fun backupSourcePreferences(flags: Int): List { + if (flags and BACKUP_SOURCE_PREFS_MASK != BACKUP_SOURCE_PREFS) return emptyList() + return sourceManager.getOnlineSources() + .filterIsInstance() + .map { + BackupSourcePreferences( + it.preferenceKey(), + it.sourcePreferences().all.toBackupPreferences(), + ) + } + } + + @Suppress("UNCHECKED_CAST") + private fun Map.toBackupPreferences(): List { + return this.filterKeys { !Preference.isPrivate(it) } + .mapNotNull { (key, value) -> + // j2k fork differences + if (key == "library_sorting_mode" && value is Int) { + val stringValue = (LibrarySort.valueOf(value) ?: LibrarySort.Title).serialize() + return@mapNotNull BackupPreference(key, StringPreferenceValue(stringValue)) + } + // end j2k fork differences + 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)?.let { + BackupPreference(key, StringSetPreferenceValue(it)) + } + else -> null + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt new file mode 100644 index 0000000000..c944fd2fa9 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/SourcesBackupCreator.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.data.backup.create.creators + +import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.source.SourceManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class SourcesBackupCreator( + private val sourceManager: SourceManager = Injekt.get(), +) { + fun backupExtensionInfo(mangas: List): List { + return mangas + .asSequence() + .map { it.source } + .distinct() + .map { sourceManager.getOrStub(it) } + .map { BackupSource.copyFrom(it) } + .toList() + } +} 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 fc1e46b69d..3147d0dde4 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 @@ -2,59 +2,27 @@ package eu.kanade.tachiyomi.data.backup.restore import android.content.Context import android.net.Uri -import dev.yokai.domain.library.custom.model.CustomMangaInfo import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore -import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.data.backup.BackupNotifier -import eu.kanade.tachiyomi.data.backup.create.BackupCreatorJob -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.BackupSourcePreferences -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.database.DatabaseHelper -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.History -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.MangaCategory -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.library.CustomMangaManager -import eu.kanade.tachiyomi.data.library.LibraryUpdateJob -import eu.kanade.tachiyomi.extension.ExtensionUpdateJob -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.sourcePreferences -import eu.kanade.tachiyomi.ui.library.LibrarySort +import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesBackupRestorer +import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaBackupRestorer +import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceBackupRestorer import eu.kanade.tachiyomi.util.BackupUtil -import eu.kanade.tachiyomi.util.chapter.ChapterUtil -import eu.kanade.tachiyomi.util.manga.MangaUtil import eu.kanade.tachiyomi.util.system.createFileInCacheDir -import eu.kanade.tachiyomi.util.system.launchNow import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive -import kotlinx.serialization.protobuf.ProtoBuf -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy import java.io.File import java.text.SimpleDateFormat import java.util.* -import kotlin.math.max - -class BackupRestorer(val context: Context, val notifier: BackupNotifier) { - - private val db: DatabaseHelper by injectLazy() - private val customMangaManager: CustomMangaManager by injectLazy() - private val preferenceStore: PreferenceStore = Injekt.get() - private val parser = ProtoBuf +class BackupRestorer( + val context: Context, + val notifier: BackupNotifier, + private val categoriesBackupRestorer: CategoriesBackupRestorer = CategoriesBackupRestorer(), + private val mangaBackupRestorer: MangaBackupRestorer = MangaBackupRestorer(), + private val preferenceBackupRestorer: PreferenceBackupRestorer = PreferenceBackupRestorer(context), +) { private var restoreAmount = 0 private var restoreProgress = 0 @@ -88,18 +56,27 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) { restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs - // Restore categories - if (backup.backupCategories.isNotEmpty()) { - restoreCategories(backup.backupCategories) - } - // Store source mapping for error messages val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources sourceMapping = backupMaps.associate { it.sourceId to it.name } return coroutineScope { - restoreAppPreferences(backup.backupPreferences) - restoreSourcePreferences(backup.backupSourcePreferences) + // Restore categories + if (backup.backupCategories.isNotEmpty()) { + categoriesBackupRestorer.restoreCategories(backup.backupCategories) { + restoreProgress += 1 + showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories)) + } + } + + preferenceBackupRestorer.restoreAppPreferences(backup.backupPreferences) { + restoreProgress += 1 + showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.app_settings)) + } + preferenceBackupRestorer.restoreSourcePreferences(backup.backupSourcePreferences) { + restoreProgress += 1 + showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.source_settings)) + } // Restore individual manga backup.backupManga.forEach { @@ -107,349 +84,24 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) { return@coroutineScope false } - restoreManga(it, backup.backupCategories) + mangaBackupRestorer.restoreManga( + it, + backup.backupCategories, + onComplete = { manga -> + restoreProgress += 1 + showRestoreProgress(restoreProgress, restoreAmount, manga.title) + }, + onError = { manga, e -> + val sourceName = sourceMapping[manga.source] ?: manga.source.toString() + errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}") + }, + ) } true } // TODO: optionally trigger online library + tracker update } - private fun restoreCategories(backupCategories: List) { - db.inTransaction { - // Get categories from file and from db - val dbCategories = db.getCategories().executeAsBlocking() - - // Iterate over them - backupCategories.map { it.getCategoryImpl() }.forEach { category -> - // Used to know if the category is already in the db - var found = false - for (dbCategory in dbCategories) { - // If the category is already in the db, assign the id to the file's category - // and do nothing - if (category.name == dbCategory.name) { - category.id = dbCategory.id - found = true - break - } - } - // If the category isn't in the db, remove the id and insert a new category - // Store the inserted id in the category - if (!found) { - // Let the db assign the id - category.id = null - val result = db.insertCategory(category).executeAsBlocking() - category.id = result.insertedId()?.toInt() - } - } - } - - restoreProgress += 1 - showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories)) - } - - private fun restoreManga(backupManga: BackupManga, backupCategories: List) { - val manga = backupManga.getMangaImpl() - val chapters = backupManga.getChaptersImpl() - val categories = backupManga.categories - val history = - backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead, it.readDuration) } + backupManga.history - val tracks = backupManga.getTrackingImpl() - val customManga = backupManga.getCustomMangaInfo() - val filteredScanlators = backupManga.excludedScanlators - - try { - val dbManga = db.getManga(manga.url, manga.source).executeAsBlocking() - if (dbManga == null) { - // Manga not in database - restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga) - } else { - // Manga in database - // Copy information from manga already in database - manga.id = dbManga.id - manga.filtered_scanlators = dbManga.filtered_scanlators - manga.copyFrom(dbManga) - db.insertManga(manga).executeAsBlocking() - // Fetch rest of manga information - restoreNewManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga) - } - } catch (e: Exception) { - val sourceName = sourceMapping[manga.source] ?: manga.source.toString() - errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}") - } - - restoreProgress += 1 - showRestoreProgress(restoreProgress, restoreAmount, manga.title) - LibraryUpdateJob.updateMutableFlow.tryEmit(manga.id) - } - - /** - * Fetches manga information - * - * @param manga manga that needs updating - * @param chapters chapters of manga that needs updating - * @param categories categories that need updating - */ - private fun restoreExistingManga( - manga: Manga, - chapters: List, - categories: List, - history: List, - tracks: List, - backupCategories: List, - filteredScanlators: List, - customManga: CustomMangaInfo?, - ) { - val fetchedManga = manga.also { - it.initialized = it.description != null - it.id = db.insertManga(it).executeAsBlocking().insertedId() - } - fetchedManga.id ?: return - - restoreChapters(fetchedManga, chapters) - restoreExtras(fetchedManga, categories, history, tracks, backupCategories, filteredScanlators, customManga) - } - - private fun restoreNewManga( - backupManga: Manga, - chapters: List, - categories: List, - history: List, - tracks: List, - backupCategories: List, - filteredScanlators: List, - customManga: CustomMangaInfo?, - ) { - restoreChapters(backupManga, chapters) - restoreExtras(backupManga, categories, history, tracks, backupCategories, filteredScanlators, customManga) - } - - private fun restoreChapters(manga: Manga, chapters: List) { - val dbChapters = db.getChapters(manga).executeAsBlocking() - - chapters.forEach { chapter -> - val dbChapter = dbChapters.find { it.url == chapter.url } - if (dbChapter != null) { - chapter.id = dbChapter.id - chapter.copyFrom(dbChapter as SChapter) - if (dbChapter.read && !chapter.read) { - chapter.read = dbChapter.read - chapter.last_page_read = dbChapter.last_page_read - } else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0) { - chapter.last_page_read = dbChapter.last_page_read - } - if (!chapter.bookmark && dbChapter.bookmark) { - chapter.bookmark = dbChapter.bookmark - } - } - - chapter.manga_id = manga.id - } - - val newChapters = chapters.groupBy { it.id != null } - newChapters[true]?.let { db.updateKnownChaptersBackup(it).executeAsBlocking() } - newChapters[false]?.let { db.insertChapters(it).executeAsBlocking() } - } - - private fun restoreExtras( - manga: Manga, - categories: List, - history: List, - tracks: List, - backupCategories: List, - filteredScanlators: List, - customManga: CustomMangaInfo?, - ) { - restoreCategories(manga, categories, backupCategories) - restoreHistoryForManga(history) - restoreTrackForManga(manga, tracks) - restoreFilteredScanlatorsForManga(manga, filteredScanlators) - customManga?.let { - it.mangaId = manga.id!! - launchNow { - customMangaManager.saveMangaInfo(it) - } - } - } - - /** - * Restores the categories a manga is in. - * - * @param manga the manga whose categories have to be restored. - * @param categories the categories to restore. - */ - private fun restoreCategories(manga: Manga, categories: List, backupCategories: List) { - val dbCategories = db.getCategories().executeAsBlocking() - val mangaCategoriesToUpdate = ArrayList(categories.size) - categories.forEach { backupCategoryOrder -> - backupCategories.firstOrNull { - it.order == backupCategoryOrder - }?.let { backupCategory -> - dbCategories.firstOrNull { dbCategory -> - dbCategory.name == backupCategory.name - }?.let { dbCategory -> - mangaCategoriesToUpdate += MangaCategory.create(manga, dbCategory) - } - } - } - - // Update database - if (mangaCategoriesToUpdate.isNotEmpty()) { - db.deleteOldMangasCategories(listOf(manga)).executeAsBlocking() - db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking() - } - } - - /** - * Restore history from Json - * - * @param history list containing history to be restored - */ - internal fun restoreHistoryForManga(history: List) { - // List containing history to be updated - val historyToBeUpdated = ArrayList(history.size) - for ((url, lastRead, readDuration) in history) { - val dbHistory = db.getHistoryByChapterUrl(url).executeAsBlocking() - // Check if history already in database and update - if (dbHistory != null) { - dbHistory.apply { - last_read = max(lastRead, dbHistory.last_read) - time_read = max(readDuration, dbHistory.time_read) - } - historyToBeUpdated.add(dbHistory) - } else { - // If not in database create - db.getChapter(url).executeAsBlocking()?.let { - val historyToAdd = History.create(it).apply { - last_read = lastRead - time_read = readDuration - } - historyToBeUpdated.add(historyToAdd) - } - } - } - db.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() - } - - /** - * Restores the sync of a manga. - * - * @param manga the manga whose sync have to be restored. - * @param tracks the track list to restore. - */ - private fun restoreTrackForManga(manga: Manga, tracks: List) { - // Fix foreign keys with the current manga id - tracks.map { it.manga_id = manga.id!! } - - // Get tracks from database - val dbTracks = db.getTracks(manga).executeAsBlocking() - val trackToUpdate = mutableListOf() - - tracks.forEach { track -> - var isInDatabase = false - for (dbTrack in dbTracks) { - if (track.sync_id == dbTrack.sync_id) { - // The sync is already in the db, only update its fields - if (track.media_id != dbTrack.media_id) { - dbTrack.media_id = track.media_id - } - if (track.library_id != dbTrack.library_id) { - dbTrack.library_id = track.library_id - } - dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read) - isInDatabase = true - trackToUpdate.add(dbTrack) - break - } - } - if (!isInDatabase) { - // Insert new sync. Let the db assign the id - track.id = null - trackToUpdate.add(track) - } - } - // Update database - if (trackToUpdate.isNotEmpty()) { - db.insertTracks(trackToUpdate).executeAsBlocking() - } - } - - private fun restoreFilteredScanlatorsForManga(manga: Manga, filteredScanlators: List) { - val actualList = ChapterUtil.getScanlators(manga.filtered_scanlators) + filteredScanlators - MangaUtil.setScanlatorFilter(db, manga, actualList.toSet()) - } - - private fun restoreAppPreferences(preferences: List) { - restorePreferences(preferences, preferenceStore) - - ExtensionUpdateJob.setupTask(context) - LibraryUpdateJob.setupTask(context) - BackupCreatorJob.setupTask(context) - - restoreProgress += 1 - showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.app_settings)) - } - - private fun restoreSourcePreferences(preferences: List) { - preferences.forEach { - val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey)) - restorePreferences(it.prefs, sourcePrefs) - } - - restoreProgress += 1 - showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.source_settings)) - } - - private fun restorePreferences( - toRestore: List, - preferenceStore: PreferenceStore, - ) { - val prefs = preferenceStore.getAll() - toRestore.forEach { (key, value) -> - // j2k fork differences - if (key == "library_sorting_mode" && value is StringPreferenceValue && - prefs[key] is Int? - ) { - val intValue = LibrarySort.deserialize(value.value) - preferenceStore.getInt(key).set(intValue.mainValue) - return@forEach - } - // end j2k fork differences - - 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] * @@ -468,7 +120,7 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) { internal fun writeErrorLog(): File { try { if (errors.isNotEmpty()) { - val file = context.createFileInCacheDir("tachiyomi_restore.txt") + val file = context.createFileInCacheDir("yokai_restore.txt") val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()) file.bufferedWriter().use { out -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesBackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesBackupRestorer.kt new file mode 100644 index 0000000000..e48f0f98b2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesBackupRestorer.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.data.backup.restore.restorers + +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.database.DatabaseHelper +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class CategoriesBackupRestorer( + private val db: DatabaseHelper = Injekt.get(), +) { + @Suppress("RedundantSuspendModifier") + suspend fun restoreCategories(backupCategories: List, onComplete: () -> Unit) { + db.inTransaction { + // Get categories from file and from db + val dbCategories = db.getCategories().executeAsBlocking() + + // Iterate over them + backupCategories.map { it.getCategoryImpl() }.forEach { category -> + // Used to know if the category is already in the db + var found = false + for (dbCategory in dbCategories) { + // If the category is already in the db, assign the id to the file's category + // and do nothing + if (category.name == dbCategory.name) { + category.id = dbCategory.id + found = true + break + } + } + // If the category isn't in the db, remove the id and insert a new category + // Store the inserted id in the category + if (!found) { + // Let the db assign the id + category.id = null + val result = db.insertCategory(category).executeAsBlocking() + category.id = result.insertedId()?.toInt() + } + } + } + + onComplete() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaBackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaBackupRestorer.kt new file mode 100644 index 0000000000..bd4c679800 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaBackupRestorer.kt @@ -0,0 +1,260 @@ +package eu.kanade.tachiyomi.data.backup.restore.restorers + +import dev.yokai.domain.library.custom.model.CustomMangaInfo +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.database.DatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.database.models.MangaCategory +import eu.kanade.tachiyomi.data.database.models.Track +import eu.kanade.tachiyomi.data.library.CustomMangaManager +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.util.chapter.ChapterUtil +import eu.kanade.tachiyomi.util.manga.MangaUtil +import eu.kanade.tachiyomi.util.system.launchNow +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import kotlin.math.max + +class MangaBackupRestorer( + private val db: DatabaseHelper = Injekt.get(), + private val customMangaManager: CustomMangaManager = Injekt.get(), +) { + fun restoreManga( + backupManga: BackupManga, + backupCategories: List, + onComplete: (Manga) -> Unit, + onError: (Manga, Throwable) -> Unit, + ) { + val manga = backupManga.getMangaImpl() + val chapters = backupManga.getChaptersImpl() + val categories = backupManga.categories + val history = + backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead, it.readDuration) } + backupManga.history + val tracks = backupManga.getTrackingImpl() + val customManga = backupManga.getCustomMangaInfo() + val filteredScanlators = backupManga.excludedScanlators + + try { + val dbManga = db.getManga(manga.url, manga.source).executeAsBlocking() + if (dbManga == null) { + // Manga not in database + restoreExistingManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga) + } else { + // Manga in database + // Copy information from manga already in database + manga.id = dbManga.id + manga.filtered_scanlators = dbManga.filtered_scanlators + manga.copyFrom(dbManga) + db.insertManga(manga).executeAsBlocking() + // Fetch rest of manga information + restoreNewManga(manga, chapters, categories, history, tracks, backupCategories, filteredScanlators, customManga) + } + } catch (e: Exception) { + onError(manga, e) + } + + onComplete(manga) + LibraryUpdateJob.updateMutableFlow.tryEmit(manga.id) + } + + /** + * Fetches manga information + * + * @param manga manga that needs updating + * @param chapters chapters of manga that needs updating + * @param categories categories that need updating + */ + private fun restoreExistingManga( + manga: Manga, + chapters: List, + categories: List, + history: List, + tracks: List, + backupCategories: List, + filteredScanlators: List, + customManga: CustomMangaInfo?, + ) { + val fetchedManga = manga.also { + it.initialized = it.description != null + it.id = db.insertManga(it).executeAsBlocking().insertedId() + } + fetchedManga.id ?: return + + restoreChapters(fetchedManga, chapters) + restoreExtras(fetchedManga, categories, history, tracks, backupCategories, filteredScanlators, customManga) + } + + private fun restoreNewManga( + backupManga: Manga, + chapters: List, + categories: List, + history: List, + tracks: List, + backupCategories: List, + filteredScanlators: List, + customManga: CustomMangaInfo?, + ) { + restoreChapters(backupManga, chapters) + restoreExtras(backupManga, categories, history, tracks, backupCategories, filteredScanlators, customManga) + } + + private fun restoreChapters(manga: Manga, chapters: List) { + val dbChapters = db.getChapters(manga).executeAsBlocking() + + chapters.forEach { chapter -> + val dbChapter = dbChapters.find { it.url == chapter.url } + if (dbChapter != null) { + chapter.id = dbChapter.id + chapter.copyFrom(dbChapter as SChapter) + if (dbChapter.read && !chapter.read) { + chapter.read = dbChapter.read + chapter.last_page_read = dbChapter.last_page_read + } else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0) { + chapter.last_page_read = dbChapter.last_page_read + } + if (!chapter.bookmark && dbChapter.bookmark) { + chapter.bookmark = dbChapter.bookmark + } + } + + chapter.manga_id = manga.id + } + + val newChapters = chapters.groupBy { it.id != null } + newChapters[true]?.let { db.updateKnownChaptersBackup(it).executeAsBlocking() } + newChapters[false]?.let { db.insertChapters(it).executeAsBlocking() } + } + + private fun restoreExtras( + manga: Manga, + categories: List, + history: List, + tracks: List, + backupCategories: List, + filteredScanlators: List, + customManga: CustomMangaInfo?, + ) { + restoreCategories(manga, categories, backupCategories) + restoreHistoryForManga(history) + restoreTrackForManga(manga, tracks) + restoreFilteredScanlatorsForManga(manga, filteredScanlators) + customManga?.let { + it.mangaId = manga.id!! + launchNow { + customMangaManager.saveMangaInfo(it) + } + } + } + + /** + * Restores the categories a manga is in. + * + * @param manga the manga whose categories have to be restored. + * @param categories the categories to restore. + */ + private fun restoreCategories(manga: Manga, categories: List, backupCategories: List) { + val dbCategories = db.getCategories().executeAsBlocking() + val mangaCategoriesToUpdate = ArrayList(categories.size) + categories.forEach { backupCategoryOrder -> + backupCategories.firstOrNull { + it.order == backupCategoryOrder + }?.let { backupCategory -> + dbCategories.firstOrNull { dbCategory -> + dbCategory.name == backupCategory.name + }?.let { dbCategory -> + mangaCategoriesToUpdate += MangaCategory.create(manga, dbCategory) + } + } + } + + // Update database + if (mangaCategoriesToUpdate.isNotEmpty()) { + db.deleteOldMangasCategories(listOf(manga)).executeAsBlocking() + db.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking() + } + } + + /** + * Restore history from Json + * + * @param history list containing history to be restored + */ + internal fun restoreHistoryForManga(history: List) { + // List containing history to be updated + val historyToBeUpdated = ArrayList(history.size) + for ((url, lastRead, readDuration) in history) { + val dbHistory = db.getHistoryByChapterUrl(url).executeAsBlocking() + // Check if history already in database and update + if (dbHistory != null) { + dbHistory.apply { + last_read = max(lastRead, dbHistory.last_read) + time_read = max(readDuration, dbHistory.time_read) + } + historyToBeUpdated.add(dbHistory) + } else { + // If not in database create + db.getChapter(url).executeAsBlocking()?.let { + val historyToAdd = History.create(it).apply { + last_read = lastRead + time_read = readDuration + } + historyToBeUpdated.add(historyToAdd) + } + } + } + db.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking() + } + + /** + * Restores the sync of a manga. + * + * @param manga the manga whose sync have to be restored. + * @param tracks the track list to restore. + */ + private fun restoreTrackForManga(manga: Manga, tracks: List) { + // Fix foreign keys with the current manga id + tracks.map { it.manga_id = manga.id!! } + + // Get tracks from database + val dbTracks = db.getTracks(manga).executeAsBlocking() + val trackToUpdate = mutableListOf() + + tracks.forEach { track -> + var isInDatabase = false + for (dbTrack in dbTracks) { + if (track.sync_id == dbTrack.sync_id) { + // The sync is already in the db, only update its fields + if (track.media_id != dbTrack.media_id) { + dbTrack.media_id = track.media_id + } + if (track.library_id != dbTrack.library_id) { + dbTrack.library_id = track.library_id + } + dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read) + isInDatabase = true + trackToUpdate.add(dbTrack) + break + } + } + if (!isInDatabase) { + // Insert new sync. Let the db assign the id + track.id = null + trackToUpdate.add(track) + } + } + // Update database + if (trackToUpdate.isNotEmpty()) { + db.insertTracks(trackToUpdate).executeAsBlocking() + } + } + + private fun restoreFilteredScanlatorsForManga(manga: Manga, filteredScanlators: List) { + val actualList = ChapterUtil.getScanlators(manga.filtered_scanlators) + filteredScanlators + MangaUtil.setScanlatorFilter(db, manga, actualList.toSet()) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceBackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceBackupRestorer.kt new file mode 100644 index 0000000000..4cd56156b7 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/PreferenceBackupRestorer.kt @@ -0,0 +1,93 @@ +package eu.kanade.tachiyomi.data.backup.restore.restorers + +import android.content.Context +import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore +import eu.kanade.tachiyomi.core.preference.PreferenceStore +import eu.kanade.tachiyomi.data.backup.create.BackupCreatorJob +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences +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.library.LibraryUpdateJob +import eu.kanade.tachiyomi.extension.ExtensionUpdateJob +import eu.kanade.tachiyomi.source.sourcePreferences +import eu.kanade.tachiyomi.ui.library.LibrarySort +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class PreferenceBackupRestorer( + private val context: Context, + private val preferenceStore: PreferenceStore = Injekt.get(), +) { + fun restoreAppPreferences(preferences: List, onComplete: () -> Unit) { + restorePreferences(preferences, preferenceStore) + + ExtensionUpdateJob.setupTask(context) + LibraryUpdateJob.setupTask(context) + BackupCreatorJob.setupTask(context) + + onComplete() + } + + fun restoreSourcePreferences(preferences: List, onComplete: () -> Unit) { + preferences.forEach { + val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey)) + restorePreferences(it.prefs, sourcePrefs) + } + } + + private fun restorePreferences( + toRestore: List, + preferenceStore: PreferenceStore, + ) { + val prefs = preferenceStore.getAll() + toRestore.forEach { (key, value) -> + // j2k fork differences + if (key == "library_sorting_mode" && value is StringPreferenceValue && + prefs[key] is Int? + ) { + val intValue = LibrarySort.deserialize(value.value) + preferenceStore.getInt(key).set(intValue.mainValue) + return@forEach + } + // end j2k fork differences + + 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) + } + } + } + } + } +}