parent
a51108cbe8
commit
83a67feb48
3 changed files with 78 additions and 14 deletions
|
@ -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.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
|
@ -249,7 +250,16 @@ 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,
|
||||||
|
),
|
||||||
|
)
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -283,7 +293,16 @@ 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,
|
||||||
|
),
|
||||||
|
)
|
||||||
return@rememberLauncherForActivityResult
|
return@rememberLauncherForActivityResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -39,10 +39,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
|
||||||
|
|
||||||
|
@ -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)
|
val backup = BackupUtil.decodeBackup(context, uri)
|
||||||
|
|
||||||
restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs
|
|
||||||
|
|
||||||
// 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
|
||||||
|
}
|
||||||
|
if (options.appSettings) {
|
||||||
|
restoreAmount += 1
|
||||||
|
}
|
||||||
|
if (options.sourceSettings) {
|
||||||
|
restoreAmount += 1
|
||||||
|
}
|
||||||
|
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
restoreCategories(backup.backupCategories)
|
if (options.library) {
|
||||||
restoreAppPreferences(backup.backupPreferences)
|
restoreCategories(backup.backupCategories)
|
||||||
restoreSourcePreferences(backup.backupSourcePreferences)
|
}
|
||||||
restoreManga(backup.backupManga, 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
|
// TODO: optionally trigger online library + tracker update
|
||||||
}
|
}
|
||||||
|
@ -154,3 +170,19 @@ class BackupRestorer(
|
||||||
return File("")
|
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],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Reference in a new issue