diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt index b5313c01..40b8be89 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt @@ -10,7 +10,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry 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.DocumentsStorage import com.stevesoltys.seedvault.settings.SettingsManager @@ -92,7 +92,7 @@ class PluginTest : KoinComponent { @Test fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) { // no backups available initially - assertEquals(0, backend.getAvailableBackups()?.toList()?.size) + assertEquals(0, backend.getAvailableBackupFileHandles().toList().size) // prepare returned tokens requested when initializing device every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1) @@ -102,17 +102,17 @@ class PluginTest : KoinComponent { .writeAndClose(getRandomByteArray()) // 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 backend.save(LegacyAppBackupFile.Metadata(token + 1)) .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 backend.save(LegacyAppBackupFile.Metadata(token + 1)) .writeAndClose(getRandomByteArray()) - assertEquals(2, backend.getAvailableBackups()?.toList()?.size) + assertEquals(2, backend.getAvailableBackupFileHandles().toList().size) } @Test @@ -124,27 +124,26 @@ class PluginTest : KoinComponent { backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata) // get available backups, expect only one with our token and no error - var availableBackups = backend.getAvailableBackups()?.toList() - check(availableBackups != null) + var availableBackups = backend.getAvailableBackupFileHandles().toList() 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 - assertReadEquals(metadata, availableBackups[0].inputStreamRetriever()) + assertReadEquals(metadata, backend.load(backupHandle)) // initializing again (without changing storage) keeps restore set with same token backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata) - availableBackups = backend.getAvailableBackups()?.toList() - check(availableBackups != null) + availableBackups = backend.getAvailableBackupFileHandles().toList() assertEquals(1, availableBackups.size) - assertEquals(token, availableBackups[0].token) + backupHandle = availableBackups[0] as LegacyAppBackupFile.Metadata + assertEquals(token, backupHandle.token) // metadata hasn't changed - assertReadEquals(metadata, availableBackups[0].inputStreamRetriever()) + assertReadEquals(metadata, backend.load(backupHandle)) } @Test - @Suppress("Deprecation") fun v0testApkWriteRead() = runBlocking { // initialize storage with given token initStorage(token) diff --git a/app/src/main/java/com/stevesoltys/seedvault/backend/BackendExt.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendExt.kt index ffe16ba6..0387bc7c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/backend/BackendExt.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendExt.kt @@ -5,37 +5,29 @@ package com.stevesoltys.seedvault.backend -import android.util.Log 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.FileHandle import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.IOException -import java.io.InputStream import java.io.OutputStream suspend fun Backend.getMetadataOutputStream(token: Long): OutputStream { return save(LegacyAppBackupFile.Metadata(token)) } -suspend fun Backend.getAvailableBackups(): Sequence? { - return try { - // get all restore set tokens in root folder that have a metadata file - val handles = ArrayList() - list(null, LegacyAppBackupFile.Metadata::class) { fileInfo -> - val handle = fileInfo.fileHandle as LegacyAppBackupFile.Metadata - handles.add(handle) +suspend fun Backend.getAvailableBackupFileHandles(): List { + // v1 get all restore set tokens in root folder that have a metadata file + // v2 get all snapshots in all repository folders + return ArrayList().apply { + list( + null, + 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 } } - -class EncryptedMetadata(val token: Long, val inputStreamRetriever: suspend () -> InputStream) diff --git a/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt index b9db52f5..5160639c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt @@ -15,7 +15,7 @@ import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.R 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.settings.FlashDrive import com.stevesoltys.seedvault.settings.SettingsManager @@ -59,9 +59,8 @@ internal class SafHandler( @WorkerThread @Throws(IOException::class) suspend fun hasAppBackup(safProperties: SafProperties): Boolean { - val appPlugin = backendFactory.createSafBackend(safProperties) - val backups = appPlugin.getAvailableBackups() - return backups != null && backups.iterator().hasNext() + val backend = backendFactory.createSafBackend(safProperties) + return backend.getAvailableBackupFileHandles().isNotEmpty() } fun save(safProperties: SafProperties) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt index 17d6894a..e46941d8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt @@ -10,7 +10,7 @@ import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.R 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 kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -81,8 +81,7 @@ internal class WebDavHandler( @WorkerThread @Throws(IOException::class) suspend fun hasAppBackup(backend: Backend): Boolean { - val backups = backend.getAvailableBackups() - return backups != null && backups.iterator().hasNext() + return backend.getAvailableBackupFileHandles().isNotEmpty() } fun save(properties: WebDavProperties) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt index ccf3d6ca..0a40f2ad 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt @@ -30,6 +30,23 @@ data class BackupMetadata( internal var d2dBackup: Boolean = false, 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 get() = packageMetadataMap.values.sumOf { m -> (m.size ?: 0L) + (m.splits?.sumOf { it.size ?: 0L } ?: 0L) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt index 2372018c..d47bee2d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt @@ -23,12 +23,13 @@ import com.stevesoltys.seedvault.BackupMonitor import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.NO_DATA_END_SENTINEL import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.metadata.PackageState -import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.restore.install.isInstalled import com.stevesoltys.seedvault.settings.SettingsManager 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.ui.AppBackupState import com.stevesoltys.seedvault.ui.AppBackupState.FAILED @@ -263,20 +264,19 @@ internal class AppDataRestoreManager( /** * Restore the next chunk of packages. * - * We need to restore in chunks, otherwise [BackupTransport.startRestore] in the - * framework's [PerformUnifiedRestoreTask] may fail due to an oversize Binder - * transaction, causing the entire restoration to fail. + * We need to restore packages in chunks, otherwise [BackupTransport.startRestore] in the + * framework's [PerformUnifiedRestoreTask] may fail due to an oversize Binder transaction, + * causing the entire restoration to fail due to too many package names. */ private fun restoreNextPackages() { // Make sure metadata for selected backup is cached before starting each chunk. - val backupMetadata = restorableBackup.backupMetadata - restoreCoordinator.beforeStartRestore(backupMetadata) + restoreCoordinator.beforeStartRestore(restorableBackup) val nextChunkIndex = (packageIndex + PACKAGES_PER_CHUNK).coerceAtMost(packages.size) val packageChunk = packages.subList(packageIndex, nextChunkIndex).toTypedArray() packageIndex += packageChunk.size - val token = backupMetadata.token + val token = restorableBackup.token val result = session.restorePackages(token, this, packageChunk, monitor) @Suppress("UNRESOLVED_REFERENCE") // BackupManager.SUCCESS diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt index f2088fe7..df147077 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt @@ -15,6 +15,7 @@ import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.metadata.PackageMetadata 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.systemData import com.stevesoltys.seedvault.worker.IconManager @@ -68,21 +69,23 @@ internal class AppSelectionManager( val name = context.getString(data.nameRes) SelectableAppItem(packageName, metadata.copy(name = name), true) } - val systemItem = SelectableAppItem( - packageName = PACKAGE_NAME_SYSTEM, - metadata = PackageMetadata( - 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 - }, - system = true, - name = context.getString(R.string.backup_system_apps), - ), - selected = isSetupWizard, - ) - items.add(0, systemItem) + if (restorableBackup.packageMetadataMap.isNotEmpty()) { + val systemItem = SelectableAppItem( + packageName = PACKAGE_NAME_SYSTEM, + metadata = PackageMetadata( + 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 + }, + system = true, + name = context.getString(R.string.backup_system_apps), + ), + selected = isSetupWizard, + ) + items.add(0, systemItem) + } items.addAll(0, systemDataItems) selectedApps.value = SelectedAppsState(apps = items, allSelected = isSetupWizard, iconsLoaded = false) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt index 0cd9b2dd..cdf15cc0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetAdapter.kt @@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.restore.RestoreSetAdapter.RestoreSetViewHolder +import com.stevesoltys.seedvault.transport.restore.RestorableBackup internal class RestoreSetAdapter( private val listener: RestorableBackupClickListener, diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt index b6bc5399..c445125f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreSetFragment.kt @@ -17,6 +17,7 @@ import android.widget.TextView import androidx.fragment.app.Fragment import androidx.recyclerview.widget.RecyclerView import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.transport.restore.RestorableBackup import org.koin.androidx.viewmodel.ext.android.sharedViewModel class RestoreSetFragment : Fragment() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index 3abfda9c..fe7496d7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -9,7 +9,6 @@ import android.app.Application import android.app.backup.IBackupManager import android.content.Intent import android.graphics.drawable.Drawable -import android.util.Log import androidx.annotation.UiThread import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.lifecycle.LiveData @@ -17,8 +16,8 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.crypto.KeyManager 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_BACKUP 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.settings.SettingsManager 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.ui.LiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent @@ -106,20 +108,11 @@ internal class RestoreViewModel( private var storedSnapshot: StoredSnapshot? = null internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) { - val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) -> - when (metadata.time) { - 0L -> { - Log.d(TAG, "Ignoring RestoreSet with no last backup time: $token.") - null - } - - 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) + val result = when (val backups = restoreCoordinator.getAvailableBackups()) { + is ErrorResult -> RestoreSetResult( + app.getString(R.string.restore_set_error) + "\n\n${backups.e}" + ) + is SuccessResult -> RestoreSetResult(backups.backups) } mRestoreSetResults.postValue(result) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt index 2fa46b92..99f0e372 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt @@ -21,7 +21,6 @@ import com.stevesoltys.seedvault.crypto.Crypto import com.stevesoltys.seedvault.encodeBase64 import com.stevesoltys.seedvault.metadata.ApkSplit import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.RestoreService import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestorableBackup.kt similarity index 58% rename from app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt rename to app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestorableBackup.kt index f47c7a81..9ff14b29 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestorableBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestorableBackup.kt @@ -1,20 +1,31 @@ /* - * SPDX-FileCopyrightText: 2020 The Calyx Institute + * SPDX-FileCopyrightText: 2024 The Calyx Institute * 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.PackageMetadataMap import com.stevesoltys.seedvault.proto.Snapshot +sealed class RestorableBackupResult { + data class ErrorResult(val e: Exception?) : RestorableBackupResult() + data class SuccessResult(val backups: List) : RestorableBackupResult() +} + data class RestorableBackup( val backupMetadata: BackupMetadata, val repoId: String? = null, val snapshot: Snapshot? = null, ) { + constructor(repoId: String, snapshot: Snapshot) : this( + backupMetadata = BackupMetadata.fromSnapshot(snapshot), + repoId = repoId, + snapshot = snapshot, + ) + val name: String get() = backupMetadata.deviceName @@ -30,8 +41,9 @@ data class RestorableBackup( val time: Long get() = backupMetadata.time - val size: Long? - get() = backupMetadata.size + val size: Long + get() = snapshot?.blobsMap?.values?.sumOf { it.uncompressedLength.toLong() } + ?: backupMetadata.size val deviceName: String get() = backupMetadata.deviceName diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt index a166ad1e..85e7b97c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt @@ -18,20 +18,22 @@ import android.os.ParcelFileDescriptor import android.util.Log import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER 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.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 -import com.stevesoltys.seedvault.backend.BackendManager -import com.stevesoltys.seedvault.backend.getAvailableBackups +import com.stevesoltys.seedvault.proto.Snapshot import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS 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.LegacyAppBackupFile 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@ */ val autoRestorePackageInfo: PackageInfo?, - val backupMetadata: BackupMetadata, + val backup: RestorableBackup, ) { var currentPackage: String? = null } @@ -63,6 +65,7 @@ internal class RestoreCoordinator( private val metadataManager: MetadataManager, private val notificationManager: BackupNotificationManager, private val backendManager: BackendManager, + private val loader: Loader, private val kv: KVRestore, private val full: FullRestore, private val metadataReader: MetadataReader, @@ -70,34 +73,58 @@ internal class RestoreCoordinator( private val backend: Backend get() = backendManager.backend private var state: RestoreCoordinatorState? = null - private var backupMetadata: BackupMetadata? = null + private var restorableBackup: RestorableBackup? = null private val failedPackages = ArrayList() - suspend fun getAvailableMetadata(): Map? { - val availableBackups = backend.getAvailableBackups() ?: return null - val metadataMap = HashMap() - for (encryptedMetadata in availableBackups) { + suspend fun getAvailableBackups(): RestorableBackupResult { + val fileHandles = try { + backend.getAvailableBackupFileHandles() + } catch (e: Exception) { + Log.e(TAG, "Error getting available backups.", e) + return RestorableBackupResult.ErrorResult(e) + } + val backups = ArrayList() + var lastException: Exception? = null + for (handle in fileHandles) { try { - val metadata = encryptedMetadata.inputStreamRetriever().use { inputStream -> - metadataReader.readMetadata(inputStream, encryptedMetadata.token) + val backup = when (handle) { + 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) { - Log.e(TAG, "Error while getting restore set ${encryptedMetadata.token}", e) + Log.e(TAG, "Error while getting restore set $handle", e) + lastException = e continue } catch (e: SecurityException) { - Log.e(TAG, "Error while getting restore set ${encryptedMetadata.token}", e) - return null + Log.e(TAG, "Error while getting restore set $handle", e) + return RestorableBackupResult.ErrorResult(e) } 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 } catch (e: UnsupportedVersionException) { Log.w(TAG, "Backup with unsupported version read", e) + lastException = e continue } } - Log.i(TAG, "Got available metadata for tokens: ${metadataMap.keys}") - return metadataMap + if (backups.isEmpty()) return RestorableBackupResult.ErrorResult(lastException) + return RestorableBackupResult.SuccessResult(backups) } /** @@ -107,22 +134,21 @@ internal class RestoreCoordinator( * or null if an error occurred (the attempt should be rescheduled). **/ suspend fun getAvailableRestoreSets(): Array? { - return getAvailableMetadata()?.map { (_, metadata) -> - - val transportFlags = if (metadata.d2dBackup) { + val result = getAvailableBackups() as? RestorableBackupResult.SuccessResult ?: return null + val backups = result.backups + return backups.map { backup -> + val transportFlags = if (backup.d2dBackup) { D2D_TRANSPORT_FLAGS } else { DEFAULT_TRANSPORT_FLAGS } - - val deviceName = if (metadata.d2dBackup) { + val deviceName = if (backup.d2dBackup) { D2D_DEVICE_NAME } else { - metadata.deviceName + backup.deviceName } - - RestoreSet(metadata.deviceName, deviceName, metadata.token, transportFlags) - }?.toTypedArray() + RestoreSet(backup.deviceName, deviceName, backup.token, transportFlags) + }.toTypedArray() } /** @@ -141,10 +167,10 @@ internal class RestoreCoordinator( /** * Call this before starting the restore as an optimization to prevent re-fetching metadata. */ - fun beforeStartRestore(backupMetadata: BackupMetadata) { - this.backupMetadata = backupMetadata + fun beforeStartRestore(restorableBackup: RestorableBackup) { + this.restorableBackup = restorableBackup - if (backupMetadata.d2dBackup) { + if (restorableBackup.d2dBackup) { settingsManager.setD2dBackupsEnabled(true) } } @@ -188,13 +214,15 @@ internal class RestoreCoordinator( packages[1] } else null - val metadata = if (backupMetadata?.token == token) { - backupMetadata!! // if token matches, backupMetadata is non-null + val backup = if (restorableBackup?.token == token) { + restorableBackup!! // if token matches, backupMetadata is non-null } 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) - backupMetadata = null + state = RestoreCoordinatorState(token, packages.iterator(), pmPackageInfo, backup) + restorableBackup = null failedPackages.clear() return TRANSPORT_OK } @@ -231,13 +259,13 @@ internal class RestoreCoordinator( if (!state.packages.hasNext()) return NO_MORE_PACKAGES val packageInfo = state.packages.next() - val version = state.backupMetadata.version + val version = state.backup.version if (version == 0.toByte()) return nextRestorePackageV0(state, packageInfo) val packageName = packageInfo.packageName - val type = when (state.backupMetadata.packageMetadataMap[packageName]?.backupType) { + val type = when (state.backup.packageMetadataMap[packageName]?.backupType) { BackupType.KV -> { - val name = crypto.getNameForPackage(state.backupMetadata.salt, packageName) + val name = crypto.getNameForPackage(state.backup.salt, packageName) kv.initializeState( version = version, token = state.token, @@ -250,7 +278,7 @@ internal class RestoreCoordinator( } 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) state.currentPackage = packageName TYPE_FULL_STREAM @@ -258,7 +286,7 @@ internal class RestoreCoordinator( null -> { 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}") } failedPackages.add(packageName) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt index fd1d835d..739cc167 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreModule.kt @@ -14,6 +14,17 @@ val restoreModule = module { single { KVRestore(get(), get(), get(), get(), get(), get()) } single { FullRestore(get(), get(), get(), get(), get()) } 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(), + ) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt index 54d77431..bf9e904c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt @@ -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.saf.SafProperties import org.calyxos.seedvault.core.backends.webdav.WebDavProperties -import java.io.IOException private val TAG = RestoreStorageViewModel::class.java.simpleName @@ -37,9 +36,11 @@ internal class RestoreStorageViewModel( viewModelScope.launch(Dispatchers.IO) { val hasBackup = try { safHandler.hasAppBackup(safProperties) - } catch (e: IOException) { + } catch (e: Exception) { 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) { safHandler.save(safProperties) @@ -60,9 +61,11 @@ internal class RestoreStorageViewModel( viewModelScope.launch(Dispatchers.IO) { val hasBackup = try { webdavHandler.hasAppBackup(backend) - } catch (e: IOException) { + } catch (e: Exception) { 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) { webdavHandler.save(properties) diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt index 3768f501..82f8d468 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt @@ -15,6 +15,7 @@ import com.stevesoltys.seedvault.metadata.BackupMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap 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_SETTINGS 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.runTest import org.calyxos.seedvault.core.backends.Backend -import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse @@ -239,7 +239,7 @@ internal class AppSelectionManagerTest : TransportTest() { val backend: Backend = mockk() every { backendManager.backend } returns backend coEvery { - backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token)) + iconManager.downloadIcons(repoId, snapshot) } throws IOException() appSelectionManager.selectedAppsFlow.test { diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt index 9765c35f..f7ffecdd 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt @@ -29,7 +29,7 @@ import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.proto.SnapshotKt.blob import com.stevesoltys.seedvault.proto.SnapshotKt.split 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_SYSTEM_APP import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt index e7a17a4f..53b0a263 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt @@ -30,7 +30,9 @@ import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.TestKvDbManager import com.stevesoltys.seedvault.transport.restore.FullRestore 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.RestorableBackup import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.worker.ApkBackup @@ -68,6 +70,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { @Suppress("Deprecation") private val legacyPlugin = mockk() private val backend = mockk() + private val loader = mockk() private val kvBackup = KVBackup( backendManager = backendManager, settingsManager = settingsManager, @@ -114,11 +117,13 @@ internal class CoordinatorIntegrationTest : TransportTest() { metadataManager, notificationManager, backendManager, + loader, kvRestore, fullRestore, metadataReader ) + private val restorableBackup = RestorableBackup(metadata) private val backupDataInput = mockk() private val fileDescriptor = mockk(relaxed = true) private val appData = ByteArray(42).apply { Random.nextBytes(this) } @@ -185,7 +190,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, backup.finishBackup()) // start restore - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) // find data for K/V backup @@ -262,7 +267,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, backup.finishBackup()) // start restore - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) // find data for K/V backup @@ -328,7 +333,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, backup.finishBackup()) // start restore - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo))) // finds data for full backup diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt index 23773189..8d8b4588 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt @@ -14,8 +14,6 @@ import android.app.backup.RestoreDescription.TYPE_KEY_VALUE import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor 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.getRandomString import com.stevesoltys.seedvault.header.VERSION @@ -32,7 +30,10 @@ import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.verify import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.AppBackupFileType 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.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull @@ -48,6 +49,7 @@ internal class RestoreCoordinatorTest : TransportTest() { private val notificationManager: BackupNotificationManager = mockk() private val backendManager: BackendManager = mockk() private val backend = mockk() + private val loader = mockk() private val kv = mockk() private val full = mockk() private val metadataReader = mockk() @@ -59,11 +61,13 @@ internal class RestoreCoordinatorTest : TransportTest() { metadataManager = metadataManager, notificationManager = notificationManager, backendManager = backendManager, + loader = loader, kv = kv, full = full, metadataReader = metadataReader, ) + private val restorableBackup = RestorableBackup(metadata) private val inputStream = mockk() private val safStorage: SafProperties = mockk() private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" } @@ -85,12 +89,22 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test 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( - encryptedMetadata, - 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 + 1) } returns metadata every { inputStream.close() } just Runs @@ -119,16 +133,28 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() returns OK`() = runBlocking { - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray)) } @Test fun `startRestore() fetches metadata if missing`() = runBlocking { - coEvery { backend.getAvailableBackups() } returns sequenceOf( - EncryptedMetadata(token) { inputStream }, - EncryptedMetadata(token + 1) { inputStream } - ) + val info1 = FileInfo(LegacyAppBackupFile.Metadata(token), 1) + val info2 = FileInfo(LegacyAppBackupFile.Metadata(token + 1), 2) + + 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 + 1) } returns metadata every { inputStream.close() } just Runs @@ -138,18 +164,28 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() errors if metadata is not matching token`() = runBlocking { - coEvery { backend.getAvailableBackups() } returns sequenceOf( - EncryptedMetadata(token + 42) { inputStream } - ) - every { metadataReader.readMetadata(inputStream, token + 42) } returns metadata + val otherToken = token + 42 + val info = FileInfo(LegacyAppBackupFile.Metadata(otherToken), 23) + coEvery { + 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 - assertEquals(TRANSPORT_ERROR, restore.startRestore(token, packageInfoArray)) + assertEquals(TRANSPORT_ERROR, restore.startRestore(otherToken, packageInfoArray)) } @Test fun `startRestore() can not be called twice`() = runBlocking { - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray)) assertThrows(IllegalStateException::class.javaObjectType) { runBlocking { @@ -161,13 +197,13 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() can be be called again after restore finished`() = runBlocking { - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray)) every { full.hasState() } returns false restore.finishRestore() - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, packageInfoArray)) } @@ -201,7 +237,7 @@ internal class RestoreCoordinatorTest : TransportTest() { every { backendManager.backendProperties } returns safStorage every { safStorage.isUnavailableUsb(context) } returns false - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray)) verify(exactly = 0) { @@ -237,7 +273,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `nextRestorePackage() returns KV description`() = runBlocking { - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) restore.startRestore(token, packageInfoArray) every { crypto.getNameForPackage(metadata.salt, packageName) } returns name @@ -250,7 +286,8 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test @Suppress("Deprecation") 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) coEvery { kv.hasDataForPackage(token, packageInfo) } returns true @@ -263,7 +300,8 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test @Suppress("deprecation") 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) 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 { metadata.packageMetadataMap[packageName] = metadata.packageMetadataMap[packageName]!!.copy(backupType = null) - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) restore.startRestore(token, packageInfoArray2) every { crypto.getNameForPackage(metadata.salt, packageInfo2.packageName) } returns name2 @@ -292,7 +330,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `nextRestorePackage() returns all packages from startRestore()`() = runBlocking { - restore.beforeStartRestore(metadata) + restore.beforeStartRestore(restorableBackup) restore.startRestore(token, packageInfoArray2) every { crypto.getNameForPackage(metadata.salt, packageName) } returns name @@ -314,7 +352,8 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test @Suppress("deprecation") 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) coEvery { kv.hasDataForPackage(token, packageInfo) } returns true @@ -336,7 +375,8 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test @Suppress("Deprecation") 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) coEvery { kv.hasDataForPackage(token, packageInfo) } throws IOException() @@ -347,7 +387,8 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test @Suppress("deprecation") 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) coEvery { kv.hasDataForPackage(token, packageInfo) } returns false diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt index 752eab1a..6e9370a0 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt @@ -55,6 +55,7 @@ internal class RestoreV0IntegrationTest : TransportTest() { private val metadataReader = MetadataReaderImpl(cryptoImpl) private val notificationManager = mockk() private val backendManager: BackendManager = mockk() + private val loader = mockk() @Suppress("Deprecation") private val legacyPlugin = mockk() @@ -76,10 +77,14 @@ internal class RestoreV0IntegrationTest : TransportTest() { metadataManager = metadataManager, notificationManager = notificationManager, backendManager = backendManager, + loader = loader, kv = kvRestore, full = fullRestore, metadataReader = metadataReader, - ).apply { beforeStartRestore(metadata.copy(version = 0x00)) } + ).apply { + val backup = RestorableBackup(metadata.copy(version = 0x00)) + beforeStartRestore(backup) + } private val fileDescriptor = mockk(relaxed = true) private val appData = ("562AB665C3543120FC794D7CDA3AC18E5959235A4D" +