Foundations for partial restores

Related to #3136

(cherry picked from commit 83a67feb48c4e40994a334520c907f71d2fbf75e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt
This commit is contained in:
arkon 2023-12-21 22:16:42 -05:00 committed by Jobobby04
parent abf1cdb31b
commit 711ca69876
3 changed files with 105 additions and 15 deletions

View File

@ -40,6 +40,7 @@ import eu.kanade.presentation.util.relativeTimeSpanString
import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.BackupFileValidator
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.PagePreviewCache import eu.kanade.tachiyomi.data.cache.PagePreviewCache
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
@ -251,7 +252,19 @@ object SettingsDataScreen : SearchableSettings {
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
BackupRestoreJob.start(context, err.uri) BackupRestoreJob.start(
context = context,
uri = err.uri,
// TODO: allow user-selectable restore options
options = RestoreOptions(
appSettings = true,
sourceSettings = true,
library = true,
// SY -->
savedSearches = true,
// SY <--
),
)
onDismissRequest() onDismissRequest()
}, },
) { ) {
@ -285,7 +298,19 @@ object SettingsDataScreen : SearchableSettings {
} }
if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) { if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) {
BackupRestoreJob.start(context, it) BackupRestoreJob.start(
context = context,
uri = it,
// TODO: allow user-selectable restore options
options = RestoreOptions(
appSettings = true,
sourceSettings = true,
library = true,
// SY -->
savedSearches = true,
// SY <--
),
)
return@rememberLauncherForActivityResult return@rememberLauncherForActivityResult
} }

View File

@ -30,13 +30,19 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri() val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
?: return Result.failure() val options = inputData.getBooleanArray(OPTIONS_KEY)
?.let { RestoreOptions.fromBooleanArray(it) }
if (uri == null || options == null) {
return Result.failure()
}
val isSync = inputData.getBoolean(SYNC_KEY, false) val isSync = inputData.getBoolean(SYNC_KEY, false)
setForegroundSafely() setForegroundSafely()
return try { return try {
BackupRestorer(context, notifier, isSync).restore(uri) BackupRestorer(context, notifier, isSync).restore(uri, options)
Result.success() Result.success()
} catch (e: Exception) { } catch (e: Exception) {
if (e is CancellationException) { if (e is CancellationException) {
@ -69,10 +75,16 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
return context.workManager.isRunning(TAG) return context.workManager.isRunning(TAG)
} }
fun start(context: Context, uri: Uri, sync: Boolean = false) { fun start(
context: Context,
uri: Uri,
options: RestoreOptions,
sync: Boolean = false,
) {
val inputData = workDataOf( val inputData = workDataOf(
LOCATION_URI_KEY to uri.toString(), LOCATION_URI_KEY to uri.toString(),
SYNC_KEY to sync, SYNC_KEY to sync,
OPTIONS_KEY to options.toBooleanArray(),
) )
val request = OneTimeWorkRequestBuilder<BackupRestoreJob>() val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
.addTag(TAG) .addTag(TAG)
@ -91,3 +103,4 @@ private const val TAG = "BackupRestore"
private const val LOCATION_URI_KEY = "location_uri" // String private const val LOCATION_URI_KEY = "location_uri" // String
private const val SYNC_KEY = "sync" // Boolean private const val SYNC_KEY = "sync" // Boolean
private const val OPTIONS_KEY = "options" // BooleanArray

View File

@ -45,10 +45,10 @@ class BackupRestorer(
*/ */
private var sourceMapping: Map<Long, String> = emptyMap() private var sourceMapping: Map<Long, String> = emptyMap()
suspend fun restore(uri: Uri) { suspend fun restore(uri: Uri, options: RestoreOptions) {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
restoreFromFile(uri) restoreFromFile(uri, options)
val time = System.currentTimeMillis() - startTime val time = System.currentTimeMillis() - startTime
@ -63,23 +63,46 @@ class BackupRestorer(
) )
} }
private suspend fun restoreFromFile(uri: Uri) { private suspend fun restoreFromFile(uri: Uri, options: RestoreOptions) {
val backup = BackupUtil.decodeBackup(context, uri) val backup = BackupUtil.decodeBackup(context, uri)
restoreAmount = backup.backupManga.size + 4 // +4 for categories, app prefs, source prefs, saved searches
// Store source mapping for error messages // Store source mapping for error messages
val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() } val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() }
sourceMapping = backupMaps.associate { it.sourceId to it.name } sourceMapping = backupMaps.associate { it.sourceId to it.name }
if (options.library) {
restoreAmount += backup.backupManga.size + 1 // +1 for categories
}
// SY -->
if (options.savedSearches) {
restoreAmount += 1
}
// SY <--
if (options.appSettings) {
restoreAmount += 1
}
if (options.sourceSettings) {
restoreAmount += 1
}
coroutineScope { coroutineScope {
restoreCategories(backup.backupCategories) if (options.library) {
restoreCategories(backup.backupCategories)
}
// SY --> // SY -->
restoreSavedSearches(backup.backupSavedSearches) if (options.savedSearches) {
restoreSavedSearches(backup.backupSavedSearches)
}
// SY <-- // SY <--
restoreAppPreferences(backup.backupPreferences) if (options.appSettings) {
restoreSourcePreferences(backup.backupSourcePreferences) restoreAppPreferences(backup.backupPreferences)
restoreManga(backup.backupManga, backup.backupCategories) }
if (options.sourceSettings) {
restoreSourcePreferences(backup.backupSourcePreferences)
}
if (options.library) {
restoreManga(backup.backupManga, backup.backupCategories)
}
// TODO: optionally trigger online library + tracker update // TODO: optionally trigger online library + tracker update
} }
@ -179,3 +202,32 @@ class BackupRestorer(
return File("") return File("")
} }
} }
data class RestoreOptions(
val appSettings: Boolean,
val sourceSettings: Boolean,
val library: Boolean,
// SY -->
val savedSearches: Boolean
// SY <--
) {
fun toBooleanArray() = booleanArrayOf(
appSettings,
sourceSettings,
library,
// SY -->
savedSearches,
// SY <--
)
companion object {
fun fromBooleanArray(booleanArray: BooleanArray) = RestoreOptions(
appSettings = booleanArray[0],
sourceSettings = booleanArray[1],
library = booleanArray[2],
// SY -->
savedSearches = booleanArray[3],
// SY <--
)
}
}