Optimize the restore of a single application (e.g. auto restore)

This restores only the @pm@ keys that are really needed
and thus speeds up installation with auto restore considerably
when using cloud storage such as NextCloud for example.
This commit is contained in:
Torsten Grote 2020-01-03 10:58:27 -03:00
parent 43184f4d23
commit 783e676be2
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
3 changed files with 40 additions and 9 deletions

View file

@ -63,5 +63,7 @@ class App : Application() {
}
const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
const val GLOBAL_METADATA_KEY = "@meta@"
fun isDebugBuild() = Build.TYPE == "userdebug"

View file

@ -6,6 +6,9 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import com.stevesoltys.seedvault.ANCESTRAL_RECORD_KEY
import com.stevesoltys.seedvault.GLOBAL_METADATA_KEY
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.decodeBase64
import com.stevesoltys.seedvault.header.HeaderReader
@ -17,7 +20,11 @@ import javax.crypto.AEADBadTagException
private class KVRestoreState(
internal val token: Long,
internal val packageInfo: PackageInfo)
internal val packageInfo: PackageInfo,
/**
* Optional [PackageInfo] for single package restore, optimizes restore of @pm@
*/
internal val pmPackageInfo: PackageInfo?)
private val TAG = KVRestore::class.java.simpleName
@ -42,9 +49,11 @@ internal class KVRestore(
*
* It is possible that the system decides to not restore the package.
* Then a new state will be initialized right away without calling other methods.
*
* @param pmPackageInfo single optional [PackageInfo] to optimize restore of @pm@
*/
fun initializeState(token: Long, packageInfo: PackageInfo) {
state = KVRestoreState(token, packageInfo)
fun initializeState(token: Long, packageInfo: PackageInfo, pmPackageInfo: PackageInfo? = null) {
state = KVRestoreState(token, packageInfo, pmPackageInfo)
}
/**
@ -111,8 +120,15 @@ internal class KVRestore(
// Decode the key filenames into keys then sort lexically by key
val contents = ArrayList<DecodedKey>()
for (recordKey in records) contents.add(DecodedKey(recordKey))
contents.sort()
return contents
// remove keys that are not needed for single package @pm@ restore
val pmPackageName = state?.pmPackageInfo?.packageName
val sortedKeys = if (packageInfo.packageName == MAGIC_PACKAGE_MANAGER && pmPackageName != null) {
val keys = listOf(ANCESTRAL_RECORD_KEY, GLOBAL_METADATA_KEY, pmPackageName)
Log.d(TAG, "Single package restore, restrict restore keys to $pmPackageName")
contents.filterTo(ArrayList()) { it.key in keys }
} else contents
sortedKeys.sort()
return sortedKeys
}
/**

View file

@ -10,6 +10,7 @@ import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.collection.LongSparseArray
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.header.UnsupportedVersionException
import com.stevesoltys.seedvault.metadata.BackupMetadata
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
@ -21,7 +22,12 @@ import java.io.IOException
private class RestoreCoordinatorState(
internal val token: Long,
internal val packages: Iterator<PackageInfo>,
internal var currentPackage: String? = null)
/**
* Optional [PackageInfo] for single package restore, to reduce data needed to read for @pm@
*/
internal val pmPackageInfo: PackageInfo?) {
internal var currentPackage: String? = null
}
private val TAG = RestoreCoordinator::class.java.simpleName
@ -104,7 +110,14 @@ internal class RestoreCoordinator(
fun startRestore(token: Long, packages: Array<out PackageInfo>): Int {
check(state == null) { "Started new restore with existing state" }
Log.i(TAG, "Start restore with ${packages.map { info -> info.packageName }}")
state = RestoreCoordinatorState(token, packages.iterator())
// If there's only one package to restore (Auto Restore feature), add it to the state
val pmPackageInfo = if (packages.size == 2 && packages[0].packageName == MAGIC_PACKAGE_MANAGER) {
Log.d(TAG, "Optimize for single package restore of ${packages[1].packageName}")
packages[1]
} else null
state = RestoreCoordinatorState(token, packages.iterator(), pmPackageInfo)
failedPackages.clear()
return TRANSPORT_OK
}
@ -148,7 +161,7 @@ internal class RestoreCoordinator(
// check key/value data first and if available, don't even check for full data
kv.hasDataForPackage(state.token, packageInfo) -> {
Log.i(TAG, "Found K/V data for $packageName.")
kv.initializeState(state.token, packageInfo)
kv.initializeState(state.token, packageInfo, state.pmPackageInfo)
state.currentPackage = packageName
TYPE_KEY_VALUE
}
@ -174,7 +187,7 @@ internal class RestoreCoordinator(
/**
* Get the data for the application returned by [nextRestorePackage],
* if that method reported [TYPE_KEY_VALUE] as its delivery type.
* If the package has only TYPE_FULL_STREAM data, then this method will return an error.
* If the package has only [TYPE_FULL_STREAM] data, then this method will return an error.
*
* @param data An open, writable file into which the key/value backup data should be stored.
* @return the same error codes as [startRestore].