refactor: Split backup to smaller classes

This commit is contained in:
Ahmad Ansori Palembani 2024-06-10 06:42:59 +07:00
parent cdc7ab97e4
commit f19f3a5fe4
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
9 changed files with 649 additions and 556 deletions

View file

@ -6,50 +6,17 @@ import co.touchlab.kermit.Logger
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import dev.yokai.domain.storage.StorageManager import dev.yokai.domain.storage.StorageManager
import eu.kanade.tachiyomi.R 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
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_READ_MANGA_MASK 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.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.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.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.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.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 eu.kanade.tachiyomi.util.system.e
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import okio.buffer import okio.buffer
@ -60,15 +27,18 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.FileOutputStream 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 val parser = ProtoBuf
private val db: DatabaseHelper = Injekt.get() private val db: DatabaseHelper = Injekt.get()
private val sourceManager: SourceManager = Injekt.get()
private val preferences: PreferencesHelper = Injekt.get() private val preferences: PreferencesHelper = Injekt.get()
private val customMangaManager: CustomMangaManager = Injekt.get() private val storageManager: StorageManager by injectLazy()
internal val storageManager: StorageManager by injectLazy()
/** /**
* Create backup Json file from database * Create backup Json file from database
@ -89,12 +59,12 @@ class BackupCreator(val context: Context) {
} }
backup = Backup( backup = Backup(
backupMangas(databaseManga, flags), mangaBackupCreator.backupMangas(databaseManga, flags),
backupCategories(), categoriesBackupCreator.backupCategories(),
emptyList(), emptyList(),
backupExtensionInfo(databaseManga), sourcesBackupCreator.backupExtensionInfo(databaseManga),
backupAppPreferences(flags), preferenceBackupCreator.backupAppPreferences(flags),
backupSourcePreferences(flags), preferenceBackupCreator.backupSourcePreferences(flags),
) )
} }
@ -146,126 +116,4 @@ class BackupCreator(val context: Context) {
throw e throw e
} }
} }
private fun backupMangas(mangas: List<Manga>, flags: Int): List<BackupManga> {
return mangas.map {
backupManga(it, flags)
}
}
private fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
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<BackupCategory> {
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<BackupPreference> {
if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList()
return preferenceStore.getAll().toBackupPreferences()
}
private fun backupSourcePreferences(flags: Int): List<BackupSourcePreferences> {
if (flags and BACKUP_SOURCE_PREFS_MASK != BACKUP_SOURCE_PREFS) return emptyList()
return sourceManager.getOnlineSources()
.filterIsInstance<ConfigurableSource>()
.map {
BackupSourcePreferences(
it.preferenceKey(),
it.sourcePreferences().all.toBackupPreferences(),
)
}
}
@Suppress("UNCHECKED_CAST")
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
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<String>)?.let {
BackupPreference(key, StringSetPreferenceValue(it))
}
else -> null
}
}
}
} }

View file

@ -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<BackupCategory> {
return db.getCategories()
.executeAsBlocking()
.map { BackupCategory.copyFrom(it) }
}
}

View file

@ -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<Manga>, flags: Int): List<BackupManga> {
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
}
}

View file

@ -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<BackupPreference> {
if (flags and BACKUP_APP_PREFS_MASK != BACKUP_APP_PREFS) return emptyList()
return preferenceStore.getAll().toBackupPreferences()
}
fun backupSourcePreferences(flags: Int): List<BackupSourcePreferences> {
if (flags and BACKUP_SOURCE_PREFS_MASK != BACKUP_SOURCE_PREFS) return emptyList()
return sourceManager.getOnlineSources()
.filterIsInstance<ConfigurableSource>()
.map {
BackupSourcePreferences(
it.preferenceKey(),
it.sourcePreferences().all.toBackupPreferences(),
)
}
}
@Suppress("UNCHECKED_CAST")
private fun Map<String, *>.toBackupPreferences(): List<BackupPreference> {
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<String>)?.let {
BackupPreference(key, StringSetPreferenceValue(it))
}
else -> null
}
}
}
}

View file

@ -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<Manga>): List<BackupSource> {
return mangas
.asSequence()
.map { it.source }
.distinct()
.map { sourceManager.getOrStub(it) }
.map { BackupSource.copyFrom(it) }
.toList()
}
}

View file

@ -2,59 +2,27 @@ package eu.kanade.tachiyomi.data.backup.restore
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import dev.yokai.domain.library.custom.model.CustomMangaInfo
import eu.kanade.tachiyomi.R 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.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.BackupSource
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesBackupRestorer
import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaBackupRestorer
import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceBackupRestorer
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.util.BackupUtil 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.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.launchNow
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive 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.io.File
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* 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 restoreAmount = 0
private var restoreProgress = 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 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 // Store source mapping for error messages
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
sourceMapping = backupMaps.associate { it.sourceId to it.name } sourceMapping = backupMaps.associate { it.sourceId to it.name }
return coroutineScope { return coroutineScope {
restoreAppPreferences(backup.backupPreferences) // Restore categories
restoreSourcePreferences(backup.backupSourcePreferences) 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 // Restore individual manga
backup.backupManga.forEach { backup.backupManga.forEach {
@ -107,349 +84,24 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) {
return@coroutineScope false 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 true
} }
// TODO: optionally trigger online library + tracker update // TODO: optionally trigger online library + tracker update
} }
private fun restoreCategories(backupCategories: List<BackupCategory>) {
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<BackupCategory>) {
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<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
filteredScanlators: List<String>,
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<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
filteredScanlators: List<String>,
customManga: CustomMangaInfo?,
) {
restoreChapters(backupManga, chapters)
restoreExtras(backupManga, categories, history, tracks, backupCategories, filteredScanlators, customManga)
}
private fun restoreChapters(manga: Manga, chapters: List<Chapter>) {
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<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
filteredScanlators: List<String>,
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<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = db.getCategories().executeAsBlocking()
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(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<BackupHistory>) {
// List containing history to be updated
val historyToBeUpdated = ArrayList<History>(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<Track>) {
// 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<Track>()
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<String>) {
val actualList = ChapterUtil.getScanlators(manga.filtered_scanlators) + filteredScanlators
MangaUtil.setScanlatorFilter(db, manga, actualList.toSet())
}
private fun restoreAppPreferences(preferences: List<BackupPreference>) {
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<BackupSourcePreferences>) {
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<BackupPreference>,
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] * Called to update dialog in [BackupConst]
* *
@ -468,7 +120,7 @@ class BackupRestorer(val context: Context, val notifier: BackupNotifier) {
internal fun writeErrorLog(): File { internal fun writeErrorLog(): File {
try { try {
if (errors.isNotEmpty()) { 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()) val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
file.bufferedWriter().use { out -> file.bufferedWriter().use { out ->

View file

@ -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<BackupCategory>, 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()
}
}

View file

@ -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<BackupCategory>,
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<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
filteredScanlators: List<String>,
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<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
filteredScanlators: List<String>,
customManga: CustomMangaInfo?,
) {
restoreChapters(backupManga, chapters)
restoreExtras(backupManga, categories, history, tracks, backupCategories, filteredScanlators, customManga)
}
private fun restoreChapters(manga: Manga, chapters: List<Chapter>) {
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<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
filteredScanlators: List<String>,
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<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = db.getCategories().executeAsBlocking()
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(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<BackupHistory>) {
// List containing history to be updated
val historyToBeUpdated = ArrayList<History>(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<Track>) {
// 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<Track>()
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<String>) {
val actualList = ChapterUtil.getScanlators(manga.filtered_scanlators) + filteredScanlators
MangaUtil.setScanlatorFilter(db, manga, actualList.toSet())
}
}

View file

@ -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<BackupPreference>, onComplete: () -> Unit) {
restorePreferences(preferences, preferenceStore)
ExtensionUpdateJob.setupTask(context)
LibraryUpdateJob.setupTask(context)
BackupCreatorJob.setupTask(context)
onComplete()
}
fun restoreSourcePreferences(preferences: List<BackupSourcePreferences>, onComplete: () -> Unit) {
preferences.forEach {
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
restorePreferences(it.prefs, sourcePrefs)
}
}
private fun restorePreferences(
toRestore: List<BackupPreference>,
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)
}
}
}
}
}
}