From 0c1dfb316d6b2d0a875ed9e14a58d15765888da3 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 27 Aug 2024 14:57:07 -0300 Subject: [PATCH] Use new Backend directly in the app --- .../transport/backup/PackageServiceTest.kt | 6 +- .../seedvault/plugins/BackendExt.kt | 37 +++++++++++ .../seedvault/plugins/StoragePluginManager.kt | 27 ++++---- .../saf/DocumentsProviderStoragePlugin.kt | 4 +- .../seedvault/plugins/saf/SafFactory.kt | 10 ++- .../seedvault/plugins/saf/SafHandler.kt | 5 +- .../seedvault/plugins/saf/SafStorage.kt | 9 +++ .../seedvault/plugins/webdav/WebDavFactory.kt | 7 +- .../seedvault/plugins/webdav/WebDavHandler.kt | 18 ++--- .../plugins/webdav/WebDavStoragePlugin.kt | 4 +- .../restore/AppDataRestoreManager.kt | 2 +- .../seedvault/restore/AppSelectionManager.kt | 6 +- .../seedvault/restore/install/ApkRestore.kt | 7 +- .../seedvault/settings/SettingsManager.kt | 12 ++-- .../seedvault/storage/WebDavStoragePlugin.kt | 8 +-- .../transport/backup/BackupCoordinator.kt | 11 ++-- .../seedvault/transport/backup/FullBackup.kt | 7 +- .../seedvault/transport/backup/KVBackup.kt | 8 ++- .../transport/backup/PackageService.kt | 8 +-- .../transport/restore/FullRestore.kt | 6 +- .../seedvault/transport/restore/KVRestore.kt | 6 +- .../transport/restore/RestoreCoordinator.kt | 7 +- .../ui/storage/BackupStorageViewModel.kt | 6 +- .../ui/storage/RestoreStorageViewModel.kt | 8 +-- .../seedvault/ui/storage/StorageViewModel.kt | 8 +-- .../ui/storage/WebDavConfigFragment.kt | 2 +- .../seedvault/worker/ApkBackupManager.kt | 10 ++- .../restore/AppSelectionManagerTest.kt | 16 ++--- .../restore/install/ApkBackupRestoreTest.kt | 15 +++-- .../restore/install/ApkRestoreTest.kt | 65 ++++++++++--------- .../transport/CoordinatorIntegrationTest.kt | 38 +++++++---- .../seedvault/transport/TransportTest.kt | 2 + .../transport/backup/BackupCoordinatorTest.kt | 29 ++++----- .../transport/backup/FullBackupTest.kt | 21 +++--- .../transport/backup/KVBackupTest.kt | 18 ++--- .../transport/restore/FullRestoreTest.kt | 22 +++---- .../transport/restore/KVRestoreTest.kt | 14 ++-- .../restore/RestoreCoordinatorTest.kt | 15 +++-- .../restore/RestoreV0IntegrationTest.kt | 5 +- .../seedvault/worker/ApkBackupManagerTest.kt | 14 ++-- .../test/resources/simplelogger.properties | 2 +- .../seedvault/core/backends/Backend.kt | 8 +-- .../seedvault/core/backends/BackendTest.kt | 6 +- .../seedvault/core/backends/saf/SafBackend.kt | 15 ++--- .../core/backends/webdav/WebDavBackend.kt | 13 ++-- .../storage/plugin/saf/SafStoragePlugin.kt | 8 +-- 46 files changed, 331 insertions(+), 244 deletions(-) create mode 100644 app/src/main/java/com/stevesoltys/seedvault/plugins/BackendExt.kt diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt index 046b3024..971b07ad 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt @@ -9,12 +9,12 @@ import android.content.pm.PackageInfo import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.settings.AppStatus import com.stevesoltys.seedvault.settings.SettingsManager import io.mockk.every import io.mockk.mockk +import org.calyxos.seedvault.core.backends.Backend import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test @@ -32,7 +32,7 @@ class PackageServiceTest : KoinComponent { private val storagePluginManager: StoragePluginManager by inject() - private val storagePlugin: StoragePlugin<*> get() = storagePluginManager.appPlugin + private val backend: Backend get() = storagePluginManager.backend @Test fun testNotAllowedPackages() { @@ -65,6 +65,6 @@ class PackageServiceTest : KoinComponent { assertTrue(packageService.shouldIncludeAppInBackup(packageInfo.packageName)) // Should not backup storage provider - assertFalse(packageService.shouldIncludeAppInBackup(storagePlugin.providerPackageName!!)) + assertFalse(packageService.shouldIncludeAppInBackup(backend.providerPackageName!!)) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/BackendExt.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/BackendExt.kt new file mode 100644 index 00000000..ffd3909e --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/BackendExt.kt @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.plugins + +import android.util.Log +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile +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) + } + 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 + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePluginManager.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePluginManager.kt index 5872e619..5dacebc6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePluginManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePluginManager.kt @@ -10,11 +10,12 @@ import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.getStorageContext import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderStoragePlugin import com.stevesoltys.seedvault.plugins.saf.SafFactory import com.stevesoltys.seedvault.plugins.webdav.WebDavFactory import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.StoragePluginType +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.saf.SafBackend class StoragePluginManager( private val context: Context, @@ -23,14 +24,14 @@ class StoragePluginManager( webDavFactory: WebDavFactory, ) { - private var mAppPlugin: StoragePlugin<*>? + private var mBackend: Backend? private var mFilesPlugin: org.calyxos.backup.storage.api.StoragePlugin? private var mStorageProperties: StorageProperties<*>? - val appPlugin: StoragePlugin<*> + val backend: Backend @Synchronized get() { - return mAppPlugin ?: error("App plugin was loaded, but still null") + return mBackend ?: error("App plugin was loaded, but still null") } val filesPlugin: org.calyxos.backup.storage.api.StoragePlugin @@ -50,7 +51,7 @@ class StoragePluginManager( when (settingsManager.storagePluginType) { StoragePluginType.SAF -> { val safStorage = settingsManager.getSafStorage() ?: error("No SAF storage saved") - mAppPlugin = safFactory.createAppStoragePlugin(safStorage) + mBackend = safFactory.createBackend(safStorage) mFilesPlugin = safFactory.createFilesStoragePlugin(safStorage) mStorageProperties = safStorage } @@ -58,13 +59,13 @@ class StoragePluginManager( StoragePluginType.WEB_DAV -> { val webDavProperties = settingsManager.webDavProperties ?: error("No WebDAV config saved") - mAppPlugin = webDavFactory.createAppStoragePlugin(webDavProperties.config) + mBackend = webDavFactory.createBackend(webDavProperties.config) mFilesPlugin = webDavFactory.createFilesStoragePlugin(webDavProperties.config) mStorageProperties = webDavProperties } null -> { - mAppPlugin = null + mBackend = null mFilesPlugin = null mStorageProperties = null } @@ -72,8 +73,8 @@ class StoragePluginManager( } fun isValidAppPluginSet(): Boolean { - if (mAppPlugin == null || mFilesPlugin == null) return false - if (mAppPlugin is DocumentsProviderStoragePlugin) { + if (mBackend == null || mFilesPlugin == null) return false + if (mBackend is SafBackend) { val storage = settingsManager.getSafStorage() ?: return false if (storage.isUsb) return true return permitDiskReads { @@ -91,12 +92,12 @@ class StoragePluginManager( */ fun changePlugins( storageProperties: StorageProperties, - appPlugin: StoragePlugin, + backend: Backend, filesPlugin: org.calyxos.backup.storage.api.StoragePlugin, ) { - settingsManager.setStoragePlugin(appPlugin) + settingsManager.setStorageBackend(backend) mStorageProperties = storageProperties - mAppPlugin = appPlugin + mBackend = backend mFilesPlugin = filesPlugin } @@ -136,7 +137,7 @@ class StoragePluginManager( @WorkerThread suspend fun getFreeSpace(): Long? { return try { - appPlugin.getFreeSpace() + backend.getFreeSpace() } catch (e: Throwable) { // NoClassDefFound isn't an [Exception], can get thrown by dav4jvm Log.e("StoragePluginManager", "Error getting free space: ", e) null diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt index 2d1a5db1..3c83eda0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt @@ -61,7 +61,7 @@ internal class DocumentsProviderStoragePlugin( FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) else -> LegacyAppBackupFile.Blob(token, name) } - return delegate.save(handle).outputStream() + return delegate.save(handle) } @Throws(IOException::class) @@ -71,7 +71,7 @@ internal class DocumentsProviderStoragePlugin( FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) else -> LegacyAppBackupFile.Blob(token, name) } - return delegate.load(handle).inputStream() + return delegate.load(handle) } @Throws(IOException::class) diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafFactory.kt index d478414a..c8705753 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafFactory.kt @@ -6,18 +6,16 @@ package com.stevesoltys.seedvault.plugins.saf import android.content.Context -import android.net.Uri -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.storage.SeedvaultSafStoragePlugin +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.saf.SafBackend class SafFactory( private val context: Context, ) { - internal fun createAppStoragePlugin( - safStorage: SafStorage, - ): StoragePlugin { - return DocumentsProviderStoragePlugin(context, safStorage) + internal fun createBackend(safStorage: SafStorage): Backend { + return SafBackend(context, safStorage.toSafConfig()) } internal fun createFilesStoragePlugin( diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafHandler.kt index 6d5cce16..4b3f6b6f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafHandler.kt @@ -16,6 +16,7 @@ import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.isMassStorage import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.plugins.getAvailableBackups import com.stevesoltys.seedvault.settings.FlashDrive import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.storage.StorageOption @@ -50,7 +51,7 @@ internal class SafHandler( @WorkerThread @Throws(IOException::class) suspend fun hasAppBackup(safStorage: SafStorage): Boolean { - val appPlugin = safFactory.createAppStoragePlugin(safStorage) + val appPlugin = safFactory.createBackend(safStorage) val backups = appPlugin.getAvailableBackups() return backups != null && backups.iterator().hasNext() } @@ -86,7 +87,7 @@ internal class SafHandler( fun setPlugin(safStorage: SafStorage) { storagePluginManager.changePlugins( storageProperties = safStorage, - appPlugin = safFactory.createAppStoragePlugin(safStorage), + backend = safFactory.createBackend(safStorage), filesPlugin = safFactory.createFilesStoragePlugin(safStorage), ) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorage.kt index dae9410b..5018e5c5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorage.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorage.kt @@ -11,6 +11,7 @@ import android.provider.DocumentsContract.Root.COLUMN_ROOT_ID import androidx.annotation.WorkerThread import androidx.documentfile.provider.DocumentFile import com.stevesoltys.seedvault.plugins.StorageProperties +import org.calyxos.seedvault.core.backends.saf.SafConfig data class SafStorage( override val config: Uri, @@ -38,4 +39,12 @@ data class SafStorage( override fun isUnavailableUsb(context: Context): Boolean { return isUsb && !getDocumentFile(context).isDirectory } + + fun toSafConfig() = SafConfig( + config = config, + name = name, + isUsb = isUsb, + requiresNetwork = requiresNetwork, + rootId = rootId, + ) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavFactory.kt index bc8016b7..1b8859a4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavFactory.kt @@ -8,16 +8,15 @@ package com.stevesoltys.seedvault.plugins.webdav import android.annotation.SuppressLint import android.content.Context import android.provider.Settings -import com.stevesoltys.seedvault.plugins.StoragePlugin +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.webdav.WebDavBackend import org.calyxos.seedvault.core.backends.webdav.WebDavConfig class WebDavFactory( private val context: Context, ) { - fun createAppStoragePlugin(config: WebDavConfig): StoragePlugin { - return WebDavStoragePlugin(config) - } + fun createBackend(config: WebDavConfig): Backend = WebDavBackend(config) fun createFilesStoragePlugin( config: WebDavConfig, diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavHandler.kt index d93f7f6c..904cc970 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavHandler.kt @@ -10,10 +10,12 @@ import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.plugins.getAvailableBackups import com.stevesoltys.seedvault.settings.SettingsManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import okhttp3.HttpUrl.Companion.toHttpUrl +import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.webdav.WebDavConfig import java.io.IOException @@ -22,7 +24,7 @@ internal sealed interface WebDavConfigState { object Checking : WebDavConfigState class Success( val properties: WebDavProperties, - val plugin: WebDavStoragePlugin, + val backend: Backend, ) : WebDavConfigState class Error(val e: Exception?) : WebDavConfigState @@ -52,11 +54,11 @@ internal class WebDavHandler( suspend fun onConfigReceived(config: WebDavConfig) { mConfigState.value = WebDavConfigState.Checking - val plugin = webDavFactory.createAppStoragePlugin(config) as WebDavStoragePlugin + val backend = webDavFactory.createBackend(config) try { - if (plugin.test()) { + if (backend.test()) { val properties = createWebDavProperties(context, config) - mConfigState.value = WebDavConfigState.Success(properties, plugin) + mConfigState.value = WebDavConfigState.Success(properties, backend) } else { mConfigState.value = WebDavConfigState.Error(null) } @@ -76,8 +78,8 @@ internal class WebDavHandler( */ @WorkerThread @Throws(IOException::class) - suspend fun hasAppBackup(appPlugin: WebDavStoragePlugin): Boolean { - val backups = appPlugin.getAvailableBackups() + suspend fun hasAppBackup(backend: Backend): Boolean { + val backups = backend.getAvailableBackups() return backups != null && backups.iterator().hasNext() } @@ -85,10 +87,10 @@ internal class WebDavHandler( settingsManager.saveWebDavConfig(properties.config) } - fun setPlugin(properties: WebDavProperties, plugin: WebDavStoragePlugin) { + fun setPlugin(properties: WebDavProperties, backend: Backend) { storagePluginManager.changePlugins( storageProperties = properties, - appPlugin = plugin, + backend = backend, filesPlugin = webDavFactory.createFilesStoragePlugin(properties.config), ) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePlugin.kt index bc48607f..47554dfd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePlugin.kt @@ -49,7 +49,7 @@ internal class WebDavStoragePlugin( FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) else -> LegacyAppBackupFile.Blob(token, name) } - return delegate.save(handle).outputStream() + return delegate.save(handle) } @Throws(IOException::class) @@ -59,7 +59,7 @@ internal class WebDavStoragePlugin( FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) else -> LegacyAppBackupFile.Blob(token, name) } - return delegate.load(handle).inputStream() + return delegate.load(handle) } @Throws(IOException::class) 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 8d8bb01f..c51cb8c4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt @@ -101,7 +101,7 @@ internal class AppDataRestoreManager( return } - val providerPackageName = storagePluginManager.appPlugin.providerPackageName + val providerPackageName = storagePluginManager.backend.providerPackageName val observer = RestoreObserver( restoreCoordinator = restoreCoordinator, restorableBackup = restorableBackup, 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 a60c76b6..b46cb828 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt @@ -17,7 +17,6 @@ import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM import com.stevesoltys.seedvault.ui.systemData -import com.stevesoltys.seedvault.worker.FILE_BACKUP_ICONS import com.stevesoltys.seedvault.worker.IconManager import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -25,6 +24,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.util.Locale internal class SelectedAppsState( @@ -88,10 +88,10 @@ internal class AppSelectionManager( SelectedAppsState(apps = items, allSelected = isSetupWizard, iconsLoaded = false) // download icons coroutineScope.launch(workDispatcher) { - val plugin = pluginManager.appPlugin + val backend = pluginManager.backend val token = restorableBackup.token val packagesWithIcons = try { - plugin.getInputStream(token, FILE_BACKUP_ICONS).use { + backend.load(LegacyAppBackupFile.IconsFile(token)).use { iconManager.downloadIcons(restorableBackup.version, token, it) } } catch (e: Exception) { 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 d3570a97..0e9e7b01 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 @@ -34,6 +34,7 @@ import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.File import java.io.IOException import java.util.Locale @@ -54,7 +55,7 @@ internal class ApkRestore( ) { private val pm = context.packageManager - private val storagePlugin get() = pluginManager.appPlugin + private val backend get() = pluginManager.backend private val mInstallResult = MutableStateFlow(InstallResult()) val installResult = mInstallResult.asStateFlow() @@ -65,7 +66,7 @@ internal class ApkRestore( val packages = backup.packageMetadataMap.mapNotNull { (packageName, metadata) -> // We need to exclude the DocumentsProvider used to retrieve backup data. // Otherwise, it gets killed when we install it, terminating our restoration. - if (packageName == storagePlugin.providerPackageName) return@mapNotNull null + if (packageName == backend.providerPackageName) return@mapNotNull null // The @pm@ package needs to be included in [backup], but can't be installed like an app if (packageName == MAGIC_PACKAGE_MANAGER) return@mapNotNull null // we don't filter out apps without APK, so the user can manually install them @@ -294,7 +295,7 @@ internal class ApkRestore( legacyStoragePlugin.getApkInputStream(token, packageName, suffix) } else { val name = crypto.getNameForApk(salt, packageName, suffix) - storagePlugin.getInputStream(token, name) + backend.load(LegacyAppBackupFile.Blob(token, name)) } val sha256 = copyStreamsAndGetHash(inputStream, cachedApk.outputStream()) return Pair(cachedApk, sha256) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt index 13cbc75a..5dee1ff5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt @@ -12,13 +12,13 @@ import android.net.Uri import androidx.annotation.UiThread import androidx.preference.PreferenceManager import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderStoragePlugin import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler.Companion.createWebDavProperties import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties -import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin import com.stevesoltys.seedvault.transport.backup.BackupCoordinator +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.saf.SafBackend +import org.calyxos.seedvault.core.backends.webdav.WebDavBackend import org.calyxos.seedvault.core.backends.webdav.WebDavConfig import java.util.concurrent.ConcurrentSkipListSet @@ -128,10 +128,10 @@ class SettingsManager(private val context: Context) { } } - fun setStoragePlugin(plugin: StoragePlugin<*>) { + fun setStorageBackend(plugin: Backend) { val value = when (plugin) { - is DocumentsProviderStoragePlugin -> StoragePluginType.SAF - is WebDavStoragePlugin -> StoragePluginType.WEB_DAV + is SafBackend -> StoragePluginType.SAF + is WebDavBackend -> StoragePluginType.WEB_DAV else -> error("Unsupported plugin: ${plugin::class.java.simpleName}") }.name prefs.edit() diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/WebDavStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/WebDavStoragePlugin.kt index 7f458a30..75b1113e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/WebDavStoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/storage/WebDavStoragePlugin.kt @@ -45,13 +45,13 @@ internal class WebDavStoragePlugin( @Throws(IOException::class) override suspend fun getChunkOutputStream(chunkId: String): OutputStream { val fileHandle = FileBackupFileType.Blob(androidId, chunkId) - return delegate.save(fileHandle).outputStream() + return delegate.save(fileHandle) } @Throws(IOException::class) override suspend fun getBackupSnapshotOutputStream(timestamp: Long): OutputStream { val fileHandle = FileBackupFileType.Snapshot(androidId, timestamp) - return delegate.save(fileHandle).outputStream() + return delegate.save(fileHandle) } /************************* Restore *******************************/ @@ -73,7 +73,7 @@ internal class WebDavStoragePlugin( override suspend fun getBackupSnapshotInputStream(storedSnapshot: StoredSnapshot): InputStream { val androidId = storedSnapshot.androidId val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp) - return delegate.load(handle).inputStream() + return delegate.load(handle) } @Throws(IOException::class) @@ -82,7 +82,7 @@ internal class WebDavStoragePlugin( chunkId: String, ): InputStream { val handle = FileBackupFileType.Blob(snapshot.androidId, chunkId) - return delegate.load(handle).inputStream() + return delegate.load(handle) } /************************* Pruning *******************************/ diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index 510428d4..2efd5cbe 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -31,6 +31,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.plugins.getMetadataOutputStream import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.settings.SettingsManager @@ -74,7 +75,7 @@ internal class BackupCoordinator( private val nm: BackupNotificationManager, ) { - private val plugin get() = pluginManager.appPlugin + private val backend get() = pluginManager.backend private val state = CoordinatorState( calledInitialize = false, calledClearBackupData = false, @@ -97,7 +98,6 @@ internal class BackupCoordinator( val token = clock.time() Log.i(TAG, "Starting new RestoreSet with token $token...") settingsManager.setNewToken(token) - plugin.startNewRestoreSet(token) Log.d(TAG, "Resetting backup metadata...") metadataManager.onDeviceInitialization(token) } @@ -125,7 +125,6 @@ internal class BackupCoordinator( // instead of simply deleting the current one startNewRestoreSet() Log.i(TAG, "Initialize Device!") - plugin.initializeDevice() // [finishBackup] will only be called when we return [TRANSPORT_OK] here // so we remember that we initialized successfully state.calledInitialize = true @@ -410,7 +409,8 @@ internal class BackupCoordinator( } private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType, size: Long?) { - plugin.getMetadataOutputStream().use { + val token = settingsManager.getToken() ?: error("no token") + backend.getMetadataOutputStream(token).use { metadataManager.onPackageBackedUp(packageInfo, type, size, it) } } @@ -418,7 +418,8 @@ internal class BackupCoordinator( private suspend fun onPackageBackupError(packageInfo: PackageInfo, type: BackupType) { val packageName = packageInfo.packageName try { - plugin.getMetadataOutputStream().use { + val token = settingsManager.getToken() ?: error("no token") + backend.getMetadataOutputStream(token).use { metadataManager.onPackageBackupError(packageInfo, state.cancelReason, it, type) } } catch (e: IOException) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt index eed8acab..f5d134c4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt @@ -20,6 +20,7 @@ import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.Closeable import java.io.EOFException import java.io.IOException @@ -53,7 +54,7 @@ internal class FullBackup( private val crypto: Crypto, ) { - private val plugin get() = pluginManager.appPlugin + private val backend get() = pluginManager.backend private var state: FullBackupState? = null fun hasState() = state != null @@ -128,7 +129,7 @@ internal class FullBackup( val name = crypto.getNameForPackage(salt, packageName) // get OutputStream to write backup data into val outputStream = try { - plugin.getOutputStream(token, name) + backend.save(LegacyAppBackupFile.Blob(token, name)) } catch (e: IOException) { "Error getting OutputStream for full backup of $packageName".let { Log.e(TAG, it, e) @@ -186,7 +187,7 @@ internal class FullBackup( @Throws(IOException::class) suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) { val name = crypto.getNameForPackage(salt, packageInfo.packageName) - plugin.removeData(token, name) + backend.remove(LegacyAppBackupFile.Blob(token, name)) } suspend fun cancelFullBackup(token: Long, salt: String, ignoreApp: Boolean) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt index 069670ef..7144ad8c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt @@ -22,6 +22,7 @@ import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.IOException import java.util.zip.GZIPOutputStream @@ -47,7 +48,7 @@ internal class KVBackup( private val dbManager: KvDbManager, ) { - private val plugin get() = pluginManager.appPlugin + private val backend get() = pluginManager.backend private var state: KVBackupState? = null fun hasState() = state != null @@ -207,7 +208,7 @@ internal class KVBackup( suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) { Log.i(TAG, "Clearing K/V data of ${packageInfo.packageName}") val name = state?.name ?: crypto.getNameForPackage(salt, packageInfo.packageName) - plugin.removeData(token, name) + backend.remove(LegacyAppBackupFile.Blob(token, name)) if (!dbManager.deleteDb(packageInfo.packageName)) throw IOException() } @@ -254,7 +255,8 @@ internal class KVBackup( db.vacuum() db.close() - plugin.getOutputStream(token, name).use { outputStream -> + val handle = LegacyAppBackupFile.Blob(token, name) + backend.save(handle).use { outputStream -> outputStream.write(ByteArray(1) { VERSION }) val ad = getADForKV(VERSION, packageName) crypto.newEncryptingStream(outputStream, ad).use { encryptedStream -> diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt index c1fb8618..b901d03e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt @@ -27,9 +27,9 @@ import android.util.Log import android.util.Log.INFO import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.settings.SettingsManager +import org.calyxos.seedvault.core.backends.Backend private val TAG = PackageService::class.java.simpleName @@ -48,7 +48,7 @@ internal class PackageService( private val packageManager: PackageManager = context.packageManager private val myUserId = UserHandle.myUserId() - private val plugin: StoragePlugin<*> get() = pluginManager.appPlugin + private val backend: Backend get() = pluginManager.backend val eligiblePackages: List @WorkerThread @@ -182,7 +182,7 @@ internal class PackageService( // We need to explicitly exclude DocumentsProvider and Seedvault. // Otherwise, they get killed while backing them up, terminating our backup. val excludedPackages = setOf( - plugin.providerPackageName, + backend.providerPackageName, context.packageName ) @@ -225,7 +225,7 @@ internal class PackageService( */ private fun PackageInfo.doesNotGetBackedUp(): Boolean { if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true - if (packageName == plugin.providerPackageName) return true + if (packageName == backend.providerPackageName) return true return !allowsBackup() || isStopped() } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt index f0ccb4f3..3f5c2bdc 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt @@ -20,6 +20,7 @@ import com.stevesoltys.seedvault.header.getADForFull import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import libcore.io.IoUtils.closeQuietly +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.EOFException import java.io.IOException import java.io.InputStream @@ -46,7 +47,7 @@ internal class FullRestore( private val crypto: Crypto, ) { - private val plugin get() = pluginManager.appPlugin + private val backend get() = pluginManager.backend private var state: FullRestoreState? = null fun hasState() = state != null @@ -114,7 +115,8 @@ internal class FullRestore( crypto.decryptHeader(inputStream, version, packageName) state.inputStream = inputStream } else { - val inputStream = plugin.getInputStream(state.token, state.name) + val handle = LegacyAppBackupFile.Blob(state.token, state.name) + val inputStream = backend.load(handle) val version = headerReader.readVersion(inputStream, state.version) val ad = getADForFull(version, packageName) state.inputStream = crypto.newDecryptingStream(inputStream, ad) diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt index b6020bda..d6ebadb6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt @@ -25,6 +25,7 @@ import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.transport.backup.KVDb import com.stevesoltys.seedvault.transport.backup.KvDbManager import libcore.io.IoUtils.closeQuietly +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.IOException import java.security.GeneralSecurityException import java.util.zip.GZIPInputStream @@ -53,7 +54,7 @@ internal class KVRestore( private val dbManager: KvDbManager, ) { - private val plugin get() = pluginManager.appPlugin + private val backend get() = pluginManager.backend private var state: KVRestoreState? = null /** @@ -156,7 +157,8 @@ internal class KVRestore( @Throws(IOException::class, GeneralSecurityException::class, UnsupportedVersionException::class) private suspend fun downloadRestoreDb(state: KVRestoreState): KVDb { val packageName = state.packageInfo.packageName - plugin.getInputStream(state.token, state.name).use { inputStream -> + val handle = LegacyAppBackupFile.Blob(state.token, state.name) + backend.load(handle).use { inputStream -> headerReader.readVersion(inputStream, state.version) val ad = getADForKV(VERSION, packageName) crypto.newDecryptingStream(inputStream, ad).use { decryptedStream -> 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 98fa4d4e..b0703cc7 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 @@ -25,12 +25,13 @@ 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.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.plugins.getAvailableBackups 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.Backend import java.io.IOException /** @@ -67,13 +68,13 @@ internal class RestoreCoordinator( private val metadataReader: MetadataReader, ) { - private val plugin: StoragePlugin<*> get() = pluginManager.appPlugin + private val backend: Backend get() = pluginManager.backend private var state: RestoreCoordinatorState? = null private var backupMetadata: BackupMetadata? = null private val failedPackages = ArrayList() suspend fun getAvailableMetadata(): Map? { - val availableBackups = plugin.getAvailableBackups() ?: return null + val availableBackups = backend.getAvailableBackups() ?: return null val metadataMap = HashMap() for (encryptedMetadata in availableBackups) { try { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt index 8e71d9cb..afe25077 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt @@ -18,7 +18,6 @@ import com.stevesoltys.seedvault.plugins.saf.SafHandler import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties -import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.storage.StorageBackupJobService import com.stevesoltys.seedvault.transport.backup.BackupInitializer @@ -27,6 +26,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.backup.BackupJobService +import org.calyxos.seedvault.core.backends.Backend import java.io.IOException import java.util.concurrent.TimeUnit @@ -59,9 +59,9 @@ internal class BackupStorageViewModel( onStorageLocationSet(safStorage.isUsb) } - override fun onWebDavConfigSet(properties: WebDavProperties, plugin: WebDavStoragePlugin) { + override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) { webdavHandler.save(properties) - webdavHandler.setPlugin(properties, plugin) + webdavHandler.setPlugin(properties, backend) scheduleBackupWorkers() onStorageLocationSet(isUsb = false) } 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 b92607ec..9c5f3bbb 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 @@ -15,10 +15,10 @@ import com.stevesoltys.seedvault.plugins.saf.SafHandler import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties -import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin import com.stevesoltys.seedvault.settings.SettingsManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.calyxos.seedvault.core.backends.Backend import java.io.IOException private val TAG = RestoreStorageViewModel::class.java.simpleName @@ -56,17 +56,17 @@ internal class RestoreStorageViewModel( } } - override fun onWebDavConfigSet(properties: WebDavProperties, plugin: WebDavStoragePlugin) { + override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) { viewModelScope.launch(Dispatchers.IO) { val hasBackup = try { - webdavHandler.hasAppBackup(plugin) + webdavHandler.hasAppBackup(backend) } catch (e: IOException) { Log.e(TAG, "Error reading: ${properties.config.url}", e) false } if (hasBackup) { webdavHandler.save(properties) - webdavHandler.setPlugin(properties, plugin) + webdavHandler.setPlugin(properties, backend) mLocationChecked.postEvent(LocationResult()) } else { Log.w(TAG, "Location was rejected: ${properties.config.url}") diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt index 927646a1..9ce2e020 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt @@ -18,13 +18,13 @@ import com.stevesoltys.seedvault.plugins.saf.SafHandler import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties -import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.webdav.WebDavConfig internal abstract class StorageViewModel( @@ -89,7 +89,7 @@ internal abstract class StorageViewModel( } abstract fun onSafUriSet(safStorage: SafStorage) - abstract fun onWebDavConfigSet(properties: WebDavProperties, plugin: WebDavStoragePlugin) + abstract fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) override fun onCleared() { storageOptionFetcher.setRemovableStorageListener(null) @@ -107,9 +107,9 @@ internal abstract class StorageViewModel( fun resetWebDavConfig() = webdavHandler.resetConfigState() @UiThread - fun onWebDavConfigSuccess(properties: WebDavProperties, plugin: WebDavStoragePlugin) { + fun onWebDavConfigSuccess(properties: WebDavProperties, backend: Backend) { mLocationSet.setEvent(true) - onWebDavConfigSet(properties, plugin) + onWebDavConfigSet(properties, backend) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt index a302a8d5..ebf2278d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt @@ -111,7 +111,7 @@ class WebDavConfigFragment : Fragment(), View.OnClickListener { } is WebDavConfigState.Success -> { - viewModel.onWebDavConfigSuccess(state.properties, state.plugin) + viewModel.onWebDavConfigSuccess(state.properties, state.backend) } is WebDavConfigState.Error -> { diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt index 0e5d48d6..9174f042 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt @@ -13,6 +13,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.plugins.getMetadataOutputStream import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.settings.SettingsManager @@ -21,6 +22,7 @@ import com.stevesoltys.seedvault.transport.backup.isStopped import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.getAppName import kotlinx.coroutines.delay +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.IOException import java.io.OutputStream @@ -55,7 +57,8 @@ internal class ApkBackupManager( keepTrying { // upload all local changes only at the end, // so we don't have to re-upload the metadata - pluginManager.appPlugin.getMetadataOutputStream().use { outputStream -> + val token = settingsManager.getToken() ?: error("no token") + pluginManager.backend.getMetadataOutputStream(token).use { outputStream -> metadataManager.uploadMetadata(outputStream) } } @@ -101,7 +104,8 @@ internal class ApkBackupManager( private suspend fun uploadIcons() { try { val token = settingsManager.getToken() ?: throw IOException("no current token") - pluginManager.appPlugin.getOutputStream(token, FILE_BACKUP_ICONS).use { + val handle = LegacyAppBackupFile.IconsFile(token) + pluginManager.backend.save(handle).use { iconManager.uploadIcons(token, it) } } catch (e: IOException) { @@ -119,7 +123,7 @@ internal class ApkBackupManager( return try { apkBackup.backupApkIfNecessary(packageInfo) { name -> val token = settingsManager.getToken() ?: throw IOException("no current token") - pluginManager.appPlugin.getOutputStream(token, name) + pluginManager.backend.save(LegacyAppBackupFile.Blob(token, name)) }?.let { packageMetadata -> metadataManager.onApkBackedUp(packageInfo, packageMetadata) true 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 d6c85b57..6d61f866 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt @@ -13,13 +13,11 @@ import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.metadata.BackupMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.ui.PACKAGE_NAME_CONTACTS import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SETTINGS import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM -import com.stevesoltys.seedvault.worker.FILE_BACKUP_ICONS import com.stevesoltys.seedvault.worker.IconManager import io.mockk.coEvery import io.mockk.every @@ -28,6 +26,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi 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 @@ -221,10 +221,10 @@ internal class AppSelectionManagerTest : TransportTest() { @Test fun `test icon loading fails`() = scope.runTest { - val appPlugin: StoragePlugin<*> = mockk() - every { storagePluginManager.appPlugin } returns appPlugin + val backend: Backend = mockk() + every { storagePluginManager.backend } returns backend coEvery { - appPlugin.getInputStream(backupMetadata.token, FILE_BACKUP_ICONS) + backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token)) } throws IOException() appSelectionManager.selectedAppsFlow.test { @@ -427,11 +427,11 @@ internal class AppSelectionManagerTest : TransportTest() { } private fun expectIconLoading(icons: Set = setOf(packageName1, packageName2)) { - val appPlugin: StoragePlugin<*> = mockk() + val backend: Backend = mockk() val inputStream = ByteArrayInputStream(Random.nextBytes(42)) - every { storagePluginManager.appPlugin } returns appPlugin + every { storagePluginManager.backend } returns backend coEvery { - appPlugin.getInputStream(backupMetadata.token, FILE_BACKUP_ICONS) + backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token)) } returns inputStream every { iconManager.downloadIcons(backupMetadata.version, backupMetadata.token, inputStream) diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt index 12c5761c..61c64049 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt @@ -21,7 +21,6 @@ import com.stevesoltys.seedvault.metadata.ApkSplit import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS @@ -36,6 +35,8 @@ import io.mockk.mockkStatic import io.mockk.slot import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse @@ -65,7 +66,7 @@ internal class ApkBackupRestoreTest : TransportTest() { @Suppress("Deprecation") private val legacyStoragePlugin: LegacyStoragePlugin = mockk() - private val storagePlugin: StoragePlugin<*> = mockk() + private val backend: Backend = mockk() private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk() private val apkInstaller: ApkInstaller = mockk() private val installRestriction: InstallRestriction = mockk() @@ -111,7 +112,7 @@ internal class ApkBackupRestoreTest : TransportTest() { init { mockkStatic(PackageUtils::class) - every { storagePluginManager.appPlugin } returns storagePlugin + every { storagePluginManager.backend } returns backend } @Test @@ -147,7 +148,7 @@ internal class ApkBackupRestoreTest : TransportTest() { every { metadataManager.salt } returns salt every { crypto.getNameForApk(salt, packageName) } returns name every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkBackup.backupApkIfNecessary(packageInfo, outputStreamGetter) @@ -164,7 +165,7 @@ internal class ApkBackupRestoreTest : TransportTest() { every { pm.getPackageInfo(packageName, any()) } throws NameNotFoundException() every { strictContext.cacheDir } returns tmpFile every { crypto.getNameForApk(salt, packageName, "") } returns name - coEvery { storagePlugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(LegacyAppBackupFile.Blob(token, name)) } returns inputStream every { pm.getPackageArchiveInfo(capture(apkPath), any()) } returns packageInfo every { applicationInfo.loadIcon(pm) } returns icon every { pm.getApplicationLabel(packageInfo.applicationInfo!!) } returns appName @@ -172,7 +173,9 @@ internal class ApkBackupRestoreTest : TransportTest() { splitCompatChecker.isCompatible(metadata.deviceName, listOf(splitName)) } returns true every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName - coEvery { storagePlugin.getInputStream(token, suffixName) } returns splitInputStream + coEvery { + backend.load(LegacyAppBackupFile.Blob(token, suffixName)) + } returns splitInputStream val resultMap = mapOf( packageName to ApkInstallResult( packageName, 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 808723d2..fbfedd52 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 @@ -25,7 +25,6 @@ import com.stevesoltys.seedvault.metadata.ApkSplit import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED @@ -44,6 +43,8 @@ import io.mockk.mockkStatic import io.mockk.verifyOrder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse @@ -67,7 +68,7 @@ internal class ApkRestoreTest : TransportTest() { private val backupManager: IBackupManager = mockk() private val backupStateManager: BackupStateManager = mockk() private val storagePluginManager: StoragePluginManager = mockk() - private val storagePlugin: StoragePlugin<*> = mockk() + private val backend: Backend = mockk() private val legacyStoragePlugin: LegacyStoragePlugin = mockk() private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk() private val apkInstaller: ApkInstaller = mockk() @@ -108,7 +109,7 @@ internal class ApkRestoreTest : TransportTest() { // as we don't do strict signature checking, we can use a relaxed mock packageInfo.signingInfo = mockk(relaxed = true) - every { storagePluginManager.appPlugin } returns storagePlugin + every { storagePluginManager.backend } returns backend // related to starting/stopping service every { strictContext.packageName } returns "org.foo.bar" @@ -128,8 +129,8 @@ internal class ApkRestoreTest : TransportTest() { every { backupStateManager.isAutoRestoreEnabled } returns false every { strictContext.cacheDir } returns File(tmpDir.toString()) every { crypto.getNameForApk(salt, packageName, "") } returns name - coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream - every { storagePlugin.providerPackageName } returns storageProviderPackageName + coEvery { backend.load(handle) } returns apkInputStream + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -151,7 +152,7 @@ internal class ApkRestoreTest : TransportTest() { every { installRestriction.isAllowedToInstallApks() } returns true every { backupStateManager.isAutoRestoreEnabled } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName every { pm.getPackageInfo(packageName, any()) } throws NameNotFoundException() apkRestore.installResult.test { @@ -177,7 +178,7 @@ internal class ApkRestoreTest : TransportTest() { every { installRestriction.isAllowedToInstallApks() } returns true every { backupStateManager.isAutoRestoreEnabled } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName val packageInfo: PackageInfo = mockk() every { pm.getPackageInfo(packageName, any()) } returns packageInfo @@ -202,9 +203,9 @@ internal class ApkRestoreTest : TransportTest() { every { backupStateManager.isAutoRestoreEnabled } returns false every { strictContext.cacheDir } returns File(tmpDir.toString()) every { crypto.getNameForApk(salt, packageName, "") } returns name - coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream + coEvery { backend.load(handle) } returns apkInputStream every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -222,7 +223,7 @@ internal class ApkRestoreTest : TransportTest() { coEvery { apkInstaller.install(match { it.size == 1 }, packageName, installerName, any()) } throws SecurityException() - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -249,7 +250,7 @@ internal class ApkRestoreTest : TransportTest() { coEvery { apkInstaller.install(match { it.size == 1 }, packageName, installerName, any()) } returns installResult - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -285,7 +286,7 @@ internal class ApkRestoreTest : TransportTest() { coEvery { apkInstaller.install(match { it.size == 1 }, packageName, installerName, any()) } returns installResult - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -300,7 +301,7 @@ internal class ApkRestoreTest : TransportTest() { mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt") every { installRestriction.isAllowedToInstallApks() } returns true every { backupStateManager.isAutoRestoreEnabled } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName every { pm.getPackageInfo(packageName, any()) } returns packageInfo every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!! every { @@ -329,7 +330,7 @@ internal class ApkRestoreTest : TransportTest() { mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt") every { installRestriction.isAllowedToInstallApks() } returns true every { backupStateManager.isAutoRestoreEnabled } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName every { pm.getPackageInfo(packageName, any()) } returns packageInfo every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!! every { packageInfo.longVersionCode } returns packageMetadata.version!! - 1 @@ -369,7 +370,7 @@ internal class ApkRestoreTest : TransportTest() { mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt") every { installRestriction.isAllowedToInstallApks() } returns true every { backupStateManager.isAutoRestoreEnabled } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName every { pm.getPackageInfo(packageName, any()) } returns packageInfo every { packageInfo.signingInfo.getSignatures() } returns listOf("foobar") @@ -401,7 +402,7 @@ internal class ApkRestoreTest : TransportTest() { every { backupStateManager.isAutoRestoreEnabled } returns false every { pm.getPackageInfo(packageName, any()) } throws NameNotFoundException() cacheBaseApkAndGetInfo(tmpDir) - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName if (willFail) { every { @@ -476,7 +477,7 @@ internal class ApkRestoreTest : TransportTest() { every { splitCompatChecker.isCompatible(deviceName, listOf(split1Name, split2Name)) } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -502,9 +503,9 @@ internal class ApkRestoreTest : TransportTest() { every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName coEvery { - storagePlugin.getInputStream(token, suffixName) + backend.load(LegacyAppBackupFile.Blob(token, suffixName)) } returns ByteArrayInputStream(getRandomByteArray()) - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -531,8 +532,10 @@ internal class ApkRestoreTest : TransportTest() { every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName - coEvery { storagePlugin.getInputStream(token, suffixName) } throws IOException() - every { storagePlugin.providerPackageName } returns storageProviderPackageName + coEvery { + backend.load(LegacyAppBackupFile.Blob(token, suffixName)) + } throws IOException() + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -573,10 +576,14 @@ internal class ApkRestoreTest : TransportTest() { val suffixName1 = getRandomString() val suffixName2 = getRandomString() every { crypto.getNameForApk(salt, packageName, split1Name) } returns suffixName1 - coEvery { storagePlugin.getInputStream(token, suffixName1) } returns split1InputStream + coEvery { + backend.load(LegacyAppBackupFile.Blob(token, suffixName1)) + } returns split1InputStream every { crypto.getNameForApk(salt, packageName, split2Name) } returns suffixName2 - coEvery { storagePlugin.getInputStream(token, suffixName2) } returns split2InputStream - every { storagePlugin.providerPackageName } returns storageProviderPackageName + coEvery { + backend.load(LegacyAppBackupFile.Blob(token, suffixName2)) + } returns split2InputStream + every { backend.providerPackageName } returns storageProviderPackageName val resultMap = mapOf( packageName to ApkInstallResult( @@ -602,7 +609,7 @@ internal class ApkRestoreTest : TransportTest() { every { backupStateManager.isAutoRestoreEnabled } returns false // set the storage provider package name to match our current package name, // and ensure that the current package is therefore skipped. - every { storagePlugin.providerPackageName } returns packageName + every { backend.providerPackageName } returns packageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -627,7 +634,7 @@ internal class ApkRestoreTest : TransportTest() { every { installRestriction.isAllowedToInstallApks() } returns true every { backupStateManager.isAutoRestoreEnabled } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -656,7 +663,7 @@ internal class ApkRestoreTest : TransportTest() { every { installRestriction.isAllowedToInstallApks() } returns true every { backupStateManager.isAutoRestoreEnabled } returns true - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName every { backupManager.setAutoRestore(false) } just Runs every { pm.getPackageInfo(packageName, any()) } throws NameNotFoundException() // cache APK and get icon as well as app name @@ -680,7 +687,7 @@ internal class ApkRestoreTest : TransportTest() { @Test fun `no apks get installed when blocked by policy`() = runBlocking { every { installRestriction.isAllowedToInstallApks() } returns false - every { storagePlugin.providerPackageName } returns storageProviderPackageName + every { backend.providerPackageName } returns storageProviderPackageName apkRestore.installResult.test { awaitItem() // initial empty state @@ -703,7 +710,7 @@ internal class ApkRestoreTest : TransportTest() { private fun cacheBaseApkAndGetInfo(tmpDir: Path) { every { strictContext.cacheDir } returns File(tmpDir.toString()) every { crypto.getNameForApk(salt, packageName, "") } returns name - coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream + coEvery { backend.load(handle) } returns apkInputStream every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo every { applicationInfo.loadIcon(pm) } returns icon every { pm.getApplicationLabel(packageInfo.applicationInfo!!) } returns appName 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 bf03c370..4948206a 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt @@ -21,9 +21,7 @@ import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.MetadataReaderImpl import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.transport.backup.BackupCoordinator import com.stevesoltys.seedvault.transport.backup.FullBackup import com.stevesoltys.seedvault.transport.backup.InputFactory @@ -44,6 +42,8 @@ import io.mockk.just import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.fail @@ -67,7 +67,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { @Suppress("Deprecation") private val legacyPlugin = mockk() - private val backupPlugin = mockk>() + private val backend = mockk() private val kvBackup = KVBackup( pluginManager = storagePluginManager, settingsManager = settingsManager, @@ -132,7 +132,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { private val realName = cryptoImpl.getNameForPackage(salt, packageInfo.packageName) init { - every { storagePluginManager.appPlugin } returns backupPlugin + every { storagePluginManager.backend } returns backend } @Test @@ -161,7 +161,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns packageMetadata coEvery { - backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) + backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { metadataManager.onApkBackedUp(packageInfo, packageMetadata) @@ -179,7 +179,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0)) // upload DB - coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream + coEvery { + backend.save(LegacyAppBackupFile.Blob(token, realName)) + } returns bOutputStream // finish K/V backup assertEquals(TRANSPORT_OK, backup.finishBackup()) @@ -198,7 +200,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { // restore finds the backed up key and writes the decrypted value val backupDataOutput = mockk() val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray()) - coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream + coEvery { + backend.load(LegacyAppBackupFile.Blob(token, name)) + } returns rInputStream every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137 every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size @@ -237,7 +241,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns null every { settingsManager.getToken() } returns token coEvery { - backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) + backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { metadataManager.onPackageBackedUp( @@ -252,7 +256,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0)) // upload DB - coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream + coEvery { + backend.save(LegacyAppBackupFile.Blob(token, realName)) + } returns bOutputStream // finish K/V backup assertEquals(TRANSPORT_OK, backup.finishBackup()) @@ -271,7 +277,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { // restore finds the backed up key and writes the decrypted value val backupDataOutput = mockk() val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray()) - coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream + coEvery { + backend.load(LegacyAppBackupFile.Blob(token, name)) + } returns rInputStream every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137 every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size @@ -294,14 +302,16 @@ internal class CoordinatorIntegrationTest : TransportTest() { // return streams from plugin and app data val bOutputStream = ByteArrayOutputStream() val bInputStream = ByteArrayInputStream(appData) - coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream + coEvery { + backend.save(LegacyAppBackupFile.Blob(token, realName)) + } returns bOutputStream every { inputFactory.getInputStream(fileDescriptor) } returns bInputStream every { settingsManager.isQuotaUnlimited() } returns false coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns packageMetadata every { settingsManager.getToken() } returns token every { metadataManager.salt } returns salt coEvery { - backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) + backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { metadataManager.onApkBackedUp(packageInfo, packageMetadata) } just Runs every { @@ -333,7 +343,9 @@ internal class CoordinatorIntegrationTest : TransportTest() { // reverse the backup streams into restore input val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray()) val rOutputStream = ByteArrayOutputStream() - coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream + coEvery { + backend.load(LegacyAppBackupFile.Blob(token, name)) + } returns rInputStream every { outputFactory.getOutputStream(fileDescriptor) } returns rOutputStream // restore data diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt index 56406985..ee0e99d7 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/TransportTest.kt @@ -28,6 +28,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.slot +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD import kotlin.random.Random @@ -73,6 +74,7 @@ internal abstract class TransportTest { protected val name = getRandomString(12) protected val name2 = getRandomString(23) protected val storageProviderPackageName = getRandomString(23) + protected val handle = LegacyAppBackupFile.Blob(token, name) init { mockkStatic(Log::class) diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt index f07bd307..4382aae3 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt @@ -20,9 +20,7 @@ import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.worker.ApkBackup @@ -33,6 +31,8 @@ import io.mockk.just import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.io.IOException @@ -60,7 +60,7 @@ internal class BackupCoordinatorTest : BackupTest() { nm = notificationManager, ) - private val plugin = mockk>() + private val backend = mockk() private val metadataOutputStream = mockk() private val fileDescriptor: ParcelFileDescriptor = mockk() private val packageMetadata: PackageMetadata = mockk() @@ -73,13 +73,12 @@ internal class BackupCoordinatorTest : BackupTest() { ) init { - every { pluginManager.appPlugin } returns plugin + every { pluginManager.backend } returns backend } @Test fun `device initialization succeeds and delegates to plugin`() = runBlocking { expectStartNewRestoreSet() - coEvery { plugin.initializeDevice() } just Runs every { kv.hasState() } returns false every { full.hasState() } returns false @@ -87,10 +86,9 @@ internal class BackupCoordinatorTest : BackupTest() { assertEquals(TRANSPORT_OK, backup.finishBackup()) } - private suspend fun expectStartNewRestoreSet() { + private fun expectStartNewRestoreSet() { every { clock.time() } returns token every { settingsManager.setNewToken(token) } just Runs - coEvery { plugin.startNewRestoreSet(token) } just Runs every { metadataManager.onDeviceInitialization(token) } just Runs } @@ -98,8 +96,9 @@ internal class BackupCoordinatorTest : BackupTest() { fun `error notification when device initialization fails`() = runBlocking { val maybeTrue = Random.nextBoolean() - expectStartNewRestoreSet() - coEvery { plugin.initializeDevice() } throws IOException() + every { clock.time() } returns token + every { settingsManager.setNewToken(token) } just Runs + every { metadataManager.onDeviceInitialization(token) } throws IOException() every { metadataManager.requiresInit } returns maybeTrue every { pluginManager.canDoBackupNow() } returns !maybeTrue every { notificationManager.onBackupError() } just Runs @@ -117,8 +116,9 @@ internal class BackupCoordinatorTest : BackupTest() { @Test fun `no error notification when device initialization fails when no backup possible`() = runBlocking { - expectStartNewRestoreSet() - coEvery { plugin.initializeDevice() } throws IOException() + every { clock.time() } returns token + every { settingsManager.setNewToken(token) } just Runs + every { metadataManager.onDeviceInitialization(token) } throws IOException() every { metadataManager.requiresInit } returns false every { pluginManager.canDoBackupNow() } returns false @@ -142,7 +142,6 @@ internal class BackupCoordinatorTest : BackupTest() { // start new restore set every { clock.time() } returns token + 1 every { settingsManager.setNewToken(token + 1) } just Runs - coEvery { plugin.startNewRestoreSet(token + 1) } just Runs every { metadataManager.onDeviceInitialization(token + 1) } just Runs every { data.close() } just Runs @@ -210,7 +209,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { kv.getCurrentPackage() } returns packageInfo coEvery { kv.finishBackup() } returns TRANSPORT_OK every { settingsManager.getToken() } returns token - coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream + coEvery { backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { kv.getCurrentSize() } returns size every { metadataManager.onPackageBackedUp( @@ -250,7 +249,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { full.getCurrentPackage() } returns packageInfo every { full.finishBackup() } returns result every { settingsManager.getToken() } returns token - coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream + coEvery { backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { full.getCurrentSize() } returns size every { metadataManager.onPackageBackedUp( @@ -385,7 +384,7 @@ internal class BackupCoordinatorTest : BackupTest() { private fun expectApkBackupAndMetadataWrite() { coEvery { apkBackup.backupApkIfNecessary(any(), any()) } returns packageMetadata every { settingsManager.getToken() } returns token - coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream + coEvery { backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { metadataManager.onApkBackedUp(any(), packageMetadata) } just Runs } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt index b509d18e..d922d10c 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt @@ -11,7 +11,6 @@ import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.Runs @@ -20,6 +19,8 @@ import io.mockk.every import io.mockk.just import io.mockk.mockk import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue @@ -31,7 +32,7 @@ import kotlin.random.Random internal class FullBackupTest : BackupTest() { private val storagePluginManager: StoragePluginManager = mockk() - private val plugin = mockk>() + private val backend = mockk() private val notificationManager = mockk() private val backup = FullBackup( pluginManager = storagePluginManager, @@ -46,7 +47,7 @@ internal class FullBackupTest : BackupTest() { private val ad = getADForFull(VERSION, packageInfo.packageName) init { - every { storagePluginManager.appPlugin } returns plugin + every { storagePluginManager.backend } returns backend } @Test @@ -167,7 +168,7 @@ internal class FullBackupTest : BackupTest() { every { settingsManager.isQuotaUnlimited() } returns false every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name - coEvery { plugin.getOutputStream(token, name) } throws IOException() + coEvery { backend.save(handle) } throws IOException() expectClearState() assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt)) @@ -184,7 +185,7 @@ internal class FullBackupTest : BackupTest() { every { settingsManager.isQuotaUnlimited() } returns false every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name - coEvery { plugin.getOutputStream(token, name) } returns outputStream + coEvery { backend.save(handle) } returns outputStream every { inputFactory.getInputStream(data) } returns inputStream every { outputStream.write(ByteArray(1) { VERSION }) } throws IOException() expectClearState() @@ -240,7 +241,7 @@ internal class FullBackupTest : BackupTest() { @Test fun `clearBackupData delegates to plugin`() = runBlocking { every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name - coEvery { plugin.removeData(token, name) } just Runs + coEvery { backend.remove(handle) } just Runs backup.clearBackupData(packageInfo, token, salt) } @@ -251,7 +252,7 @@ internal class FullBackupTest : BackupTest() { expectInitializeOutputStream() expectClearState() every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name - coEvery { plugin.removeData(token, name) } just Runs + coEvery { backend.remove(handle) } just Runs assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt)) assertTrue(backup.hasState()) @@ -265,7 +266,7 @@ internal class FullBackupTest : BackupTest() { expectInitializeOutputStream() expectClearState() every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name - coEvery { plugin.removeData(token, name) } throws IOException() + coEvery { backend.remove(handle) } throws IOException() assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt)) assertTrue(backup.hasState()) @@ -336,7 +337,9 @@ internal class FullBackupTest : BackupTest() { private fun expectInitializeOutputStream() { every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name - coEvery { plugin.getOutputStream(token, name) } returns outputStream + coEvery { + backend.save(LegacyAppBackupFile.Blob(token, name)) + } returns outputStream every { outputStream.write(ByteArray(1) { VERSION }) } just Runs } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt index 9468bb9e..85854b6c 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt @@ -17,7 +17,6 @@ import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.CapturingSlot @@ -29,6 +28,7 @@ import io.mockk.just import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue @@ -54,7 +54,7 @@ internal class KVBackupTest : BackupTest() { ) private val db = mockk() - private val plugin = mockk>() + private val backend = mockk() private val packageName = packageInfo.packageName private val key = getRandomString(MAX_KEY_LENGTH_SIZE) private val dataValue = Random.nextBytes(23) @@ -62,7 +62,7 @@ internal class KVBackupTest : BackupTest() { private val inputStream = ByteArrayInputStream(dbBytes) init { - every { pluginManager.appPlugin } returns plugin + every { pluginManager.backend } returns backend } @Test @@ -96,7 +96,7 @@ internal class KVBackupTest : BackupTest() { @Test fun `non-incremental backup with data clears old data first`() = runBlocking { singleRecordBackup(true) - coEvery { plugin.removeData(token, name) } just Runs + coEvery { backend.remove(handle) } just Runs every { dbManager.deleteDb(packageName) } returns true assertEquals( @@ -112,7 +112,7 @@ internal class KVBackupTest : BackupTest() { fun `ignoring exception when clearing data when non-incremental backup has data`() = runBlocking { singleRecordBackup(true) - coEvery { plugin.removeData(token, name) } throws IOException() + coEvery { backend.remove(handle) } throws IOException() assertEquals( TRANSPORT_OK, @@ -210,7 +210,7 @@ internal class KVBackupTest : BackupTest() { every { db.vacuum() } just Runs every { db.close() } just Runs - coEvery { plugin.getOutputStream(token, name) } returns outputStream + coEvery { backend.save(handle) } returns outputStream every { outputStream.write(ByteArray(1) { VERSION }) } throws IOException() every { outputStream.close() } just Runs assertEquals(TRANSPORT_ERROR, backup.finishBackup()) @@ -230,7 +230,7 @@ internal class KVBackupTest : BackupTest() { every { db.vacuum() } just Runs every { db.close() } just Runs - coEvery { plugin.getOutputStream(token, name) } returns outputStream + coEvery { backend.save(handle) } returns outputStream every { outputStream.write(ByteArray(1) { VERSION }) } just Runs val ad = getADForKV(VERSION, packageInfo.packageName) every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream @@ -264,7 +264,7 @@ internal class KVBackupTest : BackupTest() { assertFalse(backup.hasState()) coVerify(exactly = 0) { - plugin.getOutputStream(token, name) + backend.save(handle) } } @@ -301,7 +301,7 @@ internal class KVBackupTest : BackupTest() { every { db.vacuum() } just Runs every { db.close() } just Runs - coEvery { plugin.getOutputStream(token, name) } returns outputStream + coEvery { backend.save(handle) } returns outputStream every { outputStream.write(ByteArray(1) { VERSION }) } just Runs val ad = getADForKV(VERSION, packageInfo.packageName) every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt index bc3e3266..c7169610 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt @@ -17,7 +17,6 @@ import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VersionHeader import com.stevesoltys.seedvault.header.getADForFull import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import io.mockk.CapturingSlot import io.mockk.Runs @@ -26,6 +25,7 @@ import io.mockk.every import io.mockk.just import io.mockk.mockk import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertFalse @@ -40,7 +40,7 @@ import kotlin.random.Random internal class FullRestoreTest : RestoreTest() { private val storagePluginManager: StoragePluginManager = mockk() - private val plugin = mockk>() + private val backend = mockk() private val legacyPlugin = mockk() private val restore = FullRestore( pluginManager = storagePluginManager, @@ -55,7 +55,7 @@ internal class FullRestoreTest : RestoreTest() { private val ad = getADForFull(VERSION, packageInfo.packageName) init { - every { storagePluginManager.appPlugin } returns plugin + every { storagePluginManager.backend } returns backend } @Test @@ -90,7 +90,7 @@ internal class FullRestoreTest : RestoreTest() { fun `getting InputStream for package when getting first chunk throws`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } throws IOException() + coEvery { backend.load(handle) } throws IOException() every { fileDescriptor.close() } just Runs assertEquals( @@ -103,7 +103,7 @@ internal class FullRestoreTest : RestoreTest() { fun `reading version header when getting first chunk throws`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } throws IOException() every { fileDescriptor.close() } just Runs @@ -117,7 +117,7 @@ internal class FullRestoreTest : RestoreTest() { fun `reading unsupported version when getting first chunk`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } throws UnsupportedVersionException(unsupportedVersion) @@ -133,7 +133,7 @@ internal class FullRestoreTest : RestoreTest() { fun `getting decrypted stream when getting first chunk throws`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } throws IOException() every { fileDescriptor.close() } just Runs @@ -149,7 +149,7 @@ internal class FullRestoreTest : RestoreTest() { runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException() every { fileDescriptor.close() } just Runs @@ -197,7 +197,7 @@ internal class FullRestoreTest : RestoreTest() { fun `unexpected version aborts with error`() = runBlocking { restore.initializeState(Byte.MAX_VALUE, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, Byte.MAX_VALUE) } throws GeneralSecurityException() @@ -215,7 +215,7 @@ internal class FullRestoreTest : RestoreTest() { val decryptedInputStream = ByteArrayInputStream(encryptedBytes) restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream @@ -248,7 +248,7 @@ internal class FullRestoreTest : RestoreTest() { } private fun initInputStream() { - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt index 018d2b2d..aadd1203 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt @@ -16,7 +16,6 @@ import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VersionHeader import com.stevesoltys.seedvault.header.getADForKV import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.transport.backup.KVDb import com.stevesoltys.seedvault.transport.backup.KvDbManager @@ -29,6 +28,7 @@ import io.mockk.mockkStatic import io.mockk.verify import io.mockk.verifyAll import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.io.ByteArrayInputStream @@ -42,7 +42,7 @@ import kotlin.random.Random internal class KVRestoreTest : RestoreTest() { private val storagePluginManager: StoragePluginManager = mockk() - private val plugin = mockk>() + private val backend = mockk() @Suppress("DEPRECATION") private val legacyPlugin = mockk() private val dbManager = mockk() @@ -74,7 +74,7 @@ internal class KVRestoreTest : RestoreTest() { // for InputStream#readBytes() mockkStatic("kotlin.io.ByteStreamsKt") - every { storagePluginManager.appPlugin } returns plugin + every { storagePluginManager.backend } returns backend } @Test @@ -88,7 +88,7 @@ internal class KVRestoreTest : RestoreTest() { fun `unexpected version aborts with error`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } throws UnsupportedVersionException(Byte.MAX_VALUE) @@ -103,7 +103,7 @@ internal class KVRestoreTest : RestoreTest() { fun `newDecryptingStream throws`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException() every { dbManager.deleteDb(packageInfo.packageName, true) } returns true @@ -121,7 +121,7 @@ internal class KVRestoreTest : RestoreTest() { fun `writeEntityHeader throws`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream every { @@ -146,7 +146,7 @@ internal class KVRestoreTest : RestoreTest() { fun `two records get restored`() = runBlocking { restore.initializeState(VERSION, token, name, packageInfo) - coEvery { plugin.getInputStream(token, name) } returns inputStream + coEvery { backend.load(handle) } returns inputStream every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream every { 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 088c35b0..4a4605b0 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 @@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.MetadataReader import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.plugins.EncryptedMetadata -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.plugins.getAvailableBackups import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager @@ -30,8 +30,10 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.just import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.verify import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertThrows @@ -45,7 +47,7 @@ internal class RestoreCoordinatorTest : TransportTest() { private val notificationManager: BackupNotificationManager = mockk() private val storagePluginManager: StoragePluginManager = mockk() - private val plugin = mockk>() + private val backend = mockk() private val kv = mockk() private val full = mockk() private val metadataReader = mockk() @@ -78,14 +80,15 @@ internal class RestoreCoordinatorTest : TransportTest() { metadata.packageMetadataMap[packageInfo2.packageName] = PackageMetadata(backupType = BackupType.FULL) - every { storagePluginManager.appPlugin } returns plugin + mockkStatic("com.stevesoltys.seedvault.plugins.BackendExtKt") + every { storagePluginManager.backend } returns backend } @Test fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking { val encryptedMetadata = EncryptedMetadata(token) { inputStream } - coEvery { plugin.getAvailableBackups() } returns sequenceOf( + coEvery { backend.getAvailableBackups() } returns sequenceOf( encryptedMetadata, EncryptedMetadata(token + 1) { inputStream } ) @@ -123,7 +126,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() fetches metadata if missing`() = runBlocking { - coEvery { plugin.getAvailableBackups() } returns sequenceOf( + coEvery { backend.getAvailableBackups() } returns sequenceOf( EncryptedMetadata(token) { inputStream }, EncryptedMetadata(token + 1) { inputStream } ) @@ -136,7 +139,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() errors if metadata is not matching token`() = runBlocking { - coEvery { plugin.getAvailableBackups() } returns sequenceOf( + coEvery { backend.getAvailableBackups() } returns sequenceOf( EncryptedMetadata(token + 42) { inputStream } ) every { metadataReader.readMetadata(inputStream, token + 42) } returns metadata 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 9e8baecf..ab3afad9 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 @@ -30,6 +30,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verifyOrder import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.fail @@ -58,7 +59,7 @@ internal class RestoreV0IntegrationTest : TransportTest() { @Suppress("Deprecation") private val legacyPlugin = mockk() - private val backupPlugin = mockk>() + private val backend = mockk() private val kvRestore = KVRestore( pluginManager = storagePluginManager, legacyPlugin = legacyPlugin, @@ -123,7 +124,7 @@ internal class RestoreV0IntegrationTest : TransportTest() { private val key264 = key2.encodeBase64() init { - every { storagePluginManager.appPlugin } returns backupPlugin + every { storagePluginManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt index 5dafa04b..a0b755c5 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt @@ -14,9 +14,7 @@ import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager @@ -30,6 +28,8 @@ import io.mockk.mockk import io.mockk.verify import io.mockk.verifyAll import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.junit.jupiter.api.Test import java.io.ByteArrayOutputStream import java.io.IOException @@ -41,7 +41,7 @@ internal class ApkBackupManagerTest : TransportTest() { private val apkBackup: ApkBackup = mockk() private val iconManager: IconManager = mockk() private val storagePluginManager: StoragePluginManager = mockk() - private val plugin: StoragePlugin<*> = mockk() + private val backend: Backend = mockk() private val nm: BackupNotificationManager = mockk() private val apkBackupManager = ApkBackupManager( @@ -59,7 +59,7 @@ internal class ApkBackupManagerTest : TransportTest() { private val packageMetadata: PackageMetadata = mockk() init { - every { storagePluginManager.appPlugin } returns plugin + every { storagePluginManager.backend } returns backend } @Test @@ -258,7 +258,7 @@ internal class ApkBackupManagerTest : TransportTest() { // final upload every { settingsManager.getToken() } returns token - coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream + coEvery { backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { metadataManager.uploadMetadata(metadataOutputStream) } throws IOException() andThenThrows SecurityException() andThenJust Runs @@ -277,7 +277,7 @@ internal class ApkBackupManagerTest : TransportTest() { private suspend fun expectUploadIcons() { every { settingsManager.getToken() } returns token val stream = ByteArrayOutputStream() - coEvery { plugin.getOutputStream(token, FILE_BACKUP_ICONS) } returns stream + coEvery { backend.save(LegacyAppBackupFile.IconsFile(token)) } returns stream every { iconManager.uploadIcons(token, stream) } just Runs } @@ -288,7 +288,7 @@ internal class ApkBackupManagerTest : TransportTest() { private fun expectFinalUpload() { every { settingsManager.getToken() } returns token - coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream + coEvery { backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream every { metadataManager.uploadMetadata(metadataOutputStream) } just Runs every { metadataOutputStream.close() } just Runs } diff --git a/app/src/test/resources/simplelogger.properties b/app/src/test/resources/simplelogger.properties index e0f0d79d..df3774e5 100644 --- a/app/src/test/resources/simplelogger.properties +++ b/app/src/test/resources/simplelogger.properties @@ -1 +1 @@ -org.slf4j.simpleLogger.defaultLogLevel=trace +#org.slf4j.simpleLogger.defaultLogLevel=debug diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/Backend.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/Backend.kt index 4ae19495..03c711ca 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/backends/Backend.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/Backend.kt @@ -6,8 +6,8 @@ package org.calyxos.seedvault.core.backends import androidx.annotation.VisibleForTesting -import okio.BufferedSink -import okio.BufferedSource +import java.io.InputStream +import java.io.OutputStream import kotlin.reflect.KClass public interface Backend { @@ -25,9 +25,9 @@ public interface Backend { */ public suspend fun getFreeSpace(): Long? - public suspend fun save(handle: FileHandle): BufferedSink + public suspend fun save(handle: FileHandle): OutputStream - public suspend fun load(handle: FileHandle): BufferedSource + public suspend fun load(handle: FileHandle): InputStream public suspend fun list( topLevelFolder: TopLevelFolder?, diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/BackendTest.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/BackendTest.kt index f925b3d8..39a507f1 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/backends/BackendTest.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/BackendTest.kt @@ -56,8 +56,8 @@ public abstract class BackendTest { assertNotNull(metadata) assertNotNull(snapshot) - assertArrayEquals(bytes1, plugin.load(metadata as FileHandle).readByteArray()) - assertArrayEquals(bytes2, plugin.load(snapshot as FileHandle).readByteArray()) + assertArrayEquals(bytes1, plugin.load(metadata as FileHandle).readAllBytes()) + assertArrayEquals(bytes2, plugin.load(snapshot as FileHandle).readAllBytes()) val blobName = Random.nextBytes(32).toHexString() var blob: FileBackupFileType.Blob? = null @@ -77,7 +77,7 @@ public abstract class BackendTest { } } assertNotNull(blob) - assertArrayEquals(bytes3, plugin.load(blob as FileHandle).readByteArray()) + assertArrayEquals(bytes3, plugin.load(blob as FileHandle).readAllBytes()) // try listing with top-level folder, should find two files of FileBackupFileType in there var numFiles = 0 diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt index 6d731732..774f304f 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt @@ -15,11 +15,6 @@ import android.provider.DocumentsContract.renameDocument import androidx.core.database.getIntOrNull import androidx.documentfile.provider.DocumentFile import io.github.oshai.kotlinlogging.KotlinLogging -import okio.BufferedSink -import okio.BufferedSource -import okio.buffer -import okio.sink -import okio.source import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT import org.calyxos.seedvault.core.backends.Constants.FILE_BACKUP_METADATA @@ -35,6 +30,8 @@ import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.calyxos.seedvault.core.backends.TopLevelFolder import org.calyxos.seedvault.core.getBackendContext import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import kotlin.reflect.KClass internal const val AUTHORITY_STORAGE = "com.android.externalstorage.documents" @@ -84,14 +81,14 @@ public class SafBackend( } else bytesAvailable } - override suspend fun save(handle: FileHandle): BufferedSink { + override suspend fun save(handle: FileHandle): OutputStream { val file = cache.getFile(handle) - return file.getOutputStream(context.contentResolver).sink().buffer() + return file.getOutputStream(context.contentResolver) } - override suspend fun load(handle: FileHandle): BufferedSource { + override suspend fun load(handle: FileHandle): InputStream { val file = cache.getFile(handle) - return file.getInputStream(context.contentResolver).source().buffer() + return file.getInputStream(context.contentResolver) } override suspend fun list( diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/webdav/WebDavBackend.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/webdav/WebDavBackend.kt index d611f581..ee24b895 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/backends/webdav/WebDavBackend.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/webdav/WebDavBackend.kt @@ -26,9 +26,6 @@ import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.RequestBody import okio.BufferedSink -import okio.BufferedSource -import okio.buffer -import okio.sink import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT import org.calyxos.seedvault.core.backends.Constants.FILE_BACKUP_METADATA @@ -43,6 +40,8 @@ import org.calyxos.seedvault.core.backends.FileInfo import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import org.calyxos.seedvault.core.backends.TopLevelFolder import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import java.io.PipedInputStream import java.util.concurrent.TimeUnit import kotlin.coroutines.resume @@ -123,7 +122,7 @@ public class WebDavBackend( return availableBytes } - override suspend fun save(handle: FileHandle): BufferedSink { + override suspend fun save(handle: FileHandle): OutputStream { val location = handle.toHttpUrl() val davCollection = DavCollection(okHttpClient, location) davCollection.ensureFoldersExist(log, folders) @@ -152,10 +151,10 @@ public class WebDavBackend( deferred.await() } } - return pipedOutputStream.sink().buffer() + return pipedOutputStream } - override suspend fun load(handle: FileHandle): BufferedSource { + override suspend fun load(handle: FileHandle): InputStream { val location = handle.toHttpUrl() val davCollection = DavCollection(okHttpClient, location) @@ -167,7 +166,7 @@ public class WebDavBackend( } log.debugLog { "load($location) = $response" } if (response.code / 100 != 2) throw IOException("HTTP error ${response.code}") - return response.body?.source() ?: throw IOException("Body was null for $location") + return response.body?.byteStream() ?: throw IOException("Body was null for $location") } override suspend fun list( diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt index 1840938f..fe23e6f8 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/saf/SafStoragePlugin.kt @@ -56,13 +56,13 @@ public abstract class SafStoragePlugin( @Throws(IOException::class) override suspend fun getChunkOutputStream(chunkId: String): OutputStream { val fileHandle = FileBackupFileType.Blob(androidId, chunkId) - return delegate.save(fileHandle).outputStream() + return delegate.save(fileHandle) } @Throws(IOException::class) override suspend fun getBackupSnapshotOutputStream(timestamp: Long): OutputStream { val fileHandle = FileBackupFileType.Snapshot(androidId, timestamp) - return delegate.save(fileHandle).outputStream() + return delegate.save(fileHandle) } /************************* Restore *******************************/ @@ -84,7 +84,7 @@ public abstract class SafStoragePlugin( override suspend fun getBackupSnapshotInputStream(storedSnapshot: StoredSnapshot): InputStream { val androidId = storedSnapshot.androidId val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp) - return delegate.load(handle).inputStream() + return delegate.load(handle) } @Throws(IOException::class) @@ -93,7 +93,7 @@ public abstract class SafStoragePlugin( chunkId: String, ): InputStream { val handle = FileBackupFileType.Blob(snapshot.androidId, chunkId) - return delegate.load(handle).inputStream() + return delegate.load(handle) } /************************* Pruning *******************************/