mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-10 03:31:58 +01:00
Split restoring logic into smaller classes
This commit is contained in:
parent
5fec881387
commit
cd16522805
14 changed files with 318 additions and 260 deletions
|
@ -37,9 +37,9 @@ import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
|||
import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
|
||||
import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
|
||||
import eu.kanade.presentation.util.relativeTimeSpanString
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
|
|
|
@ -29,8 +29,8 @@ import cafe.adriel.voyager.navigator.LocalNavigator
|
|||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
|
|
|
@ -7,7 +7,7 @@ import eu.kanade.domain.base.BasePreferences
|
|||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateJob
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.data.backup
|
||||
package eu.kanade.tachiyomi.data.backup.create
|
||||
|
||||
internal object BackupCreateFlags {
|
||||
const val BACKUP_CATEGORY = 0x1
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.data.backup
|
||||
package eu.kanade.tachiyomi.data.backup.create
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
@ -14,6 +14,8 @@ import androidx.work.PeriodicWorkRequestBuilder
|
|||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||
import eu.kanade.tachiyomi.util.system.isRunning
|
|
@ -1,14 +1,15 @@
|
|||
package eu.kanade.tachiyomi.data.backup
|
||||
package eu.kanade.tachiyomi.data.backup.create
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_APP_PREFS
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_CATEGORY
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_CHAPTER
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_HISTORY
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_SOURCE_PREFS
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_TRACK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_APP_PREFS
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_CATEGORY
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_CHAPTER
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_HISTORY
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_SOURCE_PREFS
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateFlags.BACKUP_TRACK
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
|
|
@ -8,7 +8,9 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
|||
data class BrokenBackupSource(
|
||||
@ProtoNumber(0) var name: String = "",
|
||||
@ProtoNumber(1) var sourceId: Long,
|
||||
)
|
||||
) {
|
||||
fun toBackupSource() = BackupSource(name, sourceId)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BackupSource(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package eu.kanade.tachiyomi.data.backup
|
||||
package eu.kanade.tachiyomi.data.backup.restore
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
@ -9,6 +9,7 @@ import androidx.work.ForegroundInfo
|
|||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.system.cancelNotification
|
||||
import eu.kanade.tachiyomi.util.system.isRunning
|
||||
|
@ -28,13 +29,12 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
|||
override suspend fun doWork(): Result {
|
||||
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
|
||||
?: return Result.failure()
|
||||
val sync = inputData.getBoolean(SYNC_KEY, false)
|
||||
val isSync = inputData.getBoolean(SYNC_KEY, false)
|
||||
|
||||
setForegroundSafely()
|
||||
|
||||
return try {
|
||||
val restorer = BackupRestorer(context, notifier)
|
||||
restorer.syncFromBackup(uri, sync)
|
||||
BackupRestorer(context, notifier, isSync).restore(uri)
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) {
|
|
@ -0,0 +1,156 @@
|
|||
package eu.kanade.tachiyomi.data.backup.restore
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
|
||||
import eu.kanade.tachiyomi.util.BackupUtil
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.launch
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class BackupRestorer(
|
||||
private val context: Context,
|
||||
private val notifier: BackupNotifier,
|
||||
private val isSync: Boolean,
|
||||
|
||||
private val categoriesRestorer: CategoriesRestorer = CategoriesRestorer(),
|
||||
private val preferenceRestorer: PreferenceRestorer = PreferenceRestorer(context),
|
||||
private val mangaRestorer: MangaRestorer = MangaRestorer(),
|
||||
) {
|
||||
|
||||
private var restoreAmount = 0
|
||||
private var restoreProgress = 0
|
||||
private val errors = mutableListOf<Pair<Date, String>>()
|
||||
|
||||
/**
|
||||
* Mapping of source ID to source name from backup data
|
||||
*/
|
||||
private var sourceMapping: Map<Long, String> = emptyMap()
|
||||
|
||||
suspend fun restore(uri: Uri) {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
restoreFromFile(uri)
|
||||
|
||||
val time = System.currentTimeMillis() - startTime
|
||||
|
||||
val logFile = writeErrorLog()
|
||||
|
||||
notifier.showRestoreComplete(
|
||||
time,
|
||||
errors.size,
|
||||
logFile.parent,
|
||||
logFile.name,
|
||||
isSync,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun restoreFromFile(uri: Uri) {
|
||||
val backup = BackupUtil.decodeBackup(context, uri)
|
||||
|
||||
restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs
|
||||
|
||||
// Store source mapping for error messages
|
||||
val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() }
|
||||
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
||||
|
||||
coroutineScope {
|
||||
restoreCategories(backup.backupCategories)
|
||||
restoreAppPreferences(backup.backupPreferences)
|
||||
restoreSourcePreferences(backup.backupSourcePreferences)
|
||||
restoreManga(backup.backupManga, backup.backupCategories)
|
||||
|
||||
// TODO: optionally trigger online library + tracker update
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.restoreCategories(backupCategories: List<BackupCategory>) = launch {
|
||||
ensureActive()
|
||||
categoriesRestorer.restoreCategories(backupCategories)
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(
|
||||
context.stringResource(MR.strings.categories),
|
||||
restoreProgress,
|
||||
restoreAmount,
|
||||
isSync,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.restoreManga(
|
||||
backupMangas: List<BackupManga>,
|
||||
backupCategories: List<BackupCategory>,
|
||||
) = launch {
|
||||
mangaRestorer.sortByNew(backupMangas)
|
||||
.forEach {
|
||||
ensureActive()
|
||||
|
||||
try {
|
||||
mangaRestorer.restoreManga(it, backupCategories)
|
||||
} catch (e: Exception) {
|
||||
val sourceName = sourceMapping[it.source] ?: it.source.toString()
|
||||
errors.add(Date() to "${it.title} [$sourceName]: ${e.message}")
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(it.title, restoreProgress, restoreAmount, isSync)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.restoreAppPreferences(preferences: List<BackupPreference>) = launch {
|
||||
ensureActive()
|
||||
preferenceRestorer.restoreAppPreferences(preferences)
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(
|
||||
context.stringResource(MR.strings.app_settings),
|
||||
restoreProgress,
|
||||
restoreAmount,
|
||||
isSync,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.restoreSourcePreferences(preferences: List<BackupSourcePreferences>) = launch {
|
||||
ensureActive()
|
||||
preferenceRestorer.restoreSourcePreferences(preferences)
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(
|
||||
context.stringResource(MR.strings.source_settings),
|
||||
restoreProgress,
|
||||
restoreAmount,
|
||||
isSync,
|
||||
)
|
||||
}
|
||||
|
||||
private fun writeErrorLog(): File {
|
||||
try {
|
||||
if (errors.isNotEmpty()) {
|
||||
val file = context.createFileInCacheDir("tachiyomi_restore.txt")
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||
|
||||
file.bufferedWriter().use { out ->
|
||||
errors.forEach { (date, message) ->
|
||||
out.write("[${sdf.format(date)}] $message\n")
|
||||
}
|
||||
}
|
||||
return file
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Empty
|
||||
}
|
||||
return File("")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package eu.kanade.tachiyomi.data.backup.restore
|
||||
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class CategoriesRestorer(
|
||||
private val handler: DatabaseHandler = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
|
||||
if (backupCategories.isNotEmpty()) {
|
||||
val dbCategories = getCategories.await()
|
||||
val dbCategoriesByName = dbCategories.associateBy { it.name }
|
||||
|
||||
val categories = backupCategories.map {
|
||||
dbCategoriesByName[it.name]
|
||||
?: handler.awaitOneExecutable {
|
||||
categoriesQueries.insert(it.name, it.order, it.flags)
|
||||
categoriesQueries.selectLastInsertedRowId()
|
||||
}.let { id -> it.toCategory(id) }
|
||||
}
|
||||
|
||||
libraryPreferences.categorizedDisplaySettings().set(
|
||||
(dbCategories + categories)
|
||||
.distinctBy { it.flags }
|
||||
.size > 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +1,29 @@
|
|||
package eu.kanade.tachiyomi.data.backup
|
||||
package eu.kanade.tachiyomi.data.backup.restore
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
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.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.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.source.sourcePreferences
|
||||
import eu.kanade.tachiyomi.util.BackupUtil
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.core.preference.AndroidPreferenceStore
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.data.DatabaseHandler
|
||||
import tachiyomi.data.UpdateStrategyColumnAdapter
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
import tachiyomi.domain.track.interactor.InsertTrack
|
||||
import tachiyomi.domain.track.model.Track
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
|
||||
class BackupRestorer(
|
||||
private val context: Context,
|
||||
private val notifier: BackupNotifier,
|
||||
|
||||
class MangaRestorer(
|
||||
private val handler: DatabaseHandler = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
|
||||
|
@ -59,167 +31,48 @@ class BackupRestorer(
|
|||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
private val insertTrack: InsertTrack = Injekt.get(),
|
||||
private val fetchInterval: FetchInterval = Injekt.get(),
|
||||
|
||||
private val preferenceStore: PreferenceStore = Injekt.get(),
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
fetchInterval: FetchInterval = Injekt.get(),
|
||||
) {
|
||||
|
||||
private var restoreAmount = 0
|
||||
private var restoreProgress = 0
|
||||
|
||||
private var now = ZonedDateTime.now()
|
||||
private var currentFetchWindow = fetchInterval.getWindow(now)
|
||||
|
||||
/**
|
||||
* Mapping of source ID to source name from backup data
|
||||
*/
|
||||
private var sourceMapping: Map<Long, String> = emptyMap()
|
||||
|
||||
private val errors = mutableListOf<Pair<Date, String>>()
|
||||
|
||||
suspend fun syncFromBackup(uri: Uri, sync: Boolean) {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
prepareState()
|
||||
restoreFromFile(uri, sync)
|
||||
|
||||
val endTime = System.currentTimeMillis()
|
||||
val time = endTime - startTime
|
||||
|
||||
val logFile = writeErrorLog()
|
||||
|
||||
notifier.showRestoreComplete(
|
||||
time,
|
||||
errors.size,
|
||||
logFile.parent,
|
||||
logFile.name,
|
||||
sync,
|
||||
)
|
||||
}
|
||||
|
||||
private fun writeErrorLog(): File {
|
||||
try {
|
||||
if (errors.isNotEmpty()) {
|
||||
val file = context.createFileInCacheDir("tachiyomi_restore.txt")
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
|
||||
|
||||
file.bufferedWriter().use { out ->
|
||||
errors.forEach { (date, message) ->
|
||||
out.write("[${sdf.format(date)}] $message\n")
|
||||
}
|
||||
}
|
||||
return file
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Empty
|
||||
}
|
||||
return File("")
|
||||
}
|
||||
|
||||
private fun prepareState() {
|
||||
init {
|
||||
now = ZonedDateTime.now()
|
||||
currentFetchWindow = fetchInterval.getWindow(now)
|
||||
}
|
||||
|
||||
private suspend fun restoreFromFile(uri: Uri, sync: Boolean) {
|
||||
val backup = BackupUtil.decodeBackup(context, uri)
|
||||
|
||||
restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs
|
||||
|
||||
// Store source mapping for error messages
|
||||
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
||||
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
||||
|
||||
coroutineScope {
|
||||
ensureActive()
|
||||
restoreCategories(backup.backupCategories)
|
||||
|
||||
ensureActive()
|
||||
restoreAppPreferences(backup.backupPreferences)
|
||||
|
||||
ensureActive()
|
||||
restoreSourcePreferences(backup.backupSourcePreferences)
|
||||
|
||||
backup.backupManga.sortByNew()
|
||||
.forEach {
|
||||
ensureActive()
|
||||
restoreManga(it, backup.backupCategories, sync)
|
||||
}
|
||||
|
||||
// TODO: optionally trigger online library + tracker update
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<BackupManga>.sortByNew(): List<BackupManga> {
|
||||
suspend fun sortByNew(backupMangas: List<BackupManga>): List<BackupManga> {
|
||||
val urlsBySource = handler.awaitList { mangasQueries.getAllMangaSourceAndUrl() }
|
||||
.groupBy({ it.source }, { it.url })
|
||||
|
||||
return this
|
||||
return backupMangas
|
||||
.sortedWith(
|
||||
compareBy<BackupManga> { it.url in urlsBySource[it.source].orEmpty() }
|
||||
.then(compareByDescending { it.lastModifiedAt }),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
|
||||
if (backupCategories.isNotEmpty()) {
|
||||
val dbCategories = getCategories.await()
|
||||
val dbCategoriesByName = dbCategories.associateBy { it.name }
|
||||
|
||||
val categories = backupCategories.map {
|
||||
dbCategoriesByName[it.name]
|
||||
?: handler.awaitOneExecutable {
|
||||
categoriesQueries.insert(it.name, it.order, it.flags)
|
||||
categoriesQueries.selectLastInsertedRowId()
|
||||
}.let { id -> it.toCategory(id) }
|
||||
}
|
||||
|
||||
libraryPreferences.categorizedDisplaySettings().set(
|
||||
(dbCategories + categories)
|
||||
.distinctBy { it.flags }
|
||||
.size > 1,
|
||||
)
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(
|
||||
context.stringResource(MR.strings.categories),
|
||||
restoreProgress,
|
||||
restoreAmount,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun restoreManga(
|
||||
suspend fun restoreManga(
|
||||
backupManga: BackupManga,
|
||||
backupCategories: List<BackupCategory>,
|
||||
sync: Boolean,
|
||||
) {
|
||||
try {
|
||||
val dbManga = findExistingManga(backupManga)
|
||||
val manga = backupManga.getMangaImpl()
|
||||
val restoredManga = if (dbManga == null) {
|
||||
restoreNewManga(manga)
|
||||
} else {
|
||||
restoreExistingManga(manga, dbManga)
|
||||
}
|
||||
|
||||
restoreMangaDetails(
|
||||
manga = restoredManga,
|
||||
chapters = backupManga.chapters,
|
||||
categories = backupManga.categories,
|
||||
backupCategories = backupCategories,
|
||||
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
|
||||
tracks = backupManga.tracking,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
val sourceName = sourceMapping[backupManga.source] ?: backupManga.source.toString()
|
||||
errors.add(Date() to "${backupManga.title} [$sourceName]: ${e.message}")
|
||||
val dbManga = findExistingManga(backupManga)
|
||||
val manga = backupManga.getMangaImpl()
|
||||
val restoredManga = if (dbManga == null) {
|
||||
restoreNewManga(manga)
|
||||
} else {
|
||||
restoreExistingManga(manga, dbManga)
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(backupManga.title, restoreProgress, restoreAmount, sync)
|
||||
restoreMangaDetails(
|
||||
manga = restoredManga,
|
||||
chapters = backupManga.chapters,
|
||||
categories = backupManga.categories,
|
||||
backupCategories = backupCategories,
|
||||
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
|
||||
tracks = backupManga.tracking,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun findExistingManga(backupManga: BackupManga): Manga? {
|
||||
|
@ -546,75 +399,4 @@ class BackupRestorer(
|
|||
}
|
||||
|
||||
private fun Track.forComparison() = this.copy(id = 0L, mangaId = 0L)
|
||||
|
||||
private fun restoreAppPreferences(preferences: List<BackupPreference>) {
|
||||
restorePreferences(preferences, preferenceStore)
|
||||
|
||||
LibraryUpdateJob.setupTask(context)
|
||||
BackupCreateJob.setupTask(context)
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(
|
||||
context.stringResource(MR.strings.app_settings),
|
||||
restoreProgress,
|
||||
restoreAmount,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun restoreSourcePreferences(preferences: List<BackupSourcePreferences>) {
|
||||
preferences.forEach {
|
||||
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
|
||||
restorePreferences(it.prefs, sourcePrefs)
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(
|
||||
context.stringResource(MR.strings.source_settings),
|
||||
restoreProgress,
|
||||
restoreAmount,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
private fun restorePreferences(
|
||||
toRestore: List<BackupPreference>,
|
||||
preferenceStore: PreferenceStore,
|
||||
) {
|
||||
val prefs = preferenceStore.getAll()
|
||||
toRestore.forEach { (key, value) ->
|
||||
when (value) {
|
||||
is IntPreferenceValue -> {
|
||||
if (prefs[key] is Int?) {
|
||||
preferenceStore.getInt(key).set(value.value)
|
||||
}
|
||||
}
|
||||
is LongPreferenceValue -> {
|
||||
if (prefs[key] is Long?) {
|
||||
preferenceStore.getLong(key).set(value.value)
|
||||
}
|
||||
}
|
||||
is FloatPreferenceValue -> {
|
||||
if (prefs[key] is Float?) {
|
||||
preferenceStore.getFloat(key).set(value.value)
|
||||
}
|
||||
}
|
||||
is StringPreferenceValue -> {
|
||||
if (prefs[key] is String?) {
|
||||
preferenceStore.getString(key).set(value.value)
|
||||
}
|
||||
}
|
||||
is BooleanPreferenceValue -> {
|
||||
if (prefs[key] is Boolean?) {
|
||||
preferenceStore.getBoolean(key).set(value.value)
|
||||
}
|
||||
}
|
||||
is StringSetPreferenceValue -> {
|
||||
if (prefs[key] is Set<*>?) {
|
||||
preferenceStore.getStringSet(key).set(value.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package eu.kanade.tachiyomi.data.backup.restore
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
|
||||
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.source.sourcePreferences
|
||||
import tachiyomi.core.preference.AndroidPreferenceStore
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class PreferenceRestorer(
|
||||
private val context: Context,
|
||||
private val preferenceStore: PreferenceStore = Injekt.get(),
|
||||
) {
|
||||
|
||||
fun restoreAppPreferences(preferences: List<BackupPreference>) {
|
||||
restorePreferences(preferences, preferenceStore)
|
||||
|
||||
LibraryUpdateJob.setupTask(context)
|
||||
BackupCreateJob.setupTask(context)
|
||||
}
|
||||
|
||||
fun restoreSourcePreferences(preferences: List<BackupSourcePreferences>) {
|
||||
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) ->
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import android.content.Intent
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateDownloadJob
|
||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.util
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.create.BackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
|
||||
import okio.buffer
|
||||
|
|
Loading…
Reference in a new issue