Prepare restore backup loading for v2
This commit is contained in:
parent
8ce79f4195
commit
83708d9403
20 changed files with 274 additions and 168 deletions
|
@ -10,7 +10,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
import com.stevesoltys.seedvault.backend.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.backend.getAvailableBackups
|
import com.stevesoltys.seedvault.backend.getAvailableBackupFileHandles
|
||||||
import com.stevesoltys.seedvault.backend.saf.DocumentsProviderLegacyPlugin
|
import com.stevesoltys.seedvault.backend.saf.DocumentsProviderLegacyPlugin
|
||||||
import com.stevesoltys.seedvault.backend.saf.DocumentsStorage
|
import com.stevesoltys.seedvault.backend.saf.DocumentsStorage
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
|
@ -92,7 +92,7 @@ class PluginTest : KoinComponent {
|
||||||
@Test
|
@Test
|
||||||
fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) {
|
fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) {
|
||||||
// no backups available initially
|
// no backups available initially
|
||||||
assertEquals(0, backend.getAvailableBackups()?.toList()?.size)
|
assertEquals(0, backend.getAvailableBackupFileHandles().toList().size)
|
||||||
|
|
||||||
// prepare returned tokens requested when initializing device
|
// prepare returned tokens requested when initializing device
|
||||||
every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1)
|
every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1)
|
||||||
|
@ -102,17 +102,17 @@ class PluginTest : KoinComponent {
|
||||||
.writeAndClose(getRandomByteArray())
|
.writeAndClose(getRandomByteArray())
|
||||||
|
|
||||||
// one backup available now
|
// one backup available now
|
||||||
assertEquals(1, backend.getAvailableBackups()?.toList()?.size)
|
assertEquals(1, backend.getAvailableBackupFileHandles().toList().size)
|
||||||
|
|
||||||
// initializing again (with another restore set) does add a restore set
|
// initializing again (with another restore set) does add a restore set
|
||||||
backend.save(LegacyAppBackupFile.Metadata(token + 1))
|
backend.save(LegacyAppBackupFile.Metadata(token + 1))
|
||||||
.writeAndClose(getRandomByteArray())
|
.writeAndClose(getRandomByteArray())
|
||||||
assertEquals(2, backend.getAvailableBackups()?.toList()?.size)
|
assertEquals(2, backend.getAvailableBackupFileHandles().toList().size)
|
||||||
|
|
||||||
// initializing again (without new restore set) doesn't change number of restore sets
|
// initializing again (without new restore set) doesn't change number of restore sets
|
||||||
backend.save(LegacyAppBackupFile.Metadata(token + 1))
|
backend.save(LegacyAppBackupFile.Metadata(token + 1))
|
||||||
.writeAndClose(getRandomByteArray())
|
.writeAndClose(getRandomByteArray())
|
||||||
assertEquals(2, backend.getAvailableBackups()?.toList()?.size)
|
assertEquals(2, backend.getAvailableBackupFileHandles().toList().size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -124,27 +124,26 @@ class PluginTest : KoinComponent {
|
||||||
backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata)
|
backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata)
|
||||||
|
|
||||||
// get available backups, expect only one with our token and no error
|
// get available backups, expect only one with our token and no error
|
||||||
var availableBackups = backend.getAvailableBackups()?.toList()
|
var availableBackups = backend.getAvailableBackupFileHandles().toList()
|
||||||
check(availableBackups != null)
|
|
||||||
assertEquals(1, availableBackups.size)
|
assertEquals(1, availableBackups.size)
|
||||||
assertEquals(token, availableBackups[0].token)
|
var backupHandle = availableBackups[0] as LegacyAppBackupFile.Metadata
|
||||||
|
assertEquals(token, backupHandle.token)
|
||||||
|
|
||||||
// read metadata matches what was written earlier
|
// read metadata matches what was written earlier
|
||||||
assertReadEquals(metadata, availableBackups[0].inputStreamRetriever())
|
assertReadEquals(metadata, backend.load(backupHandle))
|
||||||
|
|
||||||
// initializing again (without changing storage) keeps restore set with same token
|
// initializing again (without changing storage) keeps restore set with same token
|
||||||
backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata)
|
backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata)
|
||||||
availableBackups = backend.getAvailableBackups()?.toList()
|
availableBackups = backend.getAvailableBackupFileHandles().toList()
|
||||||
check(availableBackups != null)
|
|
||||||
assertEquals(1, availableBackups.size)
|
assertEquals(1, availableBackups.size)
|
||||||
assertEquals(token, availableBackups[0].token)
|
backupHandle = availableBackups[0] as LegacyAppBackupFile.Metadata
|
||||||
|
assertEquals(token, backupHandle.token)
|
||||||
|
|
||||||
// metadata hasn't changed
|
// metadata hasn't changed
|
||||||
assertReadEquals(metadata, availableBackups[0].inputStreamRetriever())
|
assertReadEquals(metadata, backend.load(backupHandle))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Suppress("Deprecation")
|
|
||||||
fun v0testApkWriteRead() = runBlocking {
|
fun v0testApkWriteRead() = runBlocking {
|
||||||
// initialize storage with given token
|
// initialize storage with given token
|
||||||
initStorage(token)
|
initStorage(token)
|
||||||
|
|
|
@ -5,37 +5,29 @@
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.backend
|
package com.stevesoltys.seedvault.backend
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import at.bitfire.dav4jvm.exception.HttpException
|
import at.bitfire.dav4jvm.exception.HttpException
|
||||||
|
import org.calyxos.seedvault.core.backends.AppBackupFileType
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
|
import org.calyxos.seedvault.core.backends.FileHandle
|
||||||
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
|
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
suspend fun Backend.getMetadataOutputStream(token: Long): OutputStream {
|
suspend fun Backend.getMetadataOutputStream(token: Long): OutputStream {
|
||||||
return save(LegacyAppBackupFile.Metadata(token))
|
return save(LegacyAppBackupFile.Metadata(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Backend.getAvailableBackups(): Sequence<EncryptedMetadata>? {
|
suspend fun Backend.getAvailableBackupFileHandles(): List<FileHandle> {
|
||||||
return try {
|
// v1 get all restore set tokens in root folder that have a metadata file
|
||||||
// get all restore set tokens in root folder that have a metadata file
|
// v2 get all snapshots in all repository folders
|
||||||
val handles = ArrayList<LegacyAppBackupFile.Metadata>()
|
return ArrayList<FileHandle>().apply {
|
||||||
list(null, LegacyAppBackupFile.Metadata::class) { fileInfo ->
|
list(
|
||||||
val handle = fileInfo.fileHandle as LegacyAppBackupFile.Metadata
|
null,
|
||||||
handles.add(handle)
|
AppBackupFileType.Snapshot::class,
|
||||||
|
LegacyAppBackupFile.Metadata::class,
|
||||||
|
) { fileInfo ->
|
||||||
|
add(fileInfo.fileHandle)
|
||||||
}
|
}
|
||||||
val handleIterator = handles.iterator()
|
|
||||||
return generateSequence {
|
|
||||||
if (!handleIterator.hasNext()) return@generateSequence null // end sequence
|
|
||||||
val handle = handleIterator.next()
|
|
||||||
EncryptedMetadata(handle.token) {
|
|
||||||
load(handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("SafBackend", "Error getting available backups: ", e)
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,5 +41,3 @@ fun Exception.isOutOfSpace(): Boolean {
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EncryptedMetadata(val token: Long, val inputStreamRetriever: suspend () -> InputStream)
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
import com.stevesoltys.seedvault.backend.getAvailableBackups
|
import com.stevesoltys.seedvault.backend.getAvailableBackupFileHandles
|
||||||
import com.stevesoltys.seedvault.isMassStorage
|
import com.stevesoltys.seedvault.isMassStorage
|
||||||
import com.stevesoltys.seedvault.settings.FlashDrive
|
import com.stevesoltys.seedvault.settings.FlashDrive
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
|
@ -59,9 +59,8 @@ internal class SafHandler(
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
suspend fun hasAppBackup(safProperties: SafProperties): Boolean {
|
suspend fun hasAppBackup(safProperties: SafProperties): Boolean {
|
||||||
val appPlugin = backendFactory.createSafBackend(safProperties)
|
val backend = backendFactory.createSafBackend(safProperties)
|
||||||
val backups = appPlugin.getAvailableBackups()
|
return backend.getAvailableBackupFileHandles().isNotEmpty()
|
||||||
return backups != null && backups.iterator().hasNext()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(safProperties: SafProperties) {
|
fun save(safProperties: SafProperties) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
import com.stevesoltys.seedvault.backend.getAvailableBackups
|
import com.stevesoltys.seedvault.backend.getAvailableBackupFileHandles
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
@ -81,8 +81,7 @@ internal class WebDavHandler(
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
suspend fun hasAppBackup(backend: Backend): Boolean {
|
suspend fun hasAppBackup(backend: Backend): Boolean {
|
||||||
val backups = backend.getAvailableBackups()
|
return backend.getAvailableBackupFileHandles().isNotEmpty()
|
||||||
return backups != null && backups.iterator().hasNext()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(properties: WebDavProperties) {
|
fun save(properties: WebDavProperties) {
|
||||||
|
|
|
@ -30,6 +30,23 @@ data class BackupMetadata(
|
||||||
internal var d2dBackup: Boolean = false,
|
internal var d2dBackup: Boolean = false,
|
||||||
internal val packageMetadataMap: PackageMetadataMap = PackageMetadataMap(),
|
internal val packageMetadataMap: PackageMetadataMap = PackageMetadataMap(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromSnapshot(s: Snapshot) = BackupMetadata(
|
||||||
|
version = s.version.toByte(),
|
||||||
|
token = s.token,
|
||||||
|
salt = "",
|
||||||
|
time = s.token,
|
||||||
|
androidVersion = s.sdkInt,
|
||||||
|
androidIncremental = s.androidIncremental,
|
||||||
|
deviceName = s.name,
|
||||||
|
d2dBackup = s.d2D,
|
||||||
|
packageMetadataMap = s.appsMap.mapValues { (_, app) ->
|
||||||
|
PackageMetadata.fromSnapshot(app)
|
||||||
|
} as PackageMetadataMap
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val size: Long
|
val size: Long
|
||||||
get() = packageMetadataMap.values.sumOf { m ->
|
get() = packageMetadataMap.values.sumOf { m ->
|
||||||
(m.size ?: 0L) + (m.splits?.sumOf { it.size ?: 0L } ?: 0L)
|
(m.size ?: 0L) + (m.splits?.sumOf { it.size ?: 0L } ?: 0L)
|
||||||
|
|
|
@ -23,12 +23,13 @@ import com.stevesoltys.seedvault.BackupMonitor
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.NO_DATA_END_SENTINEL
|
import com.stevesoltys.seedvault.NO_DATA_END_SENTINEL
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState
|
import com.stevesoltys.seedvault.metadata.PackageState
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
|
||||||
import com.stevesoltys.seedvault.restore.install.isInstalled
|
import com.stevesoltys.seedvault.restore.install.isInstalled
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
import com.stevesoltys.seedvault.transport.TRANSPORT_ID
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
||||||
import com.stevesoltys.seedvault.ui.AppBackupState
|
import com.stevesoltys.seedvault.ui.AppBackupState
|
||||||
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED
|
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED
|
||||||
|
@ -263,20 +264,19 @@ internal class AppDataRestoreManager(
|
||||||
/**
|
/**
|
||||||
* Restore the next chunk of packages.
|
* Restore the next chunk of packages.
|
||||||
*
|
*
|
||||||
* We need to restore in chunks, otherwise [BackupTransport.startRestore] in the
|
* We need to restore packages in chunks, otherwise [BackupTransport.startRestore] in the
|
||||||
* framework's [PerformUnifiedRestoreTask] may fail due to an oversize Binder
|
* framework's [PerformUnifiedRestoreTask] may fail due to an oversize Binder transaction,
|
||||||
* transaction, causing the entire restoration to fail.
|
* causing the entire restoration to fail due to too many package names.
|
||||||
*/
|
*/
|
||||||
private fun restoreNextPackages() {
|
private fun restoreNextPackages() {
|
||||||
// Make sure metadata for selected backup is cached before starting each chunk.
|
// Make sure metadata for selected backup is cached before starting each chunk.
|
||||||
val backupMetadata = restorableBackup.backupMetadata
|
restoreCoordinator.beforeStartRestore(restorableBackup)
|
||||||
restoreCoordinator.beforeStartRestore(backupMetadata)
|
|
||||||
|
|
||||||
val nextChunkIndex = (packageIndex + PACKAGES_PER_CHUNK).coerceAtMost(packages.size)
|
val nextChunkIndex = (packageIndex + PACKAGES_PER_CHUNK).coerceAtMost(packages.size)
|
||||||
val packageChunk = packages.subList(packageIndex, nextChunkIndex).toTypedArray()
|
val packageChunk = packages.subList(packageIndex, nextChunkIndex).toTypedArray()
|
||||||
packageIndex += packageChunk.size
|
packageIndex += packageChunk.size
|
||||||
|
|
||||||
val token = backupMetadata.token
|
val token = restorableBackup.token
|
||||||
val result = session.restorePackages(token, this, packageChunk, monitor)
|
val result = session.restorePackages(token, this, packageChunk, monitor)
|
||||||
|
|
||||||
@Suppress("UNRESOLVED_REFERENCE") // BackupManager.SUCCESS
|
@Suppress("UNRESOLVED_REFERENCE") // BackupManager.SUCCESS
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM
|
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM
|
||||||
import com.stevesoltys.seedvault.ui.systemData
|
import com.stevesoltys.seedvault.ui.systemData
|
||||||
import com.stevesoltys.seedvault.worker.IconManager
|
import com.stevesoltys.seedvault.worker.IconManager
|
||||||
|
@ -68,21 +69,23 @@ internal class AppSelectionManager(
|
||||||
val name = context.getString(data.nameRes)
|
val name = context.getString(data.nameRes)
|
||||||
SelectableAppItem(packageName, metadata.copy(name = name), true)
|
SelectableAppItem(packageName, metadata.copy(name = name), true)
|
||||||
}
|
}
|
||||||
val systemItem = SelectableAppItem(
|
if (restorableBackup.packageMetadataMap.isNotEmpty()) {
|
||||||
packageName = PACKAGE_NAME_SYSTEM,
|
val systemItem = SelectableAppItem(
|
||||||
metadata = PackageMetadata(
|
packageName = PACKAGE_NAME_SYSTEM,
|
||||||
time = restorableBackup.packageMetadataMap.values.maxOf {
|
metadata = PackageMetadata(
|
||||||
if (it.system) it.time else -1
|
time = restorableBackup.packageMetadataMap.values.maxOf {
|
||||||
},
|
if (it.system) it.time else -1
|
||||||
size = restorableBackup.packageMetadataMap.values.sumOf {
|
},
|
||||||
if (it.system) it.size ?: 0L else 0L
|
size = restorableBackup.packageMetadataMap.values.sumOf {
|
||||||
},
|
if (it.system) it.size ?: 0L else 0L
|
||||||
system = true,
|
},
|
||||||
name = context.getString(R.string.backup_system_apps),
|
system = true,
|
||||||
),
|
name = context.getString(R.string.backup_system_apps),
|
||||||
selected = isSetupWizard,
|
),
|
||||||
)
|
selected = isSetupWizard,
|
||||||
items.add(0, systemItem)
|
)
|
||||||
|
items.add(0, systemItem)
|
||||||
|
}
|
||||||
items.addAll(0, systemDataItems)
|
items.addAll(0, systemDataItems)
|
||||||
selectedApps.value =
|
selectedApps.value =
|
||||||
SelectedAppsState(apps = items, allSelected = isSetupWizard, iconsLoaded = false)
|
SelectedAppsState(apps = items, allSelected = isSetupWizard, iconsLoaded = false)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView.Adapter
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.restore.RestoreSetAdapter.RestoreSetViewHolder
|
import com.stevesoltys.seedvault.restore.RestoreSetAdapter.RestoreSetViewHolder
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
|
|
||||||
internal class RestoreSetAdapter(
|
internal class RestoreSetAdapter(
|
||||||
private val listener: RestorableBackupClickListener,
|
private val listener: RestorableBackupClickListener,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import android.widget.TextView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||||
|
|
||||||
class RestoreSetFragment : Fragment() {
|
class RestoreSetFragment : Fragment() {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import android.app.Application
|
||||||
import android.app.backup.IBackupManager
|
import android.app.backup.IBackupManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
import androidx.appcompat.content.res.AppCompatResources.getDrawable
|
import androidx.appcompat.content.res.AppCompatResources.getDrawable
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
@ -17,8 +16,8 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.crypto.KeyManager
|
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
|
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||||
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
|
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
|
||||||
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
|
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
|
||||||
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES
|
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES
|
||||||
|
@ -30,6 +29,9 @@ import com.stevesoltys.seedvault.restore.install.InstallIntentCreator
|
||||||
import com.stevesoltys.seedvault.restore.install.InstallResult
|
import com.stevesoltys.seedvault.restore.install.InstallResult
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.storage.StorageRestoreService
|
import com.stevesoltys.seedvault.storage.StorageRestoreService
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackupResult.ErrorResult
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackupResult.SuccessResult
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
||||||
import com.stevesoltys.seedvault.ui.LiveEvent
|
import com.stevesoltys.seedvault.ui.LiveEvent
|
||||||
import com.stevesoltys.seedvault.ui.MutableLiveEvent
|
import com.stevesoltys.seedvault.ui.MutableLiveEvent
|
||||||
|
@ -106,20 +108,11 @@ internal class RestoreViewModel(
|
||||||
private var storedSnapshot: StoredSnapshot? = null
|
private var storedSnapshot: StoredSnapshot? = null
|
||||||
|
|
||||||
internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
|
internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
|
||||||
val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) ->
|
val result = when (val backups = restoreCoordinator.getAvailableBackups()) {
|
||||||
when (metadata.time) {
|
is ErrorResult -> RestoreSetResult(
|
||||||
0L -> {
|
app.getString(R.string.restore_set_error) + "\n\n${backups.e}"
|
||||||
Log.d(TAG, "Ignoring RestoreSet with no last backup time: $token.")
|
)
|
||||||
null
|
is SuccessResult -> RestoreSetResult(backups.backups)
|
||||||
}
|
|
||||||
|
|
||||||
else -> RestorableBackup(metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val result = when {
|
|
||||||
backups == null -> RestoreSetResult(app.getString(R.string.restore_set_error))
|
|
||||||
backups.isEmpty() -> RestoreSetResult(app.getString(R.string.restore_set_empty_result))
|
|
||||||
else -> RestoreSetResult(backups)
|
|
||||||
}
|
}
|
||||||
mRestoreSetResults.postValue(result)
|
mRestoreSetResults.postValue(result)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import com.stevesoltys.seedvault.crypto.Crypto
|
||||||
import com.stevesoltys.seedvault.encodeBase64
|
import com.stevesoltys.seedvault.encodeBase64
|
||||||
import com.stevesoltys.seedvault.metadata.ApkSplit
|
import com.stevesoltys.seedvault.metadata.ApkSplit
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||||
import com.stevesoltys.seedvault.restore.RestorableBackup
|
|
||||||
import com.stevesoltys.seedvault.restore.RestoreService
|
import com.stevesoltys.seedvault.restore.RestoreService
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2020 The Calyx Institute
|
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.stevesoltys.seedvault.restore
|
package com.stevesoltys.seedvault.transport.restore
|
||||||
|
|
||||||
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||||
import com.stevesoltys.seedvault.proto.Snapshot
|
import com.stevesoltys.seedvault.proto.Snapshot
|
||||||
|
|
||||||
|
sealed class RestorableBackupResult {
|
||||||
|
data class ErrorResult(val e: Exception?) : RestorableBackupResult()
|
||||||
|
data class SuccessResult(val backups: List<RestorableBackup>) : RestorableBackupResult()
|
||||||
|
}
|
||||||
|
|
||||||
data class RestorableBackup(
|
data class RestorableBackup(
|
||||||
val backupMetadata: BackupMetadata,
|
val backupMetadata: BackupMetadata,
|
||||||
val repoId: String? = null,
|
val repoId: String? = null,
|
||||||
val snapshot: Snapshot? = null,
|
val snapshot: Snapshot? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
constructor(repoId: String, snapshot: Snapshot) : this(
|
||||||
|
backupMetadata = BackupMetadata.fromSnapshot(snapshot),
|
||||||
|
repoId = repoId,
|
||||||
|
snapshot = snapshot,
|
||||||
|
)
|
||||||
|
|
||||||
val name: String
|
val name: String
|
||||||
get() = backupMetadata.deviceName
|
get() = backupMetadata.deviceName
|
||||||
|
|
||||||
|
@ -30,8 +41,9 @@ data class RestorableBackup(
|
||||||
val time: Long
|
val time: Long
|
||||||
get() = backupMetadata.time
|
get() = backupMetadata.time
|
||||||
|
|
||||||
val size: Long?
|
val size: Long
|
||||||
get() = backupMetadata.size
|
get() = snapshot?.blobsMap?.values?.sumOf { it.uncompressedLength.toLong() }
|
||||||
|
?: backupMetadata.size
|
||||||
|
|
||||||
val deviceName: String
|
val deviceName: String
|
||||||
get() = backupMetadata.deviceName
|
get() = backupMetadata.deviceName
|
|
@ -18,20 +18,22 @@ import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
|
import com.stevesoltys.seedvault.backend.getAvailableBackupFileHandles
|
||||||
import com.stevesoltys.seedvault.crypto.Crypto
|
import com.stevesoltys.seedvault.crypto.Crypto
|
||||||
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||||
import com.stevesoltys.seedvault.metadata.BackupMetadata
|
|
||||||
import com.stevesoltys.seedvault.metadata.BackupType
|
import com.stevesoltys.seedvault.metadata.BackupType
|
||||||
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
|
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataReader
|
import com.stevesoltys.seedvault.metadata.MetadataReader
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
import com.stevesoltys.seedvault.proto.Snapshot
|
||||||
import com.stevesoltys.seedvault.backend.getAvailableBackups
|
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS
|
import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS
|
||||||
import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS
|
import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
|
import org.calyxos.seedvault.core.backends.AppBackupFileType
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
|
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +51,7 @@ private data class RestoreCoordinatorState(
|
||||||
* Optional [PackageInfo] for single package restore, to reduce data needed to read for @pm@
|
* Optional [PackageInfo] for single package restore, to reduce data needed to read for @pm@
|
||||||
*/
|
*/
|
||||||
val autoRestorePackageInfo: PackageInfo?,
|
val autoRestorePackageInfo: PackageInfo?,
|
||||||
val backupMetadata: BackupMetadata,
|
val backup: RestorableBackup,
|
||||||
) {
|
) {
|
||||||
var currentPackage: String? = null
|
var currentPackage: String? = null
|
||||||
}
|
}
|
||||||
|
@ -63,6 +65,7 @@ internal class RestoreCoordinator(
|
||||||
private val metadataManager: MetadataManager,
|
private val metadataManager: MetadataManager,
|
||||||
private val notificationManager: BackupNotificationManager,
|
private val notificationManager: BackupNotificationManager,
|
||||||
private val backendManager: BackendManager,
|
private val backendManager: BackendManager,
|
||||||
|
private val loader: Loader,
|
||||||
private val kv: KVRestore,
|
private val kv: KVRestore,
|
||||||
private val full: FullRestore,
|
private val full: FullRestore,
|
||||||
private val metadataReader: MetadataReader,
|
private val metadataReader: MetadataReader,
|
||||||
|
@ -70,34 +73,58 @@ internal class RestoreCoordinator(
|
||||||
|
|
||||||
private val backend: Backend get() = backendManager.backend
|
private val backend: Backend get() = backendManager.backend
|
||||||
private var state: RestoreCoordinatorState? = null
|
private var state: RestoreCoordinatorState? = null
|
||||||
private var backupMetadata: BackupMetadata? = null
|
private var restorableBackup: RestorableBackup? = null
|
||||||
private val failedPackages = ArrayList<String>()
|
private val failedPackages = ArrayList<String>()
|
||||||
|
|
||||||
suspend fun getAvailableMetadata(): Map<Long, BackupMetadata>? {
|
suspend fun getAvailableBackups(): RestorableBackupResult {
|
||||||
val availableBackups = backend.getAvailableBackups() ?: return null
|
val fileHandles = try {
|
||||||
val metadataMap = HashMap<Long, BackupMetadata>()
|
backend.getAvailableBackupFileHandles()
|
||||||
for (encryptedMetadata in availableBackups) {
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error getting available backups.", e)
|
||||||
|
return RestorableBackupResult.ErrorResult(e)
|
||||||
|
}
|
||||||
|
val backups = ArrayList<RestorableBackup>()
|
||||||
|
var lastException: Exception? = null
|
||||||
|
for (handle in fileHandles) {
|
||||||
try {
|
try {
|
||||||
val metadata = encryptedMetadata.inputStreamRetriever().use { inputStream ->
|
val backup = when (handle) {
|
||||||
metadataReader.readMetadata(inputStream, encryptedMetadata.token)
|
is AppBackupFileType.Snapshot -> {
|
||||||
|
val snapshot = loader.loadFile(handle).use { inputStream ->
|
||||||
|
Snapshot.parseFrom(inputStream)
|
||||||
|
}
|
||||||
|
RestorableBackup(
|
||||||
|
repoId = handle.repoId,
|
||||||
|
snapshot = snapshot,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is LegacyAppBackupFile.Metadata -> {
|
||||||
|
val metadata = backend.load(handle).use { inputStream ->
|
||||||
|
metadataReader.readMetadata(inputStream, handle.token)
|
||||||
|
}
|
||||||
|
RestorableBackup(backupMetadata = metadata)
|
||||||
|
}
|
||||||
|
else -> error("Unexpected file handle: $handle")
|
||||||
}
|
}
|
||||||
metadataMap[encryptedMetadata.token] = metadata
|
backups.add(backup)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "Error while getting restore set ${encryptedMetadata.token}", e)
|
Log.e(TAG, "Error while getting restore set $handle", e)
|
||||||
|
lastException = e
|
||||||
continue
|
continue
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
Log.e(TAG, "Error while getting restore set ${encryptedMetadata.token}", e)
|
Log.e(TAG, "Error while getting restore set $handle", e)
|
||||||
return null
|
return RestorableBackupResult.ErrorResult(e)
|
||||||
} catch (e: DecryptionFailedException) {
|
} catch (e: DecryptionFailedException) {
|
||||||
Log.e(TAG, "Error while decrypting restore set ${encryptedMetadata.token}", e)
|
Log.e(TAG, "Error while decrypting restore set $handle", e)
|
||||||
|
lastException = e
|
||||||
continue
|
continue
|
||||||
} catch (e: UnsupportedVersionException) {
|
} catch (e: UnsupportedVersionException) {
|
||||||
Log.w(TAG, "Backup with unsupported version read", e)
|
Log.w(TAG, "Backup with unsupported version read", e)
|
||||||
|
lastException = e
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.i(TAG, "Got available metadata for tokens: ${metadataMap.keys}")
|
if (backups.isEmpty()) return RestorableBackupResult.ErrorResult(lastException)
|
||||||
return metadataMap
|
return RestorableBackupResult.SuccessResult(backups)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,22 +134,21 @@ internal class RestoreCoordinator(
|
||||||
* or null if an error occurred (the attempt should be rescheduled).
|
* or null if an error occurred (the attempt should be rescheduled).
|
||||||
**/
|
**/
|
||||||
suspend fun getAvailableRestoreSets(): Array<RestoreSet>? {
|
suspend fun getAvailableRestoreSets(): Array<RestoreSet>? {
|
||||||
return getAvailableMetadata()?.map { (_, metadata) ->
|
val result = getAvailableBackups() as? RestorableBackupResult.SuccessResult ?: return null
|
||||||
|
val backups = result.backups
|
||||||
val transportFlags = if (metadata.d2dBackup) {
|
return backups.map { backup ->
|
||||||
|
val transportFlags = if (backup.d2dBackup) {
|
||||||
D2D_TRANSPORT_FLAGS
|
D2D_TRANSPORT_FLAGS
|
||||||
} else {
|
} else {
|
||||||
DEFAULT_TRANSPORT_FLAGS
|
DEFAULT_TRANSPORT_FLAGS
|
||||||
}
|
}
|
||||||
|
val deviceName = if (backup.d2dBackup) {
|
||||||
val deviceName = if (metadata.d2dBackup) {
|
|
||||||
D2D_DEVICE_NAME
|
D2D_DEVICE_NAME
|
||||||
} else {
|
} else {
|
||||||
metadata.deviceName
|
backup.deviceName
|
||||||
}
|
}
|
||||||
|
RestoreSet(backup.deviceName, deviceName, backup.token, transportFlags)
|
||||||
RestoreSet(metadata.deviceName, deviceName, metadata.token, transportFlags)
|
}.toTypedArray()
|
||||||
}?.toTypedArray()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,10 +167,10 @@ internal class RestoreCoordinator(
|
||||||
/**
|
/**
|
||||||
* Call this before starting the restore as an optimization to prevent re-fetching metadata.
|
* Call this before starting the restore as an optimization to prevent re-fetching metadata.
|
||||||
*/
|
*/
|
||||||
fun beforeStartRestore(backupMetadata: BackupMetadata) {
|
fun beforeStartRestore(restorableBackup: RestorableBackup) {
|
||||||
this.backupMetadata = backupMetadata
|
this.restorableBackup = restorableBackup
|
||||||
|
|
||||||
if (backupMetadata.d2dBackup) {
|
if (restorableBackup.d2dBackup) {
|
||||||
settingsManager.setD2dBackupsEnabled(true)
|
settingsManager.setD2dBackupsEnabled(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,13 +214,15 @@ internal class RestoreCoordinator(
|
||||||
packages[1]
|
packages[1]
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
val metadata = if (backupMetadata?.token == token) {
|
val backup = if (restorableBackup?.token == token) {
|
||||||
backupMetadata!! // if token matches, backupMetadata is non-null
|
restorableBackup!! // if token matches, backupMetadata is non-null
|
||||||
} else {
|
} else {
|
||||||
getAvailableMetadata()?.get(token) ?: return TRANSPORT_ERROR
|
val backup = getAvailableBackups() as? RestorableBackupResult.SuccessResult
|
||||||
|
?: return TRANSPORT_ERROR
|
||||||
|
backup.backups.find { it.token == token } ?: return TRANSPORT_ERROR
|
||||||
}
|
}
|
||||||
state = RestoreCoordinatorState(token, packages.iterator(), pmPackageInfo, metadata)
|
state = RestoreCoordinatorState(token, packages.iterator(), pmPackageInfo, backup)
|
||||||
backupMetadata = null
|
restorableBackup = null
|
||||||
failedPackages.clear()
|
failedPackages.clear()
|
||||||
return TRANSPORT_OK
|
return TRANSPORT_OK
|
||||||
}
|
}
|
||||||
|
@ -231,13 +259,13 @@ internal class RestoreCoordinator(
|
||||||
|
|
||||||
if (!state.packages.hasNext()) return NO_MORE_PACKAGES
|
if (!state.packages.hasNext()) return NO_MORE_PACKAGES
|
||||||
val packageInfo = state.packages.next()
|
val packageInfo = state.packages.next()
|
||||||
val version = state.backupMetadata.version
|
val version = state.backup.version
|
||||||
if (version == 0.toByte()) return nextRestorePackageV0(state, packageInfo)
|
if (version == 0.toByte()) return nextRestorePackageV0(state, packageInfo)
|
||||||
|
|
||||||
val packageName = packageInfo.packageName
|
val packageName = packageInfo.packageName
|
||||||
val type = when (state.backupMetadata.packageMetadataMap[packageName]?.backupType) {
|
val type = when (state.backup.packageMetadataMap[packageName]?.backupType) {
|
||||||
BackupType.KV -> {
|
BackupType.KV -> {
|
||||||
val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName)
|
val name = crypto.getNameForPackage(state.backup.salt, packageName)
|
||||||
kv.initializeState(
|
kv.initializeState(
|
||||||
version = version,
|
version = version,
|
||||||
token = state.token,
|
token = state.token,
|
||||||
|
@ -250,7 +278,7 @@ internal class RestoreCoordinator(
|
||||||
}
|
}
|
||||||
|
|
||||||
BackupType.FULL -> {
|
BackupType.FULL -> {
|
||||||
val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName)
|
val name = crypto.getNameForPackage(state.backup.salt, packageName)
|
||||||
full.initializeState(version, state.token, name, packageInfo)
|
full.initializeState(version, state.token, name, packageInfo)
|
||||||
state.currentPackage = packageName
|
state.currentPackage = packageName
|
||||||
TYPE_FULL_STREAM
|
TYPE_FULL_STREAM
|
||||||
|
@ -258,7 +286,7 @@ internal class RestoreCoordinator(
|
||||||
|
|
||||||
null -> {
|
null -> {
|
||||||
Log.i(TAG, "No backup type found for $packageName. Skipping...")
|
Log.i(TAG, "No backup type found for $packageName. Skipping...")
|
||||||
state.backupMetadata.packageMetadataMap[packageName]?.backupType?.let { s ->
|
state.backup.packageMetadataMap[packageName]?.backupType?.let { s ->
|
||||||
Log.w(TAG, "State was ${s.name}")
|
Log.w(TAG, "State was ${s.name}")
|
||||||
}
|
}
|
||||||
failedPackages.add(packageName)
|
failedPackages.add(packageName)
|
||||||
|
|
|
@ -14,6 +14,17 @@ val restoreModule = module {
|
||||||
single { KVRestore(get(), get(), get(), get(), get(), get()) }
|
single { KVRestore(get(), get(), get(), get(), get(), get()) }
|
||||||
single { FullRestore(get(), get(), get(), get(), get()) }
|
single { FullRestore(get(), get(), get(), get(), get()) }
|
||||||
single {
|
single {
|
||||||
RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get())
|
RestoreCoordinator(
|
||||||
|
context = androidContext(),
|
||||||
|
crypto = get(),
|
||||||
|
settingsManager = get(),
|
||||||
|
metadataManager = get(),
|
||||||
|
notificationManager = get(),
|
||||||
|
backendManager = get(),
|
||||||
|
loader = get(),
|
||||||
|
kv = get(),
|
||||||
|
full = get(),
|
||||||
|
metadataReader = get(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import org.calyxos.seedvault.core.backends.Backend
|
||||||
import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT
|
import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT
|
||||||
import org.calyxos.seedvault.core.backends.saf.SafProperties
|
import org.calyxos.seedvault.core.backends.saf.SafProperties
|
||||||
import org.calyxos.seedvault.core.backends.webdav.WebDavProperties
|
import org.calyxos.seedvault.core.backends.webdav.WebDavProperties
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
private val TAG = RestoreStorageViewModel::class.java.simpleName
|
private val TAG = RestoreStorageViewModel::class.java.simpleName
|
||||||
|
|
||||||
|
@ -37,9 +36,11 @@ internal class RestoreStorageViewModel(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val hasBackup = try {
|
val hasBackup = try {
|
||||||
safHandler.hasAppBackup(safProperties)
|
safHandler.hasAppBackup(safProperties)
|
||||||
} catch (e: IOException) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error reading URI: ${safProperties.uri}", e)
|
Log.e(TAG, "Error reading URI: ${safProperties.uri}", e)
|
||||||
false
|
val errorMsg = app.getString(R.string.restore_set_error) + "\n\n$e"
|
||||||
|
mLocationChecked.postEvent(LocationResult(errorMsg))
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
if (hasBackup) {
|
if (hasBackup) {
|
||||||
safHandler.save(safProperties)
|
safHandler.save(safProperties)
|
||||||
|
@ -60,9 +61,11 @@ internal class RestoreStorageViewModel(
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val hasBackup = try {
|
val hasBackup = try {
|
||||||
webdavHandler.hasAppBackup(backend)
|
webdavHandler.hasAppBackup(backend)
|
||||||
} catch (e: IOException) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Error reading: ${properties.config.url}", e)
|
Log.e(TAG, "Error reading: ${properties.config.url}", e)
|
||||||
false
|
val errorMsg = app.getString(R.string.restore_set_error) + "\n\n$e"
|
||||||
|
mLocationChecked.postEvent(LocationResult(errorMsg))
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
if (hasBackup) {
|
if (hasBackup) {
|
||||||
webdavHandler.save(properties)
|
webdavHandler.save(properties)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.stevesoltys.seedvault.metadata.BackupMetadata
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||||
import com.stevesoltys.seedvault.transport.TransportTest
|
import com.stevesoltys.seedvault.transport.TransportTest
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_CONTACTS
|
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_CONTACTS
|
||||||
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SETTINGS
|
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SETTINGS
|
||||||
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM
|
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM
|
||||||
|
@ -27,7 +28,6 @@ import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
@ -239,7 +239,7 @@ internal class AppSelectionManagerTest : TransportTest() {
|
||||||
val backend: Backend = mockk()
|
val backend: Backend = mockk()
|
||||||
every { backendManager.backend } returns backend
|
every { backendManager.backend } returns backend
|
||||||
coEvery {
|
coEvery {
|
||||||
backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token))
|
iconManager.downloadIcons(repoId, snapshot)
|
||||||
} throws IOException()
|
} throws IOException()
|
||||||
|
|
||||||
appSelectionManager.selectedAppsFlow.test {
|
appSelectionManager.selectedAppsFlow.test {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||||
import com.stevesoltys.seedvault.proto.SnapshotKt.blob
|
import com.stevesoltys.seedvault.proto.SnapshotKt.blob
|
||||||
import com.stevesoltys.seedvault.proto.SnapshotKt.split
|
import com.stevesoltys.seedvault.proto.SnapshotKt.split
|
||||||
import com.stevesoltys.seedvault.proto.copy
|
import com.stevesoltys.seedvault.proto.copy
|
||||||
import com.stevesoltys.seedvault.restore.RestorableBackup
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
|
||||||
|
|
|
@ -30,7 +30,9 @@ import com.stevesoltys.seedvault.transport.backup.PackageService
|
||||||
import com.stevesoltys.seedvault.transport.backup.TestKvDbManager
|
import com.stevesoltys.seedvault.transport.backup.TestKvDbManager
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestore
|
import com.stevesoltys.seedvault.transport.restore.FullRestore
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestore
|
import com.stevesoltys.seedvault.transport.restore.KVRestore
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.Loader
|
||||||
import com.stevesoltys.seedvault.transport.restore.OutputFactory
|
import com.stevesoltys.seedvault.transport.restore.OutputFactory
|
||||||
|
import com.stevesoltys.seedvault.transport.restore.RestorableBackup
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
import com.stevesoltys.seedvault.worker.ApkBackup
|
import com.stevesoltys.seedvault.worker.ApkBackup
|
||||||
|
@ -68,6 +70,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
||||||
private val backend = mockk<Backend>()
|
private val backend = mockk<Backend>()
|
||||||
|
private val loader = mockk<Loader>()
|
||||||
private val kvBackup = KVBackup(
|
private val kvBackup = KVBackup(
|
||||||
backendManager = backendManager,
|
backendManager = backendManager,
|
||||||
settingsManager = settingsManager,
|
settingsManager = settingsManager,
|
||||||
|
@ -114,11 +117,13 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
metadataManager,
|
metadataManager,
|
||||||
notificationManager,
|
notificationManager,
|
||||||
backendManager,
|
backendManager,
|
||||||
|
loader,
|
||||||
kvRestore,
|
kvRestore,
|
||||||
fullRestore,
|
fullRestore,
|
||||||
metadataReader
|
metadataReader
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val restorableBackup = RestorableBackup(metadata)
|
||||||
private val backupDataInput = mockk<BackupDataInput>()
|
private val backupDataInput = mockk<BackupDataInput>()
|
||||||
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
||||||
private val appData = ByteArray(42).apply { Random.nextBytes(this) }
|
private val appData = ByteArray(42).apply { Random.nextBytes(this) }
|
||||||
|
@ -185,7 +190,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
assertEquals(TRANSPORT_OK, backup.finishBackup())
|
assertEquals(TRANSPORT_OK, backup.finishBackup())
|
||||||
|
|
||||||
// start restore
|
// start restore
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||||
|
|
||||||
// find data for K/V backup
|
// find data for K/V backup
|
||||||
|
@ -262,7 +267,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
assertEquals(TRANSPORT_OK, backup.finishBackup())
|
assertEquals(TRANSPORT_OK, backup.finishBackup())
|
||||||
|
|
||||||
// start restore
|
// start restore
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||||
|
|
||||||
// find data for K/V backup
|
// find data for K/V backup
|
||||||
|
@ -328,7 +333,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
assertEquals(TRANSPORT_OK, backup.finishBackup())
|
assertEquals(TRANSPORT_OK, backup.finishBackup())
|
||||||
|
|
||||||
// start restore
|
// start restore
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||||
|
|
||||||
// finds data for full backup
|
// finds data for full backup
|
||||||
|
|
|
@ -14,8 +14,6 @@ import android.app.backup.RestoreDescription.TYPE_KEY_VALUE
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
import com.stevesoltys.seedvault.backend.EncryptedMetadata
|
|
||||||
import com.stevesoltys.seedvault.backend.getAvailableBackups
|
|
||||||
import com.stevesoltys.seedvault.coAssertThrows
|
import com.stevesoltys.seedvault.coAssertThrows
|
||||||
import com.stevesoltys.seedvault.getRandomString
|
import com.stevesoltys.seedvault.getRandomString
|
||||||
import com.stevesoltys.seedvault.header.VERSION
|
import com.stevesoltys.seedvault.header.VERSION
|
||||||
|
@ -32,7 +30,10 @@ import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.calyxos.seedvault.core.backends.AppBackupFileType
|
||||||
import org.calyxos.seedvault.core.backends.Backend
|
import org.calyxos.seedvault.core.backends.Backend
|
||||||
|
import org.calyxos.seedvault.core.backends.FileInfo
|
||||||
|
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
|
||||||
import org.calyxos.seedvault.core.backends.saf.SafProperties
|
import org.calyxos.seedvault.core.backends.saf.SafProperties
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||||
|
@ -48,6 +49,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
private val notificationManager: BackupNotificationManager = mockk()
|
private val notificationManager: BackupNotificationManager = mockk()
|
||||||
private val backendManager: BackendManager = mockk()
|
private val backendManager: BackendManager = mockk()
|
||||||
private val backend = mockk<Backend>()
|
private val backend = mockk<Backend>()
|
||||||
|
private val loader = mockk<Loader>()
|
||||||
private val kv = mockk<KVRestore>()
|
private val kv = mockk<KVRestore>()
|
||||||
private val full = mockk<FullRestore>()
|
private val full = mockk<FullRestore>()
|
||||||
private val metadataReader = mockk<MetadataReader>()
|
private val metadataReader = mockk<MetadataReader>()
|
||||||
|
@ -59,11 +61,13 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
metadataManager = metadataManager,
|
metadataManager = metadataManager,
|
||||||
notificationManager = notificationManager,
|
notificationManager = notificationManager,
|
||||||
backendManager = backendManager,
|
backendManager = backendManager,
|
||||||
|
loader = loader,
|
||||||
kv = kv,
|
kv = kv,
|
||||||
full = full,
|
full = full,
|
||||||
metadataReader = metadataReader,
|
metadataReader = metadataReader,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val restorableBackup = RestorableBackup(metadata)
|
||||||
private val inputStream = mockk<InputStream>()
|
private val inputStream = mockk<InputStream>()
|
||||||
private val safStorage: SafProperties = mockk()
|
private val safStorage: SafProperties = mockk()
|
||||||
private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" }
|
private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" }
|
||||||
|
@ -85,12 +89,22 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking {
|
fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking {
|
||||||
val encryptedMetadata = EncryptedMetadata(token) { inputStream }
|
val info1 = FileInfo(LegacyAppBackupFile.Metadata(token), 1)
|
||||||
|
val info2 = FileInfo(LegacyAppBackupFile.Metadata(token + 1), 2)
|
||||||
|
|
||||||
coEvery { backend.getAvailableBackups() } returns sequenceOf(
|
coEvery {
|
||||||
encryptedMetadata,
|
backend.list(
|
||||||
EncryptedMetadata(token + 1) { inputStream }
|
topLevelFolder = null,
|
||||||
)
|
AppBackupFileType.Snapshot::class, LegacyAppBackupFile.Metadata::class,
|
||||||
|
callback = captureLambda<(FileInfo) -> Unit>()
|
||||||
|
)
|
||||||
|
} answers {
|
||||||
|
val callback = lambda<(FileInfo) -> Unit>().captured
|
||||||
|
callback(info1)
|
||||||
|
callback(info2)
|
||||||
|
}
|
||||||
|
coEvery { backend.load(info1.fileHandle) } returns inputStream
|
||||||
|
coEvery { backend.load(info2.fileHandle) } returns inputStream
|
||||||
every { metadataReader.readMetadata(inputStream, token) } returns metadata
|
every { metadataReader.readMetadata(inputStream, token) } returns metadata
|
||||||
every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata
|
every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata
|
||||||
every { inputStream.close() } just Runs
|
every { inputStream.close() } just Runs
|
||||||
|
@ -119,16 +133,28 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `startRestore() returns OK`() = runBlocking {
|
fun `startRestore() returns OK`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `startRestore() fetches metadata if missing`() = runBlocking {
|
fun `startRestore() fetches metadata if missing`() = runBlocking {
|
||||||
coEvery { backend.getAvailableBackups() } returns sequenceOf(
|
val info1 = FileInfo(LegacyAppBackupFile.Metadata(token), 1)
|
||||||
EncryptedMetadata(token) { inputStream },
|
val info2 = FileInfo(LegacyAppBackupFile.Metadata(token + 1), 2)
|
||||||
EncryptedMetadata(token + 1) { inputStream }
|
|
||||||
)
|
coEvery {
|
||||||
|
backend.list(
|
||||||
|
topLevelFolder = null,
|
||||||
|
AppBackupFileType.Snapshot::class, LegacyAppBackupFile.Metadata::class,
|
||||||
|
callback = captureLambda<(FileInfo) -> Unit>()
|
||||||
|
)
|
||||||
|
} answers {
|
||||||
|
val callback = lambda<(FileInfo) -> Unit>().captured
|
||||||
|
callback(info1)
|
||||||
|
callback(info2)
|
||||||
|
}
|
||||||
|
coEvery { backend.load(info1.fileHandle) } returns inputStream
|
||||||
|
coEvery { backend.load(info2.fileHandle) } returns inputStream
|
||||||
every { metadataReader.readMetadata(inputStream, token) } returns metadata
|
every { metadataReader.readMetadata(inputStream, token) } returns metadata
|
||||||
every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata
|
every { metadataReader.readMetadata(inputStream, token + 1) } returns metadata
|
||||||
every { inputStream.close() } just Runs
|
every { inputStream.close() } just Runs
|
||||||
|
@ -138,18 +164,28 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `startRestore() errors if metadata is not matching token`() = runBlocking {
|
fun `startRestore() errors if metadata is not matching token`() = runBlocking {
|
||||||
coEvery { backend.getAvailableBackups() } returns sequenceOf(
|
val otherToken = token + 42
|
||||||
EncryptedMetadata(token + 42) { inputStream }
|
val info = FileInfo(LegacyAppBackupFile.Metadata(otherToken), 23)
|
||||||
)
|
coEvery {
|
||||||
every { metadataReader.readMetadata(inputStream, token + 42) } returns metadata
|
backend.list(
|
||||||
|
topLevelFolder = null,
|
||||||
|
AppBackupFileType.Snapshot::class, LegacyAppBackupFile.Metadata::class,
|
||||||
|
callback = captureLambda<(FileInfo) -> Unit>()
|
||||||
|
)
|
||||||
|
} answers {
|
||||||
|
val callback = lambda<(FileInfo) -> Unit>().captured
|
||||||
|
callback(info)
|
||||||
|
}
|
||||||
|
coEvery { backend.load(info.fileHandle) } returns inputStream
|
||||||
|
every { metadataReader.readMetadata(inputStream, otherToken) } returns metadata
|
||||||
every { inputStream.close() } just Runs
|
every { inputStream.close() } just Runs
|
||||||
|
|
||||||
assertEquals(TRANSPORT_ERROR, restore.startRestore(token, packageInfoArray))
|
assertEquals(TRANSPORT_ERROR, restore.startRestore(otherToken, packageInfoArray))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `startRestore() can not be called twice`() = runBlocking {
|
fun `startRestore() can not be called twice`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
||||||
assertThrows(IllegalStateException::class.javaObjectType) {
|
assertThrows(IllegalStateException::class.javaObjectType) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
@ -161,13 +197,13 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `startRestore() can be be called again after restore finished`() = runBlocking {
|
fun `startRestore() can be be called again after restore finished`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
||||||
|
|
||||||
every { full.hasState() } returns false
|
every { full.hasState() } returns false
|
||||||
restore.finishRestore()
|
restore.finishRestore()
|
||||||
|
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +237,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
every { backendManager.backendProperties } returns safStorage
|
every { backendManager.backendProperties } returns safStorage
|
||||||
every { safStorage.isUnavailableUsb(context) } returns false
|
every { safStorage.isUnavailableUsb(context) } returns false
|
||||||
|
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray))
|
||||||
|
|
||||||
verify(exactly = 0) {
|
verify(exactly = 0) {
|
||||||
|
@ -237,7 +273,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `nextRestorePackage() returns KV description`() = runBlocking {
|
fun `nextRestorePackage() returns KV description`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
restore.startRestore(token, packageInfoArray)
|
restore.startRestore(token, packageInfoArray)
|
||||||
|
|
||||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||||
|
@ -250,7 +286,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
@Test
|
@Test
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
fun `v0 nextRestorePackage() returns KV description and takes precedence`() = runBlocking {
|
fun `v0 nextRestorePackage() returns KV description and takes precedence`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
val backup = restorableBackup.copy(backupMetadata = metadata.copy(version = 0x00))
|
||||||
|
restore.beforeStartRestore(backup)
|
||||||
restore.startRestore(token, packageInfoArray)
|
restore.startRestore(token, packageInfoArray)
|
||||||
|
|
||||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||||
|
@ -263,7 +300,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
@Test
|
@Test
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
fun `v0 nextRestorePackage() returns full description if no KV data found`() = runBlocking {
|
fun `v0 nextRestorePackage() returns full description if no KV data found`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
val backup = restorableBackup.copy(backupMetadata = metadata.copy(version = 0x00))
|
||||||
|
restore.beforeStartRestore(backup)
|
||||||
restore.startRestore(token, packageInfoArray)
|
restore.startRestore(token, packageInfoArray)
|
||||||
|
|
||||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
|
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
|
||||||
|
@ -278,7 +316,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
fun `nextRestorePackage() tries next package if one has no backup type()`() = runBlocking {
|
fun `nextRestorePackage() tries next package if one has no backup type()`() = runBlocking {
|
||||||
metadata.packageMetadataMap[packageName] =
|
metadata.packageMetadataMap[packageName] =
|
||||||
metadata.packageMetadataMap[packageName]!!.copy(backupType = null)
|
metadata.packageMetadataMap[packageName]!!.copy(backupType = null)
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
restore.startRestore(token, packageInfoArray2)
|
restore.startRestore(token, packageInfoArray2)
|
||||||
|
|
||||||
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
|
every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2
|
||||||
|
@ -292,7 +330,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `nextRestorePackage() returns all packages from startRestore()`() = runBlocking {
|
fun `nextRestorePackage() returns all packages from startRestore()`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata)
|
restore.beforeStartRestore(restorableBackup)
|
||||||
restore.startRestore(token, packageInfoArray2)
|
restore.startRestore(token, packageInfoArray2)
|
||||||
|
|
||||||
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
every { crypto.getNameForPackage(metadata.salt, packageName) } returns name
|
||||||
|
@ -314,7 +352,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
@Test
|
@Test
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
fun `v0 nextRestorePackage() returns all packages from startRestore()`() = runBlocking {
|
fun `v0 nextRestorePackage() returns all packages from startRestore()`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
val backup = restorableBackup.copy(backupMetadata = metadata.copy(version = 0x00))
|
||||||
|
restore.beforeStartRestore(backup)
|
||||||
restore.startRestore(token, packageInfoArray2)
|
restore.startRestore(token, packageInfoArray2)
|
||||||
|
|
||||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
coEvery { kv.hasDataForPackage(token, packageInfo) } returns true
|
||||||
|
@ -336,7 +375,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
@Test
|
@Test
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
fun `v0 when kv#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
fun `v0 when kv#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
val backup = restorableBackup.copy(backupMetadata = metadata.copy(version = 0x00))
|
||||||
|
restore.beforeStartRestore(backup)
|
||||||
restore.startRestore(token, packageInfoArray)
|
restore.startRestore(token, packageInfoArray)
|
||||||
|
|
||||||
coEvery { kv.hasDataForPackage(token, packageInfo) } throws IOException()
|
coEvery { kv.hasDataForPackage(token, packageInfo) } throws IOException()
|
||||||
|
@ -347,7 +387,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
@Test
|
@Test
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
fun `v0 when full#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
fun `v0 when full#hasDataForPackage() throws, it tries next package`() = runBlocking {
|
||||||
restore.beforeStartRestore(metadata.copy(version = 0x00))
|
val backup = restorableBackup.copy(backupMetadata = metadata.copy(version = 0x00))
|
||||||
|
restore.beforeStartRestore(backup)
|
||||||
restore.startRestore(token, packageInfoArray)
|
restore.startRestore(token, packageInfoArray)
|
||||||
|
|
||||||
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
|
coEvery { kv.hasDataForPackage(token, packageInfo) } returns false
|
||||||
|
|
|
@ -55,6 +55,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
||||||
private val notificationManager = mockk<BackupNotificationManager>()
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
private val backendManager: BackendManager = mockk()
|
private val backendManager: BackendManager = mockk()
|
||||||
|
private val loader = mockk<Loader>()
|
||||||
|
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
||||||
|
@ -76,10 +77,14 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
metadataManager = metadataManager,
|
metadataManager = metadataManager,
|
||||||
notificationManager = notificationManager,
|
notificationManager = notificationManager,
|
||||||
backendManager = backendManager,
|
backendManager = backendManager,
|
||||||
|
loader = loader,
|
||||||
kv = kvRestore,
|
kv = kvRestore,
|
||||||
full = fullRestore,
|
full = fullRestore,
|
||||||
metadataReader = metadataReader,
|
metadataReader = metadataReader,
|
||||||
).apply { beforeStartRestore(metadata.copy(version = 0x00)) }
|
).apply {
|
||||||
|
val backup = RestorableBackup(metadata.copy(version = 0x00))
|
||||||
|
beforeStartRestore(backup)
|
||||||
|
}
|
||||||
|
|
||||||
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
private val fileDescriptor = mockk<ParcelFileDescriptor>(relaxed = true)
|
||||||
private val appData = ("562AB665C3543120FC794D7CDA3AC18E5959235A4D" +
|
private val appData = ("562AB665C3543120FC794D7CDA3AC18E5959235A4D" +
|
||||||
|
|
Loading…
Reference in a new issue