mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor: Split backup to smaller classes
This commit is contained in:
parent
cdc7ab97e4
commit
f19f3a5fe4
9 changed files with 649 additions and 556 deletions
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 ->
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue