From 783e676be2ceac77e262c14c74de37a5cba63923 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 3 Jan 2020 10:58:27 -0300 Subject: [PATCH] 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. --- .../java/com/stevesoltys/seedvault/App.kt | 2 ++ .../seedvault/transport/restore/KVRestore.kt | 26 +++++++++++++++---- .../transport/restore/RestoreCoordinator.kt | 21 ++++++++++++--- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index 3cebd5d3..bc4e03c2 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -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" diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt index 7142d85f..38c5d8b4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt @@ -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() 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 } /** diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt index 711a82fe..0ca8c373 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt @@ -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, - 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): 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].