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:
parent
43184f4d23
commit
783e676be2
3 changed files with 40 additions and 9 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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].
|
||||
|
|
Loading…
Reference in a new issue