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 MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
|
||||||
|
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
|
||||||
|
const val GLOBAL_METADATA_KEY = "@meta@"
|
||||||
|
|
||||||
fun isDebugBuild() = Build.TYPE == "userdebug"
|
fun isDebugBuild() = Build.TYPE == "userdebug"
|
||||||
|
|
|
@ -6,6 +6,9 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
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.crypto.Crypto
|
||||||
import com.stevesoltys.seedvault.decodeBase64
|
import com.stevesoltys.seedvault.decodeBase64
|
||||||
import com.stevesoltys.seedvault.header.HeaderReader
|
import com.stevesoltys.seedvault.header.HeaderReader
|
||||||
|
@ -17,7 +20,11 @@ import javax.crypto.AEADBadTagException
|
||||||
|
|
||||||
private class KVRestoreState(
|
private class KVRestoreState(
|
||||||
internal val token: Long,
|
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
|
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.
|
* 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.
|
* 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) {
|
fun initializeState(token: Long, packageInfo: PackageInfo, pmPackageInfo: PackageInfo? = null) {
|
||||||
state = KVRestoreState(token, packageInfo)
|
state = KVRestoreState(token, packageInfo, pmPackageInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,8 +120,15 @@ internal class KVRestore(
|
||||||
// Decode the key filenames into keys then sort lexically by key
|
// Decode the key filenames into keys then sort lexically by key
|
||||||
val contents = ArrayList<DecodedKey>()
|
val contents = ArrayList<DecodedKey>()
|
||||||
for (recordKey in records) contents.add(DecodedKey(recordKey))
|
for (recordKey in records) contents.add(DecodedKey(recordKey))
|
||||||
contents.sort()
|
// remove keys that are not needed for single package @pm@ restore
|
||||||
return contents
|
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.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.collection.LongSparseArray
|
import androidx.collection.LongSparseArray
|
||||||
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||||
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
||||||
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
|
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
|
||||||
|
@ -21,7 +22,12 @@ import java.io.IOException
|
||||||
private class RestoreCoordinatorState(
|
private class RestoreCoordinatorState(
|
||||||
internal val token: Long,
|
internal val token: Long,
|
||||||
internal val packages: Iterator<PackageInfo>,
|
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
|
private val TAG = RestoreCoordinator::class.java.simpleName
|
||||||
|
|
||||||
|
@ -104,7 +110,14 @@ internal class RestoreCoordinator(
|
||||||
fun startRestore(token: Long, packages: Array<out PackageInfo>): Int {
|
fun startRestore(token: Long, packages: Array<out PackageInfo>): Int {
|
||||||
check(state == null) { "Started new restore with existing state" }
|
check(state == null) { "Started new restore with existing state" }
|
||||||
Log.i(TAG, "Start restore with ${packages.map { info -> info.packageName }}")
|
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()
|
failedPackages.clear()
|
||||||
return TRANSPORT_OK
|
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
|
// check key/value data first and if available, don't even check for full data
|
||||||
kv.hasDataForPackage(state.token, packageInfo) -> {
|
kv.hasDataForPackage(state.token, packageInfo) -> {
|
||||||
Log.i(TAG, "Found K/V data for $packageName.")
|
Log.i(TAG, "Found K/V data for $packageName.")
|
||||||
kv.initializeState(state.token, packageInfo)
|
kv.initializeState(state.token, packageInfo, state.pmPackageInfo)
|
||||||
state.currentPackage = packageName
|
state.currentPackage = packageName
|
||||||
TYPE_KEY_VALUE
|
TYPE_KEY_VALUE
|
||||||
}
|
}
|
||||||
|
@ -174,7 +187,7 @@ internal class RestoreCoordinator(
|
||||||
/**
|
/**
|
||||||
* Get the data for the application returned by [nextRestorePackage],
|
* Get the data for the application returned by [nextRestorePackage],
|
||||||
* if that method reported [TYPE_KEY_VALUE] as its delivery type.
|
* 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.
|
* @param data An open, writable file into which the key/value backup data should be stored.
|
||||||
* @return the same error codes as [startRestore].
|
* @return the same error codes as [startRestore].
|
||||||
|
|
Loading…
Reference in a new issue