From 83a67feb48c4e40994a334520c907f71d2fbf75e Mon Sep 17 00:00:00 2001 From: arkon Date: Thu, 21 Dec 2023 22:16:42 -0500 Subject: [PATCH] Foundations for partial restores Related to #3136 --- .../settings/screen/SettingsDataScreen.kt | 23 ++++++++- .../data/backup/restore/BackupRestoreJob.kt | 19 +++++-- .../data/backup/restore/BackupRestorer.kt | 50 +++++++++++++++---- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 1a5c93132c..210ce9296c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt index 9883eb023c..f1b3a28c90 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestoreJob.kt @@ -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() .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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index a1e2628442..063f592de3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -39,10 +39,10 @@ class BackupRestorer( */ private var sourceMapping: Map = 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], + ) + } +}