Refactor fetching of restorable backups
so that we don't go through the BackupManager API, but use RestoreCoordinator directly
This commit is contained in:
parent
aeafc80bb9
commit
bcb245531c
3 changed files with 63 additions and 102 deletions
|
@ -1,19 +1,15 @@
|
|||
package com.stevesoltys.seedvault.restore
|
||||
|
||||
import android.app.backup.RestoreSet
|
||||
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
||||
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||
|
||||
data class RestorableBackup(
|
||||
private val restoreSet: RestoreSet,
|
||||
private val backupMetadata: BackupMetadata
|
||||
) {
|
||||
data class RestorableBackup(private val backupMetadata: BackupMetadata) {
|
||||
|
||||
val name: String
|
||||
get() = restoreSet.name
|
||||
get() = backupMetadata.deviceName
|
||||
|
||||
val token: Long
|
||||
get() = restoreSet.token
|
||||
get() = backupMetadata.token
|
||||
|
||||
val time: Long
|
||||
get() = backupMetadata.time
|
||||
|
|
|
@ -20,7 +20,6 @@ import com.stevesoltys.seedvault.BackupMonitor
|
|||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
|
||||
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
|
||||
|
@ -65,9 +64,6 @@ import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_TIMESTA
|
|||
import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_USER_ID
|
||||
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
|
||||
import java.util.LinkedList
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
private val TAG = RestoreViewModel::class.java.simpleName
|
||||
|
||||
|
@ -126,8 +122,6 @@ internal class RestoreViewModel(
|
|||
|
||||
@Throws(RemoteException::class)
|
||||
private fun getOrStartSession(): IRestoreSession {
|
||||
// TODO consider not using the BackupManager for this, but our own API directly
|
||||
// this is less error-prone (hanging sessions) and can provide more data
|
||||
val session = this.session
|
||||
?: backupManager.beginRestoreSessionForUser(UserHandle.myUserId(), null, TRANSPORT_ID)
|
||||
?: throw RemoteException("beginRestoreSessionForUser returned null")
|
||||
|
@ -135,36 +129,27 @@ internal class RestoreViewModel(
|
|||
return session
|
||||
}
|
||||
|
||||
internal fun loadRestoreSets() = viewModelScope.launch {
|
||||
mRestoreSetResults.value = getAvailableRestoreSets()
|
||||
}
|
||||
|
||||
private suspend fun getAvailableRestoreSets() =
|
||||
suspendCoroutine<RestoreSetResult> { continuation ->
|
||||
val session = try {
|
||||
getOrStartSession()
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Error starting new session", e)
|
||||
continuation.resume(RestoreSetResult(app.getString(R.string.restore_set_error)))
|
||||
return@suspendCoroutine
|
||||
}
|
||||
|
||||
val observer = RestoreObserver(continuation)
|
||||
val setResult = session.getAvailableRestoreSets(observer, monitor)
|
||||
if (setResult != 0) {
|
||||
Log.e(TAG, "getAvailableRestoreSets() returned non-zero value")
|
||||
continuation.resume(RestoreSetResult(app.getString(R.string.restore_set_error)))
|
||||
return@suspendCoroutine
|
||||
internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
|
||||
val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) ->
|
||||
when (metadata.time) {
|
||||
0L -> {
|
||||
Log.d(TAG, "Ignoring RestoreSet with no last backup time: $token.")
|
||||
null
|
||||
}
|
||||
else -> RestorableBackup(metadata)
|
||||
}
|
||||
}
|
||||
val result = when {
|
||||
backups == null -> RestoreSetResult(app.getString(R.string.restore_set_error))
|
||||
backups.isEmpty() -> RestoreSetResult(app.getString(R.string.restore_set_empty_result))
|
||||
else -> RestoreSetResult(backups)
|
||||
}
|
||||
mRestoreSetResults.postValue(result)
|
||||
}
|
||||
|
||||
override fun onRestorableBackupClicked(restorableBackup: RestorableBackup) {
|
||||
mChosenRestorableBackup.value = restorableBackup
|
||||
mDisplayFragment.setEvent(RESTORE_APPS)
|
||||
|
||||
// re-installing apps will take some time and the session probably times out
|
||||
// so better close it cleanly and re-open it later
|
||||
closeSession()
|
||||
}
|
||||
|
||||
private fun getInstallResult(backup: RestorableBackup): LiveData<InstallResult> {
|
||||
|
@ -193,7 +178,7 @@ internal class RestoreViewModel(
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
private suspend fun startRestore(token: Long) {
|
||||
private fun startRestore(token: Long) {
|
||||
Log.d(TAG, "Starting new restore session to restore backup $token")
|
||||
|
||||
// if we had no token before (i.e. restore from setup wizard),
|
||||
|
@ -202,25 +187,35 @@ internal class RestoreViewModel(
|
|||
settingsManager.setNewToken(token)
|
||||
}
|
||||
|
||||
// we need to start a new session and retrieve the restore sets before starting the restore
|
||||
val restoreSetResult = getAvailableRestoreSets()
|
||||
if (restoreSetResult.hasError()) {
|
||||
// start a new restore session
|
||||
val session = try {
|
||||
getOrStartSession()
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Error starting new session", e)
|
||||
mRestoreBackupResult.postValue(
|
||||
RestoreBackupResult(app.getString(R.string.restore_finished_error))
|
||||
RestoreBackupResult(app.getString(R.string.restore_set_error))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// now we can start the restore of all available packages
|
||||
val observer = RestoreObserver()
|
||||
val restoreAllResult = session?.restoreAll(token, observer, monitor) ?: 1
|
||||
if (restoreAllResult != 0) {
|
||||
if (session == null) Log.e(TAG, "session was null")
|
||||
else Log.e(TAG, "restoreAll() returned non-zero value")
|
||||
// we need to retrieve the restore sets before starting the restore
|
||||
// otherwise restoreAll() won't work as they need the restore sets cached internally
|
||||
val observer = RestoreObserver { observer ->
|
||||
// this lambda gets executed after we got the restore sets
|
||||
// now we can start the restore of all available packages
|
||||
val restoreAllResult = session.restoreAll(token, observer, monitor)
|
||||
if (restoreAllResult != 0) {
|
||||
Log.e(TAG, "restoreAll() returned non-zero value")
|
||||
mRestoreBackupResult.postValue(
|
||||
RestoreBackupResult(app.getString(R.string.restore_finished_error))
|
||||
)
|
||||
}
|
||||
}
|
||||
if (session.getAvailableRestoreSets(observer, monitor) != 0) {
|
||||
Log.e(TAG, "getAvailableRestoreSets() returned non-zero value")
|
||||
mRestoreBackupResult.postValue(
|
||||
RestoreBackupResult(app.getString(R.string.restore_finished_error))
|
||||
RestoreBackupResult(app.getString(R.string.restore_set_error))
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,9 +292,8 @@ internal class RestoreViewModel(
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
private inner class RestoreObserver(
|
||||
private val continuation: Continuation<RestoreSetResult>? = null
|
||||
) : IRestoreObserver.Stub() {
|
||||
private inner class RestoreObserver(private val startRestore: (RestoreObserver) -> Unit) :
|
||||
IRestoreObserver.Stub() {
|
||||
|
||||
/**
|
||||
* Supply a list of the restore datasets available from the current transport.
|
||||
|
@ -311,39 +305,7 @@ internal class RestoreViewModel(
|
|||
* the current device. If no applicable datasets exist, restoreSets will be null.
|
||||
*/
|
||||
override fun restoreSetsAvailable(restoreSets: Array<out RestoreSet>?) {
|
||||
check(continuation != null) { "Getting restore sets without continuation" }
|
||||
|
||||
val result = if (restoreSets == null || restoreSets.isEmpty()) {
|
||||
RestoreSetResult(app.getString(R.string.restore_set_empty_result))
|
||||
} else {
|
||||
val backupMetadata = restoreCoordinator.getAndClearBackupMetadata()
|
||||
if (backupMetadata == null) {
|
||||
Log.e(TAG, "RestoreCoordinator#getAndClearBackupMetadata() returned null")
|
||||
RestoreSetResult(app.getString(R.string.restore_set_error))
|
||||
} else {
|
||||
val restorableBackups = restoreSets.mapNotNull { set ->
|
||||
getRestorableBackup(set, backupMetadata[set.token])
|
||||
}
|
||||
if (restorableBackups.isEmpty()) {
|
||||
RestoreSetResult(app.getString(R.string.restore_set_empty_result))
|
||||
} else RestoreSetResult(restorableBackups)
|
||||
}
|
||||
}
|
||||
continuation.resume(result)
|
||||
}
|
||||
|
||||
private fun getRestorableBackup(set: RestoreSet, metadata: BackupMetadata?) = when {
|
||||
metadata == null -> {
|
||||
Log.e(TAG, "No metadata for token ${set.token}.")
|
||||
null
|
||||
}
|
||||
metadata.time == 0L -> {
|
||||
Log.d(TAG, "Ignoring RestoreSet with no last backup time: ${set.token}.")
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
RestorableBackup(set, metadata)
|
||||
}
|
||||
startRestore(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,16 +54,9 @@ internal class RestoreCoordinator(
|
|||
private var backupMetadata: LongSparseArray<BackupMetadata>? = null
|
||||
private val failedPackages = ArrayList<String>()
|
||||
|
||||
/**
|
||||
* Get the set of all backups currently available over this transport.
|
||||
*
|
||||
* @return Descriptions of the set of restore images available for this device,
|
||||
* or null if an error occurred (the attempt should be rescheduled).
|
||||
**/
|
||||
suspend fun getAvailableRestoreSets(): Array<RestoreSet>? {
|
||||
suspend fun getAvailableMetadata(): Map<Long, BackupMetadata>? {
|
||||
val availableBackups = plugin.getAvailableBackups() ?: return null
|
||||
val restoreSets = ArrayList<RestoreSet>()
|
||||
val metadataMap = LongSparseArray<BackupMetadata>()
|
||||
val metadataMap = HashMap<Long, BackupMetadata>()
|
||||
for (encryptedMetadata in availableBackups) {
|
||||
if (encryptedMetadata.error) continue
|
||||
check(encryptedMetadata.inputStream != null) {
|
||||
|
@ -74,9 +67,7 @@ internal class RestoreCoordinator(
|
|||
encryptedMetadata.inputStream,
|
||||
encryptedMetadata.token
|
||||
)
|
||||
metadataMap.put(encryptedMetadata.token, metadata)
|
||||
val set = RestoreSet(metadata.deviceName, metadata.deviceName, metadata.token)
|
||||
restoreSets.add(set)
|
||||
metadataMap[encryptedMetadata.token] = metadata
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error while getting restore set ${encryptedMetadata.token}", e)
|
||||
continue
|
||||
|
@ -93,9 +84,20 @@ internal class RestoreCoordinator(
|
|||
closeQuietly(encryptedMetadata.inputStream)
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Got available restore sets: $restoreSets")
|
||||
this.backupMetadata = metadataMap
|
||||
return restoreSets.toTypedArray()
|
||||
Log.i(TAG, "Got available metadata for tokens: ${metadataMap.keys}")
|
||||
return metadataMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of all backups currently available over this transport.
|
||||
*
|
||||
* @return Descriptions of the set of restore images available for this device,
|
||||
* or null if an error occurred (the attempt should be rescheduled).
|
||||
**/
|
||||
suspend fun getAvailableRestoreSets(): Array<RestoreSet>? {
|
||||
return getAvailableMetadata()?.map { (_, metadata) ->
|
||||
RestoreSet(metadata.deviceName, metadata.deviceName, metadata.token)
|
||||
}?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,7 +213,8 @@ internal class RestoreCoordinator(
|
|||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error finding restore data for $packageName.", e)
|
||||
failedPackages.add(packageName)
|
||||
return null
|
||||
// don't return null and cause abort here, but try next package
|
||||
return nextRestorePackage()
|
||||
}
|
||||
return RestoreDescription(packageName, type)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue