Foundations for partial restores

Related to #3136
This commit is contained in:
arkon 2023-12-21 22:16:42 -05:00
parent a51108cbe8
commit 83a67feb48
3 changed files with 78 additions and 14 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.create.BackupCreateJob
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.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.DeviceUtil
@ -249,7 +250,16 @@ object SettingsDataScreen : SearchableSettings {
confirmButton = {
TextButton(
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,
),
)
onDismissRequest()
},
) {
@ -283,7 +293,16 @@ object SettingsDataScreen : SearchableSettings {
}
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,
),
)
return@rememberLauncherForActivityResult
}

View file

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

View file

@ -39,10 +39,10 @@ class BackupRestorer(
*/
private var sourceMapping: Map<Long, String> = emptyMap()
suspend fun restore(uri: Uri) {
suspend fun restore(uri: Uri, options: RestoreOptions) {
val startTime = System.currentTimeMillis()
restoreFromFile(uri)
restoreFromFile(uri, options)
val time = System.currentTimeMillis() - startTime
@ -57,20 +57,36 @@ class BackupRestorer(
)
}
private suspend fun restoreFromFile(uri: Uri) {
private suspend fun restoreFromFile(uri: Uri, options: RestoreOptions) {
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 }
if (options.library) {
restoreAmount += backup.backupManga.size + 1 // +1 for categories
}
if (options.appSettings) {
restoreAmount += 1
}
if (options.sourceSettings) {
restoreAmount += 1
}
coroutineScope {
restoreCategories(backup.backupCategories)
restoreAppPreferences(backup.backupPreferences)
restoreSourcePreferences(backup.backupSourcePreferences)
restoreManga(backup.backupManga, backup.backupCategories)
if (options.library) {
restoreCategories(backup.backupCategories)
}
if (options.appSettings) {
restoreAppPreferences(backup.backupPreferences)
}
if (options.sourceSettings) {
restoreSourcePreferences(backup.backupSourcePreferences)
}
if (options.library) {
restoreManga(backup.backupManga, backup.backupCategories)
}
// TODO: optionally trigger online library + tracker update
}
@ -154,3 +170,19 @@ class BackupRestorer(
return File("")
}
}
data class RestoreOptions(
val appSettings: Boolean,
val sourceSettings: Boolean,
val library: Boolean,
) {
fun toBooleanArray() = booleanArrayOf(appSettings, sourceSettings, library)
companion object {
fun fromBooleanArray(booleanArray: BooleanArray) = RestoreOptions(
appSettings = booleanArray[0],
sourceSettings = booleanArray[1],
library = booleanArray[2],
)
}
}