Make RestoreCoordinator use the new storage API with salt and backup type
This breaks restores until all the other required changed have been implemented.
This commit is contained in:
parent
75cf014e5d
commit
4bdaaa0ce9
6 changed files with 159 additions and 25 deletions
|
@ -13,8 +13,10 @@ import android.os.ParcelFileDescriptor
|
|||
import android.util.Log
|
||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.crypto.Crypto
|
||||
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
||||
import com.stevesoltys.seedvault.metadata.BackupType
|
||||
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
|
||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||
import com.stevesoltys.seedvault.metadata.MetadataReader
|
||||
|
@ -40,6 +42,7 @@ private val TAG = RestoreCoordinator::class.java.simpleName
|
|||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
internal class RestoreCoordinator(
|
||||
private val context: Context,
|
||||
private val crypto: Crypto,
|
||||
private val settingsManager: SettingsManager,
|
||||
private val metadataManager: MetadataManager,
|
||||
private val notificationManager: BackupNotificationManager,
|
||||
|
@ -193,22 +196,59 @@ internal class RestoreCoordinator(
|
|||
val state = this.state ?: throw IllegalStateException("no state")
|
||||
|
||||
if (!state.packages.hasNext()) return NO_MORE_PACKAGES
|
||||
val version = state.backupMetadata.version
|
||||
val packageInfo = state.packages.next()
|
||||
val packageName = packageInfo.packageName
|
||||
val version = state.backupMetadata.version
|
||||
if (version == 0.toByte()) return nextRestorePackageV0(state, packageInfo)
|
||||
|
||||
val packageName = packageInfo.packageName
|
||||
val type = try {
|
||||
when (state.backupMetadata.packageMetadataMap[packageName]?.backupType) {
|
||||
BackupType.KV -> {
|
||||
val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName)
|
||||
if (plugin.hasData(state.token, name)) {
|
||||
Log.i(TAG, "Found K/V data for $packageName.")
|
||||
kv.initializeState(version, state.token, packageInfo, state.pmPackageInfo)
|
||||
state.currentPackage = packageName
|
||||
TYPE_KEY_VALUE
|
||||
} else throw IOException("No data found for $packageName. Skipping.")
|
||||
}
|
||||
BackupType.FULL -> {
|
||||
val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName)
|
||||
if (plugin.hasData(state.token, name)) {
|
||||
Log.i(TAG, "Found full backup data for $packageName.")
|
||||
full.initializeState(version, state.token, packageInfo)
|
||||
state.currentPackage = packageName
|
||||
TYPE_FULL_STREAM
|
||||
} else throw IOException("No data found for $packageName. Skipping.")
|
||||
}
|
||||
null -> throw IOException("No backup type found for $packageName. Skipping.")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error finding restore data for $packageName.", e)
|
||||
failedPackages.add(packageName)
|
||||
// don't return null and cause abort here, but try next package
|
||||
return nextRestorePackage()
|
||||
}
|
||||
return RestoreDescription(packageName, type)
|
||||
}
|
||||
|
||||
private suspend fun nextRestorePackageV0(
|
||||
state: RestoreCoordinatorState,
|
||||
packageInfo: PackageInfo
|
||||
): RestoreDescription? {
|
||||
val packageName = packageInfo.packageName
|
||||
val type = try {
|
||||
when {
|
||||
// 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(version, state.token, packageInfo, state.pmPackageInfo)
|
||||
kv.initializeState(0x00, state.token, packageInfo, state.pmPackageInfo)
|
||||
state.currentPackage = packageName
|
||||
TYPE_KEY_VALUE
|
||||
}
|
||||
full.hasDataForPackage(state.token, packageInfo) -> {
|
||||
Log.i(TAG, "Found full backup data for $packageName.")
|
||||
full.initializeState(version, state.token, packageInfo)
|
||||
full.initializeState(0x00, state.token, packageInfo)
|
||||
state.currentPackage = packageName
|
||||
TYPE_FULL_STREAM
|
||||
}
|
||||
|
|
|
@ -7,5 +7,7 @@ val restoreModule = module {
|
|||
single { OutputFactory() }
|
||||
single { KVRestore(get<RestorePlugin>().kvRestorePlugin, get(), get(), get()) }
|
||||
single { FullRestore(get<RestorePlugin>().fullRestorePlugin, get(), get(), get()) }
|
||||
single { RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
single {
|
||||
RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||
private val restore = RestoreCoordinator(
|
||||
context,
|
||||
crypto,
|
||||
settingsManager,
|
||||
metadataManager,
|
||||
notificationManager,
|
||||
|
@ -186,7 +187,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||
|
||||
// find data for K/V backup
|
||||
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name
|
||||
coEvery { backupPlugin.hasData(token, name) } returns true
|
||||
|
||||
val restoreDescription = restore.nextRestorePackage() ?: fail()
|
||||
assertEquals(packageInfo.packageName, restoreDescription.packageName)
|
||||
|
@ -262,7 +264,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||
|
||||
// find data for K/V backup
|
||||
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name
|
||||
coEvery { backupPlugin.hasData(token, name) } returns true
|
||||
|
||||
val restoreDescription = restore.nextRestorePackage() ?: fail()
|
||||
assertEquals(packageInfo.packageName, restoreDescription.packageName)
|
||||
|
@ -288,6 +291,11 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
|
||||
@Test
|
||||
fun `test full backup and restore with two chunks`() = runBlocking {
|
||||
// package is of type FULL
|
||||
val packageMetadata = metadata.packageMetadataMap[packageInfo.packageName]!!
|
||||
metadata.packageMetadataMap[packageInfo.packageName] =
|
||||
packageMetadata.copy(backupType = BackupType.FULL)
|
||||
|
||||
// return streams from plugin and app data
|
||||
val bOutputStream = ByteArrayOutputStream()
|
||||
val bInputStream = ByteArrayInputStream(appData)
|
||||
|
@ -327,9 +335,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
|||
restore.beforeStartRestore(metadata)
|
||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||
|
||||
// find data only for full backup
|
||||
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns false
|
||||
coEvery { fullRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
|
||||
// finds data for full backup
|
||||
every { crypto.getNameForPackage(metadata.salt, packageInfo.packageName) } returns name
|
||||
coEvery { backupPlugin.hasData(token, name) } returns true
|
||||
|
||||
val restoreDescription = restore.nextRestorePackage() ?: fail()
|
||||
assertEquals(packageInfo.packageName, restoreDescription.packageName)
|
||||
|
|
|
@ -13,8 +13,11 @@ import com.stevesoltys.seedvault.crypto.Crypto
|
|||
import com.stevesoltys.seedvault.getRandomBase64
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
||||
import com.stevesoltys.seedvault.metadata.BackupType
|
||||
import com.stevesoltys.seedvault.metadata.METADATA_SALT_SIZE
|
||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
@ -50,8 +53,13 @@ internal abstract class TransportTest {
|
|||
salt = getRandomBase64(METADATA_SALT_SIZE),
|
||||
androidVersion = Random.nextInt(),
|
||||
androidIncremental = getRandomString(),
|
||||
deviceName = getRandomString()
|
||||
deviceName = getRandomString(),
|
||||
packageMetadataMap = PackageMetadataMap().apply {
|
||||
put(packageInfo.packageName, PackageMetadata(backupType = BackupType.KV))
|
||||
}
|
||||
)
|
||||
protected val name = getRandomString(12)
|
||||
protected val name2 = getRandomString(23)
|
||||
|
||||
init {
|
||||
mockkStatic(Log::class)
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.os.ParcelFileDescriptor
|
|||
import com.stevesoltys.seedvault.coAssertThrows
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.header.VERSION
|
||||
import com.stevesoltys.seedvault.metadata.BackupType
|
||||
import com.stevesoltys.seedvault.metadata.MetadataReader
|
||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||
import com.stevesoltys.seedvault.settings.Storage
|
||||
|
@ -45,6 +46,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
|
||||
private val restore = RestoreCoordinator(
|
||||
context,
|
||||
crypto,
|
||||
settingsManager,
|
||||
metadataManager,
|
||||
notificationManager,
|
||||
|
@ -66,6 +68,11 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
private val packageName = packageInfo.packageName
|
||||
private val storageName = getRandomString()
|
||||
|
||||
init {
|
||||
metadata.packageMetadataMap[packageInfo2.packageName] =
|
||||
PackageMetadata(backupType = BackupType.FULL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking {
|
||||
val encryptedMetadata = EncryptedMetadata(token) { inputStream }
|
||||
|
@ -210,37 +217,69 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `nextRestorePackage() returns KV description and takes precedence`() = runBlocking {
|
||||
fun `nextRestorePackage() returns KV description`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata)
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||
coEvery { plugin.hasData(token, name) } returns true
|
||||
every { kv.initializeState(VERSION, token, packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `v0 nextRestorePackage() returns KV description and takes precedence`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { kv.initializeState(0x00, token, packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nextRestorePackage() returns full description if no KV data found`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata)
|
||||
fun `v0 nextRestorePackage() returns full description if no KV data found`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
|
||||
coEvery { full.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { full.initializeState(VERSION, token, packageInfo) } just Runs
|
||||
every { full.initializeState(0x00, token, packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo.packageName, TYPE_FULL_STREAM)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nextRestorePackage() returns NO_MORE_PACKAGES if data found`() = runBlocking {
|
||||
fun `nextRestorePackage() returns NO_MORE_PACKAGES if data not found`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata)
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
restore.startRestore(token, packageInfoArray2)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
|
||||
coEvery { full.hasDataForPackage(token, packageInfo) } returns false
|
||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||
coEvery { plugin.hasData(token, name) } returns false
|
||||
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
|
||||
coEvery { plugin.hasData(token, name2) } returns false
|
||||
|
||||
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nextRestorePackage() tries next package if one has no backup type()`() = runBlocking {
|
||||
metadata.packageMetadataMap[packageName] =
|
||||
metadata.packageMetadataMap[packageName]!!.copy(backupType = null)
|
||||
restore.beforeStartRestore(metadata)
|
||||
restore.startRestore(token, packageInfoArray2)
|
||||
|
||||
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
|
||||
coEvery { plugin.hasData(token, name2) } returns true
|
||||
every { full.initializeState(VERSION, token, packageInfo2) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
|
||||
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
|
||||
}
|
||||
|
@ -250,15 +289,38 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
restore.beforeStartRestore(metadata)
|
||||
restore.startRestore(token, packageInfoArray2)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||
coEvery { plugin.hasData(token, name) } returns true
|
||||
every { kv.initializeState(VERSION, token, packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
|
||||
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
|
||||
coEvery { plugin.hasData(token, name2) } returns true
|
||||
every { full.initializeState(VERSION, token, packageInfo2) } just Runs
|
||||
|
||||
val expected2 =
|
||||
RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM)
|
||||
assertEquals(expected2, restore.nextRestorePackage())
|
||||
|
||||
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `v0 nextRestorePackage() returns all packages from startRestore()`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
||||
restore.startRestore(token, packageInfoArray2)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||
every { kv.initializeState(0.toByte(), token, packageInfo) } just Runs
|
||||
|
||||
val expected = RestoreDescription(packageInfo.packageName, TYPE_KEY_VALUE)
|
||||
assertEquals(expected, restore.nextRestorePackage())
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo2) } returns false
|
||||
coEvery { full.hasDataForPackage(token, packageInfo2) } returns true
|
||||
every { full.initializeState(VERSION, token, packageInfo2) } just Runs
|
||||
every { full.initializeState(0.toByte(), token, packageInfo2) } just Runs
|
||||
|
||||
val expected2 = RestoreDescription(packageInfo2.packageName, TYPE_FULL_STREAM)
|
||||
assertEquals(expected2, restore.nextRestorePackage())
|
||||
|
@ -267,8 +329,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `when kv#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata)
|
||||
fun `v0 when kv#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } throws IOException()
|
||||
|
@ -277,8 +339,21 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `when full#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
||||
fun `when plugin#hasData() throws, it tries next package`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata)
|
||||
restore.startRestore(token, packageInfoArray2)
|
||||
|
||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||
coEvery { plugin.hasData(token, name) } returns false
|
||||
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
|
||||
coEvery { plugin.hasData(token, name2) } throws IOException()
|
||||
|
||||
assertEquals(NO_MORE_PACKAGES, restore.nextRestorePackage())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `v0 when full#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
||||
restore.startRestore(token, packageInfoArray)
|
||||
|
||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
|
||||
|
|
|
@ -55,6 +55,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
|||
FullRestore(fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
||||
private val restore = RestoreCoordinator(
|
||||
context,
|
||||
crypto,
|
||||
settingsManager,
|
||||
metadataManager,
|
||||
notificationManager,
|
||||
|
|
Loading…
Reference in a new issue