Use new Backend directly in the app

This commit is contained in:
Torsten Grote 2024-08-27 14:57:07 -03:00
parent 5bb599e528
commit 0c1dfb316d
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
46 changed files with 331 additions and 244 deletions

View file

@ -9,12 +9,12 @@ import android.content.pm.PackageInfo
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.settings.AppStatus import com.stevesoltys.seedvault.settings.AppStatus
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import org.calyxos.seedvault.core.backends.Backend
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
@ -32,7 +32,7 @@ class PackageServiceTest : KoinComponent {
private val storagePluginManager: StoragePluginManager by inject() private val storagePluginManager: StoragePluginManager by inject()
private val storagePlugin: StoragePlugin<*> get() = storagePluginManager.appPlugin private val backend: Backend get() = storagePluginManager.backend
@Test @Test
fun testNotAllowedPackages() { fun testNotAllowedPackages() {
@ -65,6 +65,6 @@ class PackageServiceTest : KoinComponent {
assertTrue(packageService.shouldIncludeAppInBackup(packageInfo.packageName)) assertTrue(packageService.shouldIncludeAppInBackup(packageInfo.packageName))
// Should not backup storage provider // Should not backup storage provider
assertFalse(packageService.shouldIncludeAppInBackup(storagePlugin.providerPackageName!!)) assertFalse(packageService.shouldIncludeAppInBackup(backend.providerPackageName!!))
} }
} }

View file

@ -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<EncryptedMetadata>? {
return try {
// get all restore set tokens in root folder that have a metadata file
val handles = ArrayList<LegacyAppBackupFile.Metadata>()
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
}
}

View file

@ -10,11 +10,12 @@ import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.getStorageContext import com.stevesoltys.seedvault.getStorageContext
import com.stevesoltys.seedvault.permitDiskReads import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderStoragePlugin
import com.stevesoltys.seedvault.plugins.saf.SafFactory import com.stevesoltys.seedvault.plugins.saf.SafFactory
import com.stevesoltys.seedvault.plugins.webdav.WebDavFactory import com.stevesoltys.seedvault.plugins.webdav.WebDavFactory
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.StoragePluginType import com.stevesoltys.seedvault.settings.StoragePluginType
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.saf.SafBackend
class StoragePluginManager( class StoragePluginManager(
private val context: Context, private val context: Context,
@ -23,14 +24,14 @@ class StoragePluginManager(
webDavFactory: WebDavFactory, webDavFactory: WebDavFactory,
) { ) {
private var mAppPlugin: StoragePlugin<*>? private var mBackend: Backend?
private var mFilesPlugin: org.calyxos.backup.storage.api.StoragePlugin? private var mFilesPlugin: org.calyxos.backup.storage.api.StoragePlugin?
private var mStorageProperties: StorageProperties<*>? private var mStorageProperties: StorageProperties<*>?
val appPlugin: StoragePlugin<*> val backend: Backend
@Synchronized @Synchronized
get() { 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 val filesPlugin: org.calyxos.backup.storage.api.StoragePlugin
@ -50,7 +51,7 @@ class StoragePluginManager(
when (settingsManager.storagePluginType) { when (settingsManager.storagePluginType) {
StoragePluginType.SAF -> { StoragePluginType.SAF -> {
val safStorage = settingsManager.getSafStorage() ?: error("No SAF storage saved") val safStorage = settingsManager.getSafStorage() ?: error("No SAF storage saved")
mAppPlugin = safFactory.createAppStoragePlugin(safStorage) mBackend = safFactory.createBackend(safStorage)
mFilesPlugin = safFactory.createFilesStoragePlugin(safStorage) mFilesPlugin = safFactory.createFilesStoragePlugin(safStorage)
mStorageProperties = safStorage mStorageProperties = safStorage
} }
@ -58,13 +59,13 @@ class StoragePluginManager(
StoragePluginType.WEB_DAV -> { StoragePluginType.WEB_DAV -> {
val webDavProperties = val webDavProperties =
settingsManager.webDavProperties ?: error("No WebDAV config saved") settingsManager.webDavProperties ?: error("No WebDAV config saved")
mAppPlugin = webDavFactory.createAppStoragePlugin(webDavProperties.config) mBackend = webDavFactory.createBackend(webDavProperties.config)
mFilesPlugin = webDavFactory.createFilesStoragePlugin(webDavProperties.config) mFilesPlugin = webDavFactory.createFilesStoragePlugin(webDavProperties.config)
mStorageProperties = webDavProperties mStorageProperties = webDavProperties
} }
null -> { null -> {
mAppPlugin = null mBackend = null
mFilesPlugin = null mFilesPlugin = null
mStorageProperties = null mStorageProperties = null
} }
@ -72,8 +73,8 @@ class StoragePluginManager(
} }
fun isValidAppPluginSet(): Boolean { fun isValidAppPluginSet(): Boolean {
if (mAppPlugin == null || mFilesPlugin == null) return false if (mBackend == null || mFilesPlugin == null) return false
if (mAppPlugin is DocumentsProviderStoragePlugin) { if (mBackend is SafBackend) {
val storage = settingsManager.getSafStorage() ?: return false val storage = settingsManager.getSafStorage() ?: return false
if (storage.isUsb) return true if (storage.isUsb) return true
return permitDiskReads { return permitDiskReads {
@ -91,12 +92,12 @@ class StoragePluginManager(
*/ */
fun <T> changePlugins( fun <T> changePlugins(
storageProperties: StorageProperties<T>, storageProperties: StorageProperties<T>,
appPlugin: StoragePlugin<T>, backend: Backend,
filesPlugin: org.calyxos.backup.storage.api.StoragePlugin, filesPlugin: org.calyxos.backup.storage.api.StoragePlugin,
) { ) {
settingsManager.setStoragePlugin(appPlugin) settingsManager.setStorageBackend(backend)
mStorageProperties = storageProperties mStorageProperties = storageProperties
mAppPlugin = appPlugin mBackend = backend
mFilesPlugin = filesPlugin mFilesPlugin = filesPlugin
} }
@ -136,7 +137,7 @@ class StoragePluginManager(
@WorkerThread @WorkerThread
suspend fun getFreeSpace(): Long? { suspend fun getFreeSpace(): Long? {
return try { return try {
appPlugin.getFreeSpace() backend.getFreeSpace()
} catch (e: Throwable) { // NoClassDefFound isn't an [Exception], can get thrown by dav4jvm } catch (e: Throwable) { // NoClassDefFound isn't an [Exception], can get thrown by dav4jvm
Log.e("StoragePluginManager", "Error getting free space: ", e) Log.e("StoragePluginManager", "Error getting free space: ", e)
null null

View file

@ -61,7 +61,7 @@ internal class DocumentsProviderStoragePlugin(
FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token)
else -> LegacyAppBackupFile.Blob(token, name) else -> LegacyAppBackupFile.Blob(token, name)
} }
return delegate.save(handle).outputStream() return delegate.save(handle)
} }
@Throws(IOException::class) @Throws(IOException::class)
@ -71,7 +71,7 @@ internal class DocumentsProviderStoragePlugin(
FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token)
else -> LegacyAppBackupFile.Blob(token, name) else -> LegacyAppBackupFile.Blob(token, name)
} }
return delegate.load(handle).inputStream() return delegate.load(handle)
} }
@Throws(IOException::class) @Throws(IOException::class)

View file

@ -6,18 +6,16 @@
package com.stevesoltys.seedvault.plugins.saf package com.stevesoltys.seedvault.plugins.saf
import android.content.Context import android.content.Context
import android.net.Uri
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.storage.SeedvaultSafStoragePlugin import com.stevesoltys.seedvault.storage.SeedvaultSafStoragePlugin
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.saf.SafBackend
class SafFactory( class SafFactory(
private val context: Context, private val context: Context,
) { ) {
internal fun createAppStoragePlugin( internal fun createBackend(safStorage: SafStorage): Backend {
safStorage: SafStorage, return SafBackend(context, safStorage.toSafConfig())
): StoragePlugin<Uri> {
return DocumentsProviderStoragePlugin(context, safStorage)
} }
internal fun createFilesStoragePlugin( internal fun createFilesStoragePlugin(

View file

@ -16,6 +16,7 @@ import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.isMassStorage import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.getAvailableBackups
import com.stevesoltys.seedvault.settings.FlashDrive import com.stevesoltys.seedvault.settings.FlashDrive
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.storage.StorageOption import com.stevesoltys.seedvault.ui.storage.StorageOption
@ -50,7 +51,7 @@ internal class SafHandler(
@WorkerThread @WorkerThread
@Throws(IOException::class) @Throws(IOException::class)
suspend fun hasAppBackup(safStorage: SafStorage): Boolean { suspend fun hasAppBackup(safStorage: SafStorage): Boolean {
val appPlugin = safFactory.createAppStoragePlugin(safStorage) val appPlugin = safFactory.createBackend(safStorage)
val backups = appPlugin.getAvailableBackups() val backups = appPlugin.getAvailableBackups()
return backups != null && backups.iterator().hasNext() return backups != null && backups.iterator().hasNext()
} }
@ -86,7 +87,7 @@ internal class SafHandler(
fun setPlugin(safStorage: SafStorage) { fun setPlugin(safStorage: SafStorage) {
storagePluginManager.changePlugins( storagePluginManager.changePlugins(
storageProperties = safStorage, storageProperties = safStorage,
appPlugin = safFactory.createAppStoragePlugin(safStorage), backend = safFactory.createBackend(safStorage),
filesPlugin = safFactory.createFilesStoragePlugin(safStorage), filesPlugin = safFactory.createFilesStoragePlugin(safStorage),
) )
} }

View file

@ -11,6 +11,7 @@ import android.provider.DocumentsContract.Root.COLUMN_ROOT_ID
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.plugins.StorageProperties import com.stevesoltys.seedvault.plugins.StorageProperties
import org.calyxos.seedvault.core.backends.saf.SafConfig
data class SafStorage( data class SafStorage(
override val config: Uri, override val config: Uri,
@ -38,4 +39,12 @@ data class SafStorage(
override fun isUnavailableUsb(context: Context): Boolean { override fun isUnavailableUsb(context: Context): Boolean {
return isUsb && !getDocumentFile(context).isDirectory return isUsb && !getDocumentFile(context).isDirectory
} }
fun toSafConfig() = SafConfig(
config = config,
name = name,
isUsb = isUsb,
requiresNetwork = requiresNetwork,
rootId = rootId,
)
} }

View file

@ -8,16 +8,15 @@ package com.stevesoltys.seedvault.plugins.webdav
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.provider.Settings 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 import org.calyxos.seedvault.core.backends.webdav.WebDavConfig
class WebDavFactory( class WebDavFactory(
private val context: Context, private val context: Context,
) { ) {
fun createAppStoragePlugin(config: WebDavConfig): StoragePlugin<WebDavConfig> { fun createBackend(config: WebDavConfig): Backend = WebDavBackend(config)
return WebDavStoragePlugin(config)
}
fun createFilesStoragePlugin( fun createFilesStoragePlugin(
config: WebDavConfig, config: WebDavConfig,

View file

@ -10,10 +10,12 @@ import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.getAvailableBackups
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.webdav.WebDavConfig import org.calyxos.seedvault.core.backends.webdav.WebDavConfig
import java.io.IOException import java.io.IOException
@ -22,7 +24,7 @@ internal sealed interface WebDavConfigState {
object Checking : WebDavConfigState object Checking : WebDavConfigState
class Success( class Success(
val properties: WebDavProperties, val properties: WebDavProperties,
val plugin: WebDavStoragePlugin, val backend: Backend,
) : WebDavConfigState ) : WebDavConfigState
class Error(val e: Exception?) : WebDavConfigState class Error(val e: Exception?) : WebDavConfigState
@ -52,11 +54,11 @@ internal class WebDavHandler(
suspend fun onConfigReceived(config: WebDavConfig) { suspend fun onConfigReceived(config: WebDavConfig) {
mConfigState.value = WebDavConfigState.Checking mConfigState.value = WebDavConfigState.Checking
val plugin = webDavFactory.createAppStoragePlugin(config) as WebDavStoragePlugin val backend = webDavFactory.createBackend(config)
try { try {
if (plugin.test()) { if (backend.test()) {
val properties = createWebDavProperties(context, config) val properties = createWebDavProperties(context, config)
mConfigState.value = WebDavConfigState.Success(properties, plugin) mConfigState.value = WebDavConfigState.Success(properties, backend)
} else { } else {
mConfigState.value = WebDavConfigState.Error(null) mConfigState.value = WebDavConfigState.Error(null)
} }
@ -76,8 +78,8 @@ internal class WebDavHandler(
*/ */
@WorkerThread @WorkerThread
@Throws(IOException::class) @Throws(IOException::class)
suspend fun hasAppBackup(appPlugin: WebDavStoragePlugin): Boolean { suspend fun hasAppBackup(backend: Backend): Boolean {
val backups = appPlugin.getAvailableBackups() val backups = backend.getAvailableBackups()
return backups != null && backups.iterator().hasNext() return backups != null && backups.iterator().hasNext()
} }
@ -85,10 +87,10 @@ internal class WebDavHandler(
settingsManager.saveWebDavConfig(properties.config) settingsManager.saveWebDavConfig(properties.config)
} }
fun setPlugin(properties: WebDavProperties, plugin: WebDavStoragePlugin) { fun setPlugin(properties: WebDavProperties, backend: Backend) {
storagePluginManager.changePlugins( storagePluginManager.changePlugins(
storageProperties = properties, storageProperties = properties,
appPlugin = plugin, backend = backend,
filesPlugin = webDavFactory.createFilesStoragePlugin(properties.config), filesPlugin = webDavFactory.createFilesStoragePlugin(properties.config),
) )
} }

View file

@ -49,7 +49,7 @@ internal class WebDavStoragePlugin(
FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token)
else -> LegacyAppBackupFile.Blob(token, name) else -> LegacyAppBackupFile.Blob(token, name)
} }
return delegate.save(handle).outputStream() return delegate.save(handle)
} }
@Throws(IOException::class) @Throws(IOException::class)
@ -59,7 +59,7 @@ internal class WebDavStoragePlugin(
FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token)
else -> LegacyAppBackupFile.Blob(token, name) else -> LegacyAppBackupFile.Blob(token, name)
} }
return delegate.load(handle).inputStream() return delegate.load(handle)
} }
@Throws(IOException::class) @Throws(IOException::class)

View file

@ -101,7 +101,7 @@ internal class AppDataRestoreManager(
return return
} }
val providerPackageName = storagePluginManager.appPlugin.providerPackageName val providerPackageName = storagePluginManager.backend.providerPackageName
val observer = RestoreObserver( val observer = RestoreObserver(
restoreCoordinator = restoreCoordinator, restoreCoordinator = restoreCoordinator,
restorableBackup = restorableBackup, restorableBackup = restorableBackup,

View file

@ -17,7 +17,6 @@ import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM
import com.stevesoltys.seedvault.ui.systemData import com.stevesoltys.seedvault.ui.systemData
import com.stevesoltys.seedvault.worker.FILE_BACKUP_ICONS
import com.stevesoltys.seedvault.worker.IconManager import com.stevesoltys.seedvault.worker.IconManager
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -25,6 +24,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import java.util.Locale import java.util.Locale
internal class SelectedAppsState( internal class SelectedAppsState(
@ -88,10 +88,10 @@ internal class AppSelectionManager(
SelectedAppsState(apps = items, allSelected = isSetupWizard, iconsLoaded = false) SelectedAppsState(apps = items, allSelected = isSetupWizard, iconsLoaded = false)
// download icons // download icons
coroutineScope.launch(workDispatcher) { coroutineScope.launch(workDispatcher) {
val plugin = pluginManager.appPlugin val backend = pluginManager.backend
val token = restorableBackup.token val token = restorableBackup.token
val packagesWithIcons = try { val packagesWithIcons = try {
plugin.getInputStream(token, FILE_BACKUP_ICONS).use { backend.load(LegacyAppBackupFile.IconsFile(token)).use {
iconManager.downloadIcons(restorableBackup.version, token, it) iconManager.downloadIcons(restorableBackup.version, token, it)
} }
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -34,6 +34,7 @@ import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.Locale import java.util.Locale
@ -54,7 +55,7 @@ internal class ApkRestore(
) { ) {
private val pm = context.packageManager private val pm = context.packageManager
private val storagePlugin get() = pluginManager.appPlugin private val backend get() = pluginManager.backend
private val mInstallResult = MutableStateFlow(InstallResult()) private val mInstallResult = MutableStateFlow(InstallResult())
val installResult = mInstallResult.asStateFlow() val installResult = mInstallResult.asStateFlow()
@ -65,7 +66,7 @@ internal class ApkRestore(
val packages = backup.packageMetadataMap.mapNotNull { (packageName, metadata) -> val packages = backup.packageMetadataMap.mapNotNull { (packageName, metadata) ->
// We need to exclude the DocumentsProvider used to retrieve backup data. // We need to exclude the DocumentsProvider used to retrieve backup data.
// Otherwise, it gets killed when we install it, terminating our restoration. // 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 // 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 if (packageName == MAGIC_PACKAGE_MANAGER) return@mapNotNull null
// we don't filter out apps without APK, so the user can manually install them // 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) legacyStoragePlugin.getApkInputStream(token, packageName, suffix)
} else { } else {
val name = crypto.getNameForApk(salt, packageName, suffix) val name = crypto.getNameForApk(salt, packageName, suffix)
storagePlugin.getInputStream(token, name) backend.load(LegacyAppBackupFile.Blob(token, name))
} }
val sha256 = copyStreamsAndGetHash(inputStream, cachedApk.outputStream()) val sha256 = copyStreamsAndGetHash(inputStream, cachedApk.outputStream())
return Pair(cachedApk, sha256) return Pair(cachedApk, sha256)

View file

@ -12,13 +12,13 @@ import android.net.Uri
import androidx.annotation.UiThread import androidx.annotation.UiThread
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.stevesoltys.seedvault.permitDiskReads 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.saf.SafStorage
import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler.Companion.createWebDavProperties import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler.Companion.createWebDavProperties
import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties
import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator 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 org.calyxos.seedvault.core.backends.webdav.WebDavConfig
import java.util.concurrent.ConcurrentSkipListSet 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) { val value = when (plugin) {
is DocumentsProviderStoragePlugin -> StoragePluginType.SAF is SafBackend -> StoragePluginType.SAF
is WebDavStoragePlugin -> StoragePluginType.WEB_DAV is WebDavBackend -> StoragePluginType.WEB_DAV
else -> error("Unsupported plugin: ${plugin::class.java.simpleName}") else -> error("Unsupported plugin: ${plugin::class.java.simpleName}")
}.name }.name
prefs.edit() prefs.edit()

View file

@ -45,13 +45,13 @@ internal class WebDavStoragePlugin(
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun getChunkOutputStream(chunkId: String): OutputStream { override suspend fun getChunkOutputStream(chunkId: String): OutputStream {
val fileHandle = FileBackupFileType.Blob(androidId, chunkId) val fileHandle = FileBackupFileType.Blob(androidId, chunkId)
return delegate.save(fileHandle).outputStream() return delegate.save(fileHandle)
} }
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun getBackupSnapshotOutputStream(timestamp: Long): OutputStream { override suspend fun getBackupSnapshotOutputStream(timestamp: Long): OutputStream {
val fileHandle = FileBackupFileType.Snapshot(androidId, timestamp) val fileHandle = FileBackupFileType.Snapshot(androidId, timestamp)
return delegate.save(fileHandle).outputStream() return delegate.save(fileHandle)
} }
/************************* Restore *******************************/ /************************* Restore *******************************/
@ -73,7 +73,7 @@ internal class WebDavStoragePlugin(
override suspend fun getBackupSnapshotInputStream(storedSnapshot: StoredSnapshot): InputStream { override suspend fun getBackupSnapshotInputStream(storedSnapshot: StoredSnapshot): InputStream {
val androidId = storedSnapshot.androidId val androidId = storedSnapshot.androidId
val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp) val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp)
return delegate.load(handle).inputStream() return delegate.load(handle)
} }
@Throws(IOException::class) @Throws(IOException::class)
@ -82,7 +82,7 @@ internal class WebDavStoragePlugin(
chunkId: String, chunkId: String,
): InputStream { ): InputStream {
val handle = FileBackupFileType.Blob(snapshot.androidId, chunkId) val handle = FileBackupFileType.Blob(snapshot.androidId, chunkId)
return delegate.load(handle).inputStream() return delegate.load(handle)
} }
/************************* Pruning *******************************/ /************************* Pruning *******************************/

View file

@ -31,6 +31,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.getMetadataOutputStream
import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.plugins.isOutOfSpace
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
@ -74,7 +75,7 @@ internal class BackupCoordinator(
private val nm: BackupNotificationManager, private val nm: BackupNotificationManager,
) { ) {
private val plugin get() = pluginManager.appPlugin private val backend get() = pluginManager.backend
private val state = CoordinatorState( private val state = CoordinatorState(
calledInitialize = false, calledInitialize = false,
calledClearBackupData = false, calledClearBackupData = false,
@ -97,7 +98,6 @@ internal class BackupCoordinator(
val token = clock.time() val token = clock.time()
Log.i(TAG, "Starting new RestoreSet with token $token...") Log.i(TAG, "Starting new RestoreSet with token $token...")
settingsManager.setNewToken(token) settingsManager.setNewToken(token)
plugin.startNewRestoreSet(token)
Log.d(TAG, "Resetting backup metadata...") Log.d(TAG, "Resetting backup metadata...")
metadataManager.onDeviceInitialization(token) metadataManager.onDeviceInitialization(token)
} }
@ -125,7 +125,6 @@ internal class BackupCoordinator(
// instead of simply deleting the current one // instead of simply deleting the current one
startNewRestoreSet() startNewRestoreSet()
Log.i(TAG, "Initialize Device!") Log.i(TAG, "Initialize Device!")
plugin.initializeDevice()
// [finishBackup] will only be called when we return [TRANSPORT_OK] here // [finishBackup] will only be called when we return [TRANSPORT_OK] here
// so we remember that we initialized successfully // so we remember that we initialized successfully
state.calledInitialize = true state.calledInitialize = true
@ -410,7 +409,8 @@ internal class BackupCoordinator(
} }
private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType, size: Long?) { 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) metadataManager.onPackageBackedUp(packageInfo, type, size, it)
} }
} }
@ -418,7 +418,8 @@ internal class BackupCoordinator(
private suspend fun onPackageBackupError(packageInfo: PackageInfo, type: BackupType) { private suspend fun onPackageBackupError(packageInfo: PackageInfo, type: BackupType) {
val packageName = packageInfo.packageName val packageName = packageInfo.packageName
try { try {
plugin.getMetadataOutputStream().use { val token = settingsManager.getToken() ?: error("no token")
backend.getMetadataOutputStream(token).use {
metadataManager.onPackageBackupError(packageInfo, state.cancelReason, it, type) metadataManager.onPackageBackupError(packageInfo, state.cancelReason, it, type)
} }
} catch (e: IOException) { } catch (e: IOException) {

View file

@ -20,6 +20,7 @@ import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.plugins.isOutOfSpace
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import java.io.Closeable import java.io.Closeable
import java.io.EOFException import java.io.EOFException
import java.io.IOException import java.io.IOException
@ -53,7 +54,7 @@ internal class FullBackup(
private val crypto: Crypto, private val crypto: Crypto,
) { ) {
private val plugin get() = pluginManager.appPlugin private val backend get() = pluginManager.backend
private var state: FullBackupState? = null private var state: FullBackupState? = null
fun hasState() = state != null fun hasState() = state != null
@ -128,7 +129,7 @@ internal class FullBackup(
val name = crypto.getNameForPackage(salt, packageName) val name = crypto.getNameForPackage(salt, packageName)
// get OutputStream to write backup data into // get OutputStream to write backup data into
val outputStream = try { val outputStream = try {
plugin.getOutputStream(token, name) backend.save(LegacyAppBackupFile.Blob(token, name))
} catch (e: IOException) { } catch (e: IOException) {
"Error getting OutputStream for full backup of $packageName".let { "Error getting OutputStream for full backup of $packageName".let {
Log.e(TAG, it, e) Log.e(TAG, it, e)
@ -186,7 +187,7 @@ internal class FullBackup(
@Throws(IOException::class) @Throws(IOException::class)
suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) { suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) {
val name = crypto.getNameForPackage(salt, packageInfo.packageName) 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) { suspend fun cancelFullBackup(token: Long, salt: String, ignoreApp: Boolean) {

View file

@ -22,6 +22,7 @@ import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.plugins.isOutOfSpace
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import java.io.IOException import java.io.IOException
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
@ -47,7 +48,7 @@ internal class KVBackup(
private val dbManager: KvDbManager, private val dbManager: KvDbManager,
) { ) {
private val plugin get() = pluginManager.appPlugin private val backend get() = pluginManager.backend
private var state: KVBackupState? = null private var state: KVBackupState? = null
fun hasState() = state != null fun hasState() = state != null
@ -207,7 +208,7 @@ internal class KVBackup(
suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) { suspend fun clearBackupData(packageInfo: PackageInfo, token: Long, salt: String) {
Log.i(TAG, "Clearing K/V data of ${packageInfo.packageName}") Log.i(TAG, "Clearing K/V data of ${packageInfo.packageName}")
val name = state?.name ?: crypto.getNameForPackage(salt, 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() if (!dbManager.deleteDb(packageInfo.packageName)) throw IOException()
} }
@ -254,7 +255,8 @@ internal class KVBackup(
db.vacuum() db.vacuum()
db.close() db.close()
plugin.getOutputStream(token, name).use { outputStream -> val handle = LegacyAppBackupFile.Blob(token, name)
backend.save(handle).use { outputStream ->
outputStream.write(ByteArray(1) { VERSION }) outputStream.write(ByteArray(1) { VERSION })
val ad = getADForKV(VERSION, packageName) val ad = getADForKV(VERSION, packageName)
crypto.newEncryptingStream(outputStream, ad).use { encryptedStream -> crypto.newEncryptingStream(outputStream, ad).use { encryptedStream ->

View file

@ -27,9 +27,9 @@ import android.util.Log
import android.util.Log.INFO import android.util.Log.INFO
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import org.calyxos.seedvault.core.backends.Backend
private val TAG = PackageService::class.java.simpleName private val TAG = PackageService::class.java.simpleName
@ -48,7 +48,7 @@ internal class PackageService(
private val packageManager: PackageManager = context.packageManager private val packageManager: PackageManager = context.packageManager
private val myUserId = UserHandle.myUserId() private val myUserId = UserHandle.myUserId()
private val plugin: StoragePlugin<*> get() = pluginManager.appPlugin private val backend: Backend get() = pluginManager.backend
val eligiblePackages: List<String> val eligiblePackages: List<String>
@WorkerThread @WorkerThread
@ -182,7 +182,7 @@ internal class PackageService(
// We need to explicitly exclude DocumentsProvider and Seedvault. // We need to explicitly exclude DocumentsProvider and Seedvault.
// Otherwise, they get killed while backing them up, terminating our backup. // Otherwise, they get killed while backing them up, terminating our backup.
val excludedPackages = setOf( val excludedPackages = setOf(
plugin.providerPackageName, backend.providerPackageName,
context.packageName context.packageName
) )
@ -225,7 +225,7 @@ internal class PackageService(
*/ */
private fun PackageInfo.doesNotGetBackedUp(): Boolean { private fun PackageInfo.doesNotGetBackedUp(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true
if (packageName == plugin.providerPackageName) return true if (packageName == backend.providerPackageName) return true
return !allowsBackup() || isStopped() return !allowsBackup() || isStopped()
} }
} }

View file

@ -20,6 +20,7 @@ import com.stevesoltys.seedvault.header.getADForFull
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import libcore.io.IoUtils.closeQuietly import libcore.io.IoUtils.closeQuietly
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import java.io.EOFException import java.io.EOFException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -46,7 +47,7 @@ internal class FullRestore(
private val crypto: Crypto, private val crypto: Crypto,
) { ) {
private val plugin get() = pluginManager.appPlugin private val backend get() = pluginManager.backend
private var state: FullRestoreState? = null private var state: FullRestoreState? = null
fun hasState() = state != null fun hasState() = state != null
@ -114,7 +115,8 @@ internal class FullRestore(
crypto.decryptHeader(inputStream, version, packageName) crypto.decryptHeader(inputStream, version, packageName)
state.inputStream = inputStream state.inputStream = inputStream
} else { } 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 version = headerReader.readVersion(inputStream, state.version)
val ad = getADForFull(version, packageName) val ad = getADForFull(version, packageName)
state.inputStream = crypto.newDecryptingStream(inputStream, ad) state.inputStream = crypto.newDecryptingStream(inputStream, ad)

View file

@ -25,6 +25,7 @@ import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.transport.backup.KVDb import com.stevesoltys.seedvault.transport.backup.KVDb
import com.stevesoltys.seedvault.transport.backup.KvDbManager import com.stevesoltys.seedvault.transport.backup.KvDbManager
import libcore.io.IoUtils.closeQuietly import libcore.io.IoUtils.closeQuietly
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import java.io.IOException import java.io.IOException
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
@ -53,7 +54,7 @@ internal class KVRestore(
private val dbManager: KvDbManager, private val dbManager: KvDbManager,
) { ) {
private val plugin get() = pluginManager.appPlugin private val backend get() = pluginManager.backend
private var state: KVRestoreState? = null private var state: KVRestoreState? = null
/** /**
@ -156,7 +157,8 @@ internal class KVRestore(
@Throws(IOException::class, GeneralSecurityException::class, UnsupportedVersionException::class) @Throws(IOException::class, GeneralSecurityException::class, UnsupportedVersionException::class)
private suspend fun downloadRestoreDb(state: KVRestoreState): KVDb { private suspend fun downloadRestoreDb(state: KVRestoreState): KVDb {
val packageName = state.packageInfo.packageName 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) headerReader.readVersion(inputStream, state.version)
val ad = getADForKV(VERSION, packageName) val ad = getADForKV(VERSION, packageName)
crypto.newDecryptingStream(inputStream, ad).use { decryptedStream -> crypto.newDecryptingStream(inputStream, ad).use { decryptedStream ->

View file

@ -25,12 +25,13 @@ import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.DecryptionFailedException import com.stevesoltys.seedvault.metadata.DecryptionFailedException
import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.MetadataReader import com.stevesoltys.seedvault.metadata.MetadataReader
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.getAvailableBackups
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS
import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import org.calyxos.seedvault.core.backends.Backend
import java.io.IOException import java.io.IOException
/** /**
@ -67,13 +68,13 @@ internal class RestoreCoordinator(
private val metadataReader: MetadataReader, 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 state: RestoreCoordinatorState? = null
private var backupMetadata: BackupMetadata? = null private var backupMetadata: BackupMetadata? = null
private val failedPackages = ArrayList<String>() private val failedPackages = ArrayList<String>()
suspend fun getAvailableMetadata(): Map<Long, BackupMetadata>? { suspend fun getAvailableMetadata(): Map<Long, BackupMetadata>? {
val availableBackups = plugin.getAvailableBackups() ?: return null val availableBackups = backend.getAvailableBackups() ?: return null
val metadataMap = HashMap<Long, BackupMetadata>() val metadataMap = HashMap<Long, BackupMetadata>()
for (encryptedMetadata in availableBackups) { for (encryptedMetadata in availableBackups) {
try { try {

View file

@ -18,7 +18,6 @@ import com.stevesoltys.seedvault.plugins.saf.SafHandler
import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler
import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties
import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.storage.StorageBackupJobService import com.stevesoltys.seedvault.storage.StorageBackupJobService
import com.stevesoltys.seedvault.transport.backup.BackupInitializer import com.stevesoltys.seedvault.transport.backup.BackupInitializer
@ -27,6 +26,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.backup.BackupJobService import org.calyxos.backup.storage.backup.BackupJobService
import org.calyxos.seedvault.core.backends.Backend
import java.io.IOException import java.io.IOException
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -59,9 +59,9 @@ internal class BackupStorageViewModel(
onStorageLocationSet(safStorage.isUsb) onStorageLocationSet(safStorage.isUsb)
} }
override fun onWebDavConfigSet(properties: WebDavProperties, plugin: WebDavStoragePlugin) { override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) {
webdavHandler.save(properties) webdavHandler.save(properties)
webdavHandler.setPlugin(properties, plugin) webdavHandler.setPlugin(properties, backend)
scheduleBackupWorkers() scheduleBackupWorkers()
onStorageLocationSet(isUsb = false) onStorageLocationSet(isUsb = false)
} }

View file

@ -15,10 +15,10 @@ import com.stevesoltys.seedvault.plugins.saf.SafHandler
import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler
import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties
import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.calyxos.seedvault.core.backends.Backend
import java.io.IOException import java.io.IOException
private val TAG = RestoreStorageViewModel::class.java.simpleName 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) { viewModelScope.launch(Dispatchers.IO) {
val hasBackup = try { val hasBackup = try {
webdavHandler.hasAppBackup(plugin) webdavHandler.hasAppBackup(backend)
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error reading: ${properties.config.url}", e) Log.e(TAG, "Error reading: ${properties.config.url}", e)
false false
} }
if (hasBackup) { if (hasBackup) {
webdavHandler.save(properties) webdavHandler.save(properties)
webdavHandler.setPlugin(properties, plugin) webdavHandler.setPlugin(properties, backend)
mLocationChecked.postEvent(LocationResult()) mLocationChecked.postEvent(LocationResult())
} else { } else {
Log.w(TAG, "Location was rejected: ${properties.config.url}") Log.w(TAG, "Location was rejected: ${properties.config.url}")

View file

@ -18,13 +18,13 @@ import com.stevesoltys.seedvault.plugins.saf.SafHandler
import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler
import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties
import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent
import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.webdav.WebDavConfig import org.calyxos.seedvault.core.backends.webdav.WebDavConfig
internal abstract class StorageViewModel( internal abstract class StorageViewModel(
@ -89,7 +89,7 @@ internal abstract class StorageViewModel(
} }
abstract fun onSafUriSet(safStorage: SafStorage) abstract fun onSafUriSet(safStorage: SafStorage)
abstract fun onWebDavConfigSet(properties: WebDavProperties, plugin: WebDavStoragePlugin) abstract fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend)
override fun onCleared() { override fun onCleared() {
storageOptionFetcher.setRemovableStorageListener(null) storageOptionFetcher.setRemovableStorageListener(null)
@ -107,9 +107,9 @@ internal abstract class StorageViewModel(
fun resetWebDavConfig() = webdavHandler.resetConfigState() fun resetWebDavConfig() = webdavHandler.resetConfigState()
@UiThread @UiThread
fun onWebDavConfigSuccess(properties: WebDavProperties, plugin: WebDavStoragePlugin) { fun onWebDavConfigSuccess(properties: WebDavProperties, backend: Backend) {
mLocationSet.setEvent(true) mLocationSet.setEvent(true)
onWebDavConfigSet(properties, plugin) onWebDavConfigSet(properties, backend)
} }
} }

View file

@ -111,7 +111,7 @@ class WebDavConfigFragment : Fragment(), View.OnClickListener {
} }
is WebDavConfigState.Success -> { is WebDavConfigState.Success -> {
viewModel.onWebDavConfigSuccess(state.properties, state.plugin) viewModel.onWebDavConfigSuccess(state.properties, state.backend)
} }
is WebDavConfigState.Error -> { is WebDavConfigState.Error -> {

View file

@ -13,6 +13,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.getMetadataOutputStream
import com.stevesoltys.seedvault.plugins.isOutOfSpace import com.stevesoltys.seedvault.plugins.isOutOfSpace
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.settings.SettingsManager 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.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.getAppName import com.stevesoltys.seedvault.ui.notification.getAppName
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@ -55,7 +57,8 @@ internal class ApkBackupManager(
keepTrying { keepTrying {
// upload all local changes only at the end, // upload all local changes only at the end,
// so we don't have to re-upload the metadata // 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) metadataManager.uploadMetadata(outputStream)
} }
} }
@ -101,7 +104,8 @@ internal class ApkBackupManager(
private suspend fun uploadIcons() { private suspend fun uploadIcons() {
try { try {
val token = settingsManager.getToken() ?: throw IOException("no current token") 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) iconManager.uploadIcons(token, it)
} }
} catch (e: IOException) { } catch (e: IOException) {
@ -119,7 +123,7 @@ internal class ApkBackupManager(
return try { return try {
apkBackup.backupApkIfNecessary(packageInfo) { name -> apkBackup.backupApkIfNecessary(packageInfo) { name ->
val token = settingsManager.getToken() ?: throw IOException("no current token") val token = settingsManager.getToken() ?: throw IOException("no current token")
pluginManager.appPlugin.getOutputStream(token, name) pluginManager.backend.save(LegacyAppBackupFile.Blob(token, name))
}?.let { packageMetadata -> }?.let { packageMetadata ->
metadataManager.onApkBackedUp(packageInfo, packageMetadata) metadataManager.onApkBackedUp(packageInfo, packageMetadata)
true true

View file

@ -13,13 +13,11 @@ import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.metadata.BackupMetadata import com.stevesoltys.seedvault.metadata.BackupMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_CONTACTS import com.stevesoltys.seedvault.ui.PACKAGE_NAME_CONTACTS
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SETTINGS import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SETTINGS
import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM
import com.stevesoltys.seedvault.worker.FILE_BACKUP_ICONS
import com.stevesoltys.seedvault.worker.IconManager import com.stevesoltys.seedvault.worker.IconManager
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
@ -28,6 +26,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import org.junit.Test import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
@ -221,10 +221,10 @@ internal class AppSelectionManagerTest : TransportTest() {
@Test @Test
fun `test icon loading fails`() = scope.runTest { fun `test icon loading fails`() = scope.runTest {
val appPlugin: StoragePlugin<*> = mockk() val backend: Backend = mockk()
every { storagePluginManager.appPlugin } returns appPlugin every { storagePluginManager.backend } returns backend
coEvery { coEvery {
appPlugin.getInputStream(backupMetadata.token, FILE_BACKUP_ICONS) backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token))
} throws IOException() } throws IOException()
appSelectionManager.selectedAppsFlow.test { appSelectionManager.selectedAppsFlow.test {
@ -427,11 +427,11 @@ internal class AppSelectionManagerTest : TransportTest() {
} }
private fun expectIconLoading(icons: Set<String> = setOf(packageName1, packageName2)) { private fun expectIconLoading(icons: Set<String> = setOf(packageName1, packageName2)) {
val appPlugin: StoragePlugin<*> = mockk() val backend: Backend = mockk()
val inputStream = ByteArrayInputStream(Random.nextBytes(42)) val inputStream = ByteArrayInputStream(Random.nextBytes(42))
every { storagePluginManager.appPlugin } returns appPlugin every { storagePluginManager.backend } returns backend
coEvery { coEvery {
appPlugin.getInputStream(backupMetadata.token, FILE_BACKUP_ICONS) backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token))
} returns inputStream } returns inputStream
every { every {
iconManager.downloadIcons(backupMetadata.version, backupMetadata.token, inputStream) iconManager.downloadIcons(backupMetadata.version, backupMetadata.token, inputStream)

View file

@ -21,7 +21,6 @@ import com.stevesoltys.seedvault.metadata.ApkSplit
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.RestorableBackup
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
@ -36,6 +35,8 @@ import io.mockk.mockkStatic
import io.mockk.slot import io.mockk.slot
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking 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.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
@ -65,7 +66,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
@Suppress("Deprecation") @Suppress("Deprecation")
private val legacyStoragePlugin: LegacyStoragePlugin = mockk() private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
private val storagePlugin: StoragePlugin<*> = mockk() private val backend: Backend = mockk()
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk() private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
private val apkInstaller: ApkInstaller = mockk() private val apkInstaller: ApkInstaller = mockk()
private val installRestriction: InstallRestriction = mockk() private val installRestriction: InstallRestriction = mockk()
@ -111,7 +112,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
init { init {
mockkStatic(PackageUtils::class) mockkStatic(PackageUtils::class)
every { storagePluginManager.appPlugin } returns storagePlugin every { storagePluginManager.backend } returns backend
} }
@Test @Test
@ -147,7 +148,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
every { metadataManager.salt } returns salt every { metadataManager.salt } returns salt
every { crypto.getNameForApk(salt, packageName) } returns name every { crypto.getNameForApk(salt, packageName) } returns name
every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkBackup.backupApkIfNecessary(packageInfo, outputStreamGetter) apkBackup.backupApkIfNecessary(packageInfo, outputStreamGetter)
@ -164,7 +165,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException() every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
every { strictContext.cacheDir } returns tmpFile every { strictContext.cacheDir } returns tmpFile
every { crypto.getNameForApk(salt, packageName, "") } returns name 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<Int>()) } returns packageInfo every { pm.getPackageArchiveInfo(capture(apkPath), any<Int>()) } returns packageInfo
every { applicationInfo.loadIcon(pm) } returns icon every { applicationInfo.loadIcon(pm) } returns icon
every { pm.getApplicationLabel(packageInfo.applicationInfo!!) } returns appName every { pm.getApplicationLabel(packageInfo.applicationInfo!!) } returns appName
@ -172,7 +173,9 @@ internal class ApkBackupRestoreTest : TransportTest() {
splitCompatChecker.isCompatible(metadata.deviceName, listOf(splitName)) splitCompatChecker.isCompatible(metadata.deviceName, listOf(splitName))
} returns true } returns true
every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName 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( val resultMap = mapOf(
packageName to ApkInstallResult( packageName to ApkInstallResult(
packageName, packageName,

View file

@ -25,7 +25,6 @@ import com.stevesoltys.seedvault.metadata.ApkSplit
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.RestorableBackup
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
@ -44,6 +43,8 @@ import io.mockk.mockkStatic
import io.mockk.verifyOrder import io.mockk.verifyOrder
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking 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
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
@ -67,7 +68,7 @@ internal class ApkRestoreTest : TransportTest() {
private val backupManager: IBackupManager = mockk() private val backupManager: IBackupManager = mockk()
private val backupStateManager: BackupStateManager = mockk() private val backupStateManager: BackupStateManager = mockk()
private val storagePluginManager: StoragePluginManager = mockk() private val storagePluginManager: StoragePluginManager = mockk()
private val storagePlugin: StoragePlugin<*> = mockk() private val backend: Backend = mockk()
private val legacyStoragePlugin: LegacyStoragePlugin = mockk() private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk() private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
private val apkInstaller: ApkInstaller = 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 // as we don't do strict signature checking, we can use a relaxed mock
packageInfo.signingInfo = mockk(relaxed = true) packageInfo.signingInfo = mockk(relaxed = true)
every { storagePluginManager.appPlugin } returns storagePlugin every { storagePluginManager.backend } returns backend
// related to starting/stopping service // related to starting/stopping service
every { strictContext.packageName } returns "org.foo.bar" every { strictContext.packageName } returns "org.foo.bar"
@ -128,8 +129,8 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { strictContext.cacheDir } returns File(tmpDir.toString()) every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name every { crypto.getNameForApk(salt, packageName, "") } returns name
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream coEvery { backend.load(handle) } returns apkInputStream
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -151,7 +152,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException() every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
apkRestore.installResult.test { apkRestore.installResult.test {
@ -177,7 +178,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
val packageInfo: PackageInfo = mockk() val packageInfo: PackageInfo = mockk()
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
@ -202,9 +203,9 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { strictContext.cacheDir } returns File(tmpDir.toString()) every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name 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<Int>()) } returns packageInfo every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -222,7 +223,7 @@ internal class ApkRestoreTest : TransportTest() {
coEvery { coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any()) apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
} throws SecurityException() } throws SecurityException()
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -249,7 +250,7 @@ internal class ApkRestoreTest : TransportTest() {
coEvery { coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any()) apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
} returns installResult } returns installResult
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -285,7 +286,7 @@ internal class ApkRestoreTest : TransportTest() {
coEvery { coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any()) apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
} returns installResult } returns installResult
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -300,7 +301,7 @@ internal class ApkRestoreTest : TransportTest() {
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt") mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
every { installRestriction.isAllowedToInstallApks() } returns true every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!! every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!!
every { every {
@ -329,7 +330,7 @@ internal class ApkRestoreTest : TransportTest() {
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt") mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
every { installRestriction.isAllowedToInstallApks() } returns true every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!! every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!!
every { packageInfo.longVersionCode } returns packageMetadata.version!! - 1 every { packageInfo.longVersionCode } returns packageMetadata.version!! - 1
@ -369,7 +370,7 @@ internal class ApkRestoreTest : TransportTest() {
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt") mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
every { installRestriction.isAllowedToInstallApks() } returns true every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
every { packageInfo.signingInfo.getSignatures() } returns listOf("foobar") every { packageInfo.signingInfo.getSignatures() } returns listOf("foobar")
@ -401,7 +402,7 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException() every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
cacheBaseApkAndGetInfo(tmpDir) cacheBaseApkAndGetInfo(tmpDir)
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
if (willFail) { if (willFail) {
every { every {
@ -476,7 +477,7 @@ internal class ApkRestoreTest : TransportTest() {
every { every {
splitCompatChecker.isCompatible(deviceName, listOf(split1Name, split2Name)) splitCompatChecker.isCompatible(deviceName, listOf(split1Name, split2Name))
} returns false } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -502,9 +503,9 @@ internal class ApkRestoreTest : TransportTest() {
every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true
every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName
coEvery { coEvery {
storagePlugin.getInputStream(token, suffixName) backend.load(LegacyAppBackupFile.Blob(token, suffixName))
} returns ByteArrayInputStream(getRandomByteArray()) } returns ByteArrayInputStream(getRandomByteArray())
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -531,8 +532,10 @@ internal class ApkRestoreTest : TransportTest() {
every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true
every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName
coEvery { storagePlugin.getInputStream(token, suffixName) } throws IOException() coEvery {
every { storagePlugin.providerPackageName } returns storageProviderPackageName backend.load(LegacyAppBackupFile.Blob(token, suffixName))
} throws IOException()
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -573,10 +576,14 @@ internal class ApkRestoreTest : TransportTest() {
val suffixName1 = getRandomString() val suffixName1 = getRandomString()
val suffixName2 = getRandomString() val suffixName2 = getRandomString()
every { crypto.getNameForApk(salt, packageName, split1Name) } returns suffixName1 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 every { crypto.getNameForApk(salt, packageName, split2Name) } returns suffixName2
coEvery { storagePlugin.getInputStream(token, suffixName2) } returns split2InputStream coEvery {
every { storagePlugin.providerPackageName } returns storageProviderPackageName backend.load(LegacyAppBackupFile.Blob(token, suffixName2))
} returns split2InputStream
every { backend.providerPackageName } returns storageProviderPackageName
val resultMap = mapOf( val resultMap = mapOf(
packageName to ApkInstallResult( packageName to ApkInstallResult(
@ -602,7 +609,7 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
// set the storage provider package name to match our current package name, // set the storage provider package name to match our current package name,
// and ensure that the current package is therefore skipped. // and ensure that the current package is therefore skipped.
every { storagePlugin.providerPackageName } returns packageName every { backend.providerPackageName } returns packageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -627,7 +634,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -656,7 +663,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns true every { backupStateManager.isAutoRestoreEnabled } returns true
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
every { backupManager.setAutoRestore(false) } just Runs every { backupManager.setAutoRestore(false) } just Runs
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException() every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
// cache APK and get icon as well as app name // cache APK and get icon as well as app name
@ -680,7 +687,7 @@ internal class ApkRestoreTest : TransportTest() {
@Test @Test
fun `no apks get installed when blocked by policy`() = runBlocking { fun `no apks get installed when blocked by policy`() = runBlocking {
every { installRestriction.isAllowedToInstallApks() } returns false every { installRestriction.isAllowedToInstallApks() } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test { apkRestore.installResult.test {
awaitItem() // initial empty state awaitItem() // initial empty state
@ -703,7 +710,7 @@ internal class ApkRestoreTest : TransportTest() {
private fun cacheBaseApkAndGetInfo(tmpDir: Path) { private fun cacheBaseApkAndGetInfo(tmpDir: Path) {
every { strictContext.cacheDir } returns File(tmpDir.toString()) every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name 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<Int>()) } returns packageInfo every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo
every { applicationInfo.loadIcon(pm) } returns icon every { applicationInfo.loadIcon(pm) } returns icon
every { pm.getApplicationLabel(packageInfo.applicationInfo!!) } returns appName every { pm.getApplicationLabel(packageInfo.applicationInfo!!) } returns appName

View file

@ -21,9 +21,7 @@ import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager 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.BackupCoordinator
import com.stevesoltys.seedvault.transport.backup.FullBackup import com.stevesoltys.seedvault.transport.backup.FullBackup
import com.stevesoltys.seedvault.transport.backup.InputFactory import com.stevesoltys.seedvault.transport.backup.InputFactory
@ -44,6 +42,8 @@ import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.runBlocking 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.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.fail import org.junit.jupiter.api.Assertions.fail
@ -67,7 +67,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
@Suppress("Deprecation") @Suppress("Deprecation")
private val legacyPlugin = mockk<LegacyStoragePlugin>() private val legacyPlugin = mockk<LegacyStoragePlugin>()
private val backupPlugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
private val kvBackup = KVBackup( private val kvBackup = KVBackup(
pluginManager = storagePluginManager, pluginManager = storagePluginManager,
settingsManager = settingsManager, settingsManager = settingsManager,
@ -132,7 +132,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
private val realName = cryptoImpl.getNameForPackage(salt, packageInfo.packageName) private val realName = cryptoImpl.getNameForPackage(salt, packageInfo.packageName)
init { init {
every { storagePluginManager.appPlugin } returns backupPlugin every { storagePluginManager.backend } returns backend
} }
@Test @Test
@ -161,7 +161,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
apkBackup.backupApkIfNecessary(packageInfo, any()) apkBackup.backupApkIfNecessary(packageInfo, any())
} returns packageMetadata } returns packageMetadata
coEvery { coEvery {
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) backend.save(LegacyAppBackupFile.Metadata(token))
} returns metadataOutputStream } returns metadataOutputStream
every { every {
metadataManager.onApkBackedUp(packageInfo, packageMetadata) metadataManager.onApkBackedUp(packageInfo, packageMetadata)
@ -179,7 +179,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0)) assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
// upload DB // upload DB
coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream coEvery {
backend.save(LegacyAppBackupFile.Blob(token, realName))
} returns bOutputStream
// finish K/V backup // finish K/V backup
assertEquals(TRANSPORT_OK, backup.finishBackup()) assertEquals(TRANSPORT_OK, backup.finishBackup())
@ -198,7 +200,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// restore finds the backed up key and writes the decrypted value // restore finds the backed up key and writes the decrypted value
val backupDataOutput = mockk<BackupDataOutput>() val backupDataOutput = mockk<BackupDataOutput>()
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray()) 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 { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137 every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137
every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size
@ -237,7 +241,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns null coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns null
every { settingsManager.getToken() } returns token every { settingsManager.getToken() } returns token
coEvery { coEvery {
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) backend.save(LegacyAppBackupFile.Metadata(token))
} returns metadataOutputStream } returns metadataOutputStream
every { every {
metadataManager.onPackageBackedUp( metadataManager.onPackageBackedUp(
@ -252,7 +256,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0)) assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
// upload DB // upload DB
coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream coEvery {
backend.save(LegacyAppBackupFile.Blob(token, realName))
} returns bOutputStream
// finish K/V backup // finish K/V backup
assertEquals(TRANSPORT_OK, backup.finishBackup()) assertEquals(TRANSPORT_OK, backup.finishBackup())
@ -271,7 +277,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// restore finds the backed up key and writes the decrypted value // restore finds the backed up key and writes the decrypted value
val backupDataOutput = mockk<BackupDataOutput>() val backupDataOutput = mockk<BackupDataOutput>()
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray()) 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 { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137 every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137
every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size
@ -294,14 +302,16 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// return streams from plugin and app data // return streams from plugin and app data
val bOutputStream = ByteArrayOutputStream() val bOutputStream = ByteArrayOutputStream()
val bInputStream = ByteArrayInputStream(appData) 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 { inputFactory.getInputStream(fileDescriptor) } returns bInputStream
every { settingsManager.isQuotaUnlimited() } returns false every { settingsManager.isQuotaUnlimited() } returns false
coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns packageMetadata coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns packageMetadata
every { settingsManager.getToken() } returns token every { settingsManager.getToken() } returns token
every { metadataManager.salt } returns salt every { metadataManager.salt } returns salt
coEvery { coEvery {
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA) backend.save(LegacyAppBackupFile.Metadata(token))
} returns metadataOutputStream } returns metadataOutputStream
every { metadataManager.onApkBackedUp(packageInfo, packageMetadata) } just Runs every { metadataManager.onApkBackedUp(packageInfo, packageMetadata) } just Runs
every { every {
@ -333,7 +343,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// reverse the backup streams into restore input // reverse the backup streams into restore input
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray()) val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray())
val rOutputStream = ByteArrayOutputStream() 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 every { outputFactory.getOutputStream(fileDescriptor) } returns rOutputStream
// restore data // restore data

View file

@ -28,6 +28,7 @@ import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.slot import io.mockk.slot
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD import org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD
import kotlin.random.Random import kotlin.random.Random
@ -73,6 +74,7 @@ internal abstract class TransportTest {
protected val name = getRandomString(12) protected val name = getRandomString(12)
protected val name2 = getRandomString(23) protected val name2 = getRandomString(23)
protected val storageProviderPackageName = getRandomString(23) protected val storageProviderPackageName = getRandomString(23)
protected val handle = LegacyAppBackupFile.Blob(token, name)
init { init {
mockkStatic(Log::class) mockkStatic(Log::class)

View file

@ -20,9 +20,7 @@ import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED 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.StoragePluginManager
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.worker.ApkBackup import com.stevesoltys.seedvault.worker.ApkBackup
@ -33,6 +31,8 @@ import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.runBlocking 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.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.io.IOException import java.io.IOException
@ -60,7 +60,7 @@ internal class BackupCoordinatorTest : BackupTest() {
nm = notificationManager, nm = notificationManager,
) )
private val plugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
private val metadataOutputStream = mockk<OutputStream>() private val metadataOutputStream = mockk<OutputStream>()
private val fileDescriptor: ParcelFileDescriptor = mockk() private val fileDescriptor: ParcelFileDescriptor = mockk()
private val packageMetadata: PackageMetadata = mockk() private val packageMetadata: PackageMetadata = mockk()
@ -73,13 +73,12 @@ internal class BackupCoordinatorTest : BackupTest() {
) )
init { init {
every { pluginManager.appPlugin } returns plugin every { pluginManager.backend } returns backend
} }
@Test @Test
fun `device initialization succeeds and delegates to plugin`() = runBlocking { fun `device initialization succeeds and delegates to plugin`() = runBlocking {
expectStartNewRestoreSet() expectStartNewRestoreSet()
coEvery { plugin.initializeDevice() } just Runs
every { kv.hasState() } returns false every { kv.hasState() } returns false
every { full.hasState() } returns false every { full.hasState() } returns false
@ -87,10 +86,9 @@ internal class BackupCoordinatorTest : BackupTest() {
assertEquals(TRANSPORT_OK, backup.finishBackup()) assertEquals(TRANSPORT_OK, backup.finishBackup())
} }
private suspend fun expectStartNewRestoreSet() { private fun expectStartNewRestoreSet() {
every { clock.time() } returns token every { clock.time() } returns token
every { settingsManager.setNewToken(token) } just Runs every { settingsManager.setNewToken(token) } just Runs
coEvery { plugin.startNewRestoreSet(token) } just Runs
every { metadataManager.onDeviceInitialization(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 { fun `error notification when device initialization fails`() = runBlocking {
val maybeTrue = Random.nextBoolean() val maybeTrue = Random.nextBoolean()
expectStartNewRestoreSet() every { clock.time() } returns token
coEvery { plugin.initializeDevice() } throws IOException() every { settingsManager.setNewToken(token) } just Runs
every { metadataManager.onDeviceInitialization(token) } throws IOException()
every { metadataManager.requiresInit } returns maybeTrue every { metadataManager.requiresInit } returns maybeTrue
every { pluginManager.canDoBackupNow() } returns !maybeTrue every { pluginManager.canDoBackupNow() } returns !maybeTrue
every { notificationManager.onBackupError() } just Runs every { notificationManager.onBackupError() } just Runs
@ -117,8 +116,9 @@ internal class BackupCoordinatorTest : BackupTest() {
@Test @Test
fun `no error notification when device initialization fails when no backup possible`() = fun `no error notification when device initialization fails when no backup possible`() =
runBlocking { runBlocking {
expectStartNewRestoreSet() every { clock.time() } returns token
coEvery { plugin.initializeDevice() } throws IOException() every { settingsManager.setNewToken(token) } just Runs
every { metadataManager.onDeviceInitialization(token) } throws IOException()
every { metadataManager.requiresInit } returns false every { metadataManager.requiresInit } returns false
every { pluginManager.canDoBackupNow() } returns false every { pluginManager.canDoBackupNow() } returns false
@ -142,7 +142,6 @@ internal class BackupCoordinatorTest : BackupTest() {
// start new restore set // start new restore set
every { clock.time() } returns token + 1 every { clock.time() } returns token + 1
every { settingsManager.setNewToken(token + 1) } just Runs every { settingsManager.setNewToken(token + 1) } just Runs
coEvery { plugin.startNewRestoreSet(token + 1) } just Runs
every { metadataManager.onDeviceInitialization(token + 1) } just Runs every { metadataManager.onDeviceInitialization(token + 1) } just Runs
every { data.close() } just Runs every { data.close() } just Runs
@ -210,7 +209,7 @@ internal class BackupCoordinatorTest : BackupTest() {
every { kv.getCurrentPackage() } returns packageInfo every { kv.getCurrentPackage() } returns packageInfo
coEvery { kv.finishBackup() } returns TRANSPORT_OK coEvery { kv.finishBackup() } returns TRANSPORT_OK
every { settingsManager.getToken() } returns token 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 { kv.getCurrentSize() } returns size
every { every {
metadataManager.onPackageBackedUp( metadataManager.onPackageBackedUp(
@ -250,7 +249,7 @@ internal class BackupCoordinatorTest : BackupTest() {
every { full.getCurrentPackage() } returns packageInfo every { full.getCurrentPackage() } returns packageInfo
every { full.finishBackup() } returns result every { full.finishBackup() } returns result
every { settingsManager.getToken() } returns token 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 { full.getCurrentSize() } returns size
every { every {
metadataManager.onPackageBackedUp( metadataManager.onPackageBackedUp(
@ -385,7 +384,7 @@ internal class BackupCoordinatorTest : BackupTest() {
private fun expectApkBackupAndMetadataWrite() { private fun expectApkBackupAndMetadataWrite() {
coEvery { apkBackup.backupApkIfNecessary(any(), any()) } returns packageMetadata coEvery { apkBackup.backupApkIfNecessary(any(), any()) } returns packageMetadata
every { settingsManager.getToken() } returns token 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 every { metadataManager.onApkBackedUp(any(), packageMetadata) } just Runs
} }

View file

@ -11,7 +11,6 @@ import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED
import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.header.getADForFull import com.stevesoltys.seedvault.header.getADForFull
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import io.mockk.Runs import io.mockk.Runs
@ -20,6 +19,8 @@ import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.runBlocking 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.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
@ -31,7 +32,7 @@ import kotlin.random.Random
internal class FullBackupTest : BackupTest() { internal class FullBackupTest : BackupTest() {
private val storagePluginManager: StoragePluginManager = mockk() private val storagePluginManager: StoragePluginManager = mockk()
private val plugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
private val notificationManager = mockk<BackupNotificationManager>() private val notificationManager = mockk<BackupNotificationManager>()
private val backup = FullBackup( private val backup = FullBackup(
pluginManager = storagePluginManager, pluginManager = storagePluginManager,
@ -46,7 +47,7 @@ internal class FullBackupTest : BackupTest() {
private val ad = getADForFull(VERSION, packageInfo.packageName) private val ad = getADForFull(VERSION, packageInfo.packageName)
init { init {
every { storagePluginManager.appPlugin } returns plugin every { storagePluginManager.backend } returns backend
} }
@Test @Test
@ -167,7 +168,7 @@ internal class FullBackupTest : BackupTest() {
every { settingsManager.isQuotaUnlimited() } returns false every { settingsManager.isQuotaUnlimited() } returns false
every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name
coEvery { plugin.getOutputStream(token, name) } throws IOException() coEvery { backend.save(handle) } throws IOException()
expectClearState() expectClearState()
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt)) assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt))
@ -184,7 +185,7 @@ internal class FullBackupTest : BackupTest() {
every { settingsManager.isQuotaUnlimited() } returns false every { settingsManager.isQuotaUnlimited() } returns false
every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name 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 { inputFactory.getInputStream(data) } returns inputStream
every { outputStream.write(ByteArray(1) { VERSION }) } throws IOException() every { outputStream.write(ByteArray(1) { VERSION }) } throws IOException()
expectClearState() expectClearState()
@ -240,7 +241,7 @@ internal class FullBackupTest : BackupTest() {
@Test @Test
fun `clearBackupData delegates to plugin`() = runBlocking { fun `clearBackupData delegates to plugin`() = runBlocking {
every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name 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) backup.clearBackupData(packageInfo, token, salt)
} }
@ -251,7 +252,7 @@ internal class FullBackupTest : BackupTest() {
expectInitializeOutputStream() expectInitializeOutputStream()
expectClearState() expectClearState()
every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name 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)) assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt))
assertTrue(backup.hasState()) assertTrue(backup.hasState())
@ -265,7 +266,7 @@ internal class FullBackupTest : BackupTest() {
expectInitializeOutputStream() expectInitializeOutputStream()
expectClearState() expectClearState()
every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name 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)) assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, data, 0, token, salt))
assertTrue(backup.hasState()) assertTrue(backup.hasState())
@ -336,7 +337,9 @@ internal class FullBackupTest : BackupTest() {
private fun expectInitializeOutputStream() { private fun expectInitializeOutputStream() {
every { crypto.getNameForPackage(salt, packageInfo.packageName) } returns name 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 every { outputStream.write(ByteArray(1) { VERSION }) } just Runs
} }

View file

@ -17,7 +17,6 @@ import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE
import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.header.getADForKV import com.stevesoltys.seedvault.header.getADForKV
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import io.mockk.CapturingSlot import io.mockk.CapturingSlot
@ -29,6 +28,7 @@ import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.Backend
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertTrue
@ -54,7 +54,7 @@ internal class KVBackupTest : BackupTest() {
) )
private val db = mockk<KVDb>() private val db = mockk<KVDb>()
private val plugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
private val packageName = packageInfo.packageName private val packageName = packageInfo.packageName
private val key = getRandomString(MAX_KEY_LENGTH_SIZE) private val key = getRandomString(MAX_KEY_LENGTH_SIZE)
private val dataValue = Random.nextBytes(23) private val dataValue = Random.nextBytes(23)
@ -62,7 +62,7 @@ internal class KVBackupTest : BackupTest() {
private val inputStream = ByteArrayInputStream(dbBytes) private val inputStream = ByteArrayInputStream(dbBytes)
init { init {
every { pluginManager.appPlugin } returns plugin every { pluginManager.backend } returns backend
} }
@Test @Test
@ -96,7 +96,7 @@ internal class KVBackupTest : BackupTest() {
@Test @Test
fun `non-incremental backup with data clears old data first`() = runBlocking { fun `non-incremental backup with data clears old data first`() = runBlocking {
singleRecordBackup(true) singleRecordBackup(true)
coEvery { plugin.removeData(token, name) } just Runs coEvery { backend.remove(handle) } just Runs
every { dbManager.deleteDb(packageName) } returns true every { dbManager.deleteDb(packageName) } returns true
assertEquals( assertEquals(
@ -112,7 +112,7 @@ internal class KVBackupTest : BackupTest() {
fun `ignoring exception when clearing data when non-incremental backup has data`() = fun `ignoring exception when clearing data when non-incremental backup has data`() =
runBlocking { runBlocking {
singleRecordBackup(true) singleRecordBackup(true)
coEvery { plugin.removeData(token, name) } throws IOException() coEvery { backend.remove(handle) } throws IOException()
assertEquals( assertEquals(
TRANSPORT_OK, TRANSPORT_OK,
@ -210,7 +210,7 @@ internal class KVBackupTest : BackupTest() {
every { db.vacuum() } just Runs every { db.vacuum() } just Runs
every { db.close() } 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.write(ByteArray(1) { VERSION }) } throws IOException()
every { outputStream.close() } just Runs every { outputStream.close() } just Runs
assertEquals(TRANSPORT_ERROR, backup.finishBackup()) assertEquals(TRANSPORT_ERROR, backup.finishBackup())
@ -230,7 +230,7 @@ internal class KVBackupTest : BackupTest() {
every { db.vacuum() } just Runs every { db.vacuum() } just Runs
every { db.close() } 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 every { outputStream.write(ByteArray(1) { VERSION }) } just Runs
val ad = getADForKV(VERSION, packageInfo.packageName) val ad = getADForKV(VERSION, packageInfo.packageName)
every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream
@ -264,7 +264,7 @@ internal class KVBackupTest : BackupTest() {
assertFalse(backup.hasState()) assertFalse(backup.hasState())
coVerify(exactly = 0) { 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.vacuum() } just Runs
every { db.close() } 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 every { outputStream.write(ByteArray(1) { VERSION }) } just Runs
val ad = getADForKV(VERSION, packageInfo.packageName) val ad = getADForKV(VERSION, packageInfo.packageName)
every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream every { crypto.newEncryptingStream(outputStream, ad) } returns encryptedOutputStream

View file

@ -17,7 +17,6 @@ import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.header.VersionHeader import com.stevesoltys.seedvault.header.VersionHeader
import com.stevesoltys.seedvault.header.getADForFull import com.stevesoltys.seedvault.header.getADForFull
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import io.mockk.CapturingSlot import io.mockk.CapturingSlot
import io.mockk.Runs import io.mockk.Runs
@ -26,6 +25,7 @@ import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.Backend
import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertFalse
@ -40,7 +40,7 @@ import kotlin.random.Random
internal class FullRestoreTest : RestoreTest() { internal class FullRestoreTest : RestoreTest() {
private val storagePluginManager: StoragePluginManager = mockk() private val storagePluginManager: StoragePluginManager = mockk()
private val plugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
private val legacyPlugin = mockk<LegacyStoragePlugin>() private val legacyPlugin = mockk<LegacyStoragePlugin>()
private val restore = FullRestore( private val restore = FullRestore(
pluginManager = storagePluginManager, pluginManager = storagePluginManager,
@ -55,7 +55,7 @@ internal class FullRestoreTest : RestoreTest() {
private val ad = getADForFull(VERSION, packageInfo.packageName) private val ad = getADForFull(VERSION, packageInfo.packageName)
init { init {
every { storagePluginManager.appPlugin } returns plugin every { storagePluginManager.backend } returns backend
} }
@Test @Test
@ -90,7 +90,7 @@ internal class FullRestoreTest : RestoreTest() {
fun `getting InputStream for package when getting first chunk throws`() = runBlocking { fun `getting InputStream for package when getting first chunk throws`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) restore.initializeState(VERSION, token, name, packageInfo)
coEvery { plugin.getInputStream(token, name) } throws IOException() coEvery { backend.load(handle) } throws IOException()
every { fileDescriptor.close() } just Runs every { fileDescriptor.close() } just Runs
assertEquals( assertEquals(
@ -103,7 +103,7 @@ internal class FullRestoreTest : RestoreTest() {
fun `reading version header when getting first chunk throws`() = runBlocking { fun `reading version header when getting first chunk throws`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) 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 { headerReader.readVersion(inputStream, VERSION) } throws IOException()
every { fileDescriptor.close() } just Runs every { fileDescriptor.close() } just Runs
@ -117,7 +117,7 @@ internal class FullRestoreTest : RestoreTest() {
fun `reading unsupported version when getting first chunk`() = runBlocking { fun `reading unsupported version when getting first chunk`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) restore.initializeState(VERSION, token, name, packageInfo)
coEvery { plugin.getInputStream(token, name) } returns inputStream coEvery { backend.load(handle) } returns inputStream
every { every {
headerReader.readVersion(inputStream, VERSION) headerReader.readVersion(inputStream, VERSION)
} throws UnsupportedVersionException(unsupportedVersion) } throws UnsupportedVersionException(unsupportedVersion)
@ -133,7 +133,7 @@ internal class FullRestoreTest : RestoreTest() {
fun `getting decrypted stream when getting first chunk throws`() = runBlocking { fun `getting decrypted stream when getting first chunk throws`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) 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 { headerReader.readVersion(inputStream, VERSION) } returns VERSION
every { crypto.newDecryptingStream(inputStream, ad) } throws IOException() every { crypto.newDecryptingStream(inputStream, ad) } throws IOException()
every { fileDescriptor.close() } just Runs every { fileDescriptor.close() } just Runs
@ -149,7 +149,7 @@ internal class FullRestoreTest : RestoreTest() {
runBlocking { runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) 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 { headerReader.readVersion(inputStream, VERSION) } returns VERSION
every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException() every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException()
every { fileDescriptor.close() } just Runs every { fileDescriptor.close() } just Runs
@ -197,7 +197,7 @@ internal class FullRestoreTest : RestoreTest() {
fun `unexpected version aborts with error`() = runBlocking { fun `unexpected version aborts with error`() = runBlocking {
restore.initializeState(Byte.MAX_VALUE, token, name, packageInfo) restore.initializeState(Byte.MAX_VALUE, token, name, packageInfo)
coEvery { plugin.getInputStream(token, name) } returns inputStream coEvery { backend.load(handle) } returns inputStream
every { every {
headerReader.readVersion(inputStream, Byte.MAX_VALUE) headerReader.readVersion(inputStream, Byte.MAX_VALUE)
} throws GeneralSecurityException() } throws GeneralSecurityException()
@ -215,7 +215,7 @@ internal class FullRestoreTest : RestoreTest() {
val decryptedInputStream = ByteArrayInputStream(encryptedBytes) val decryptedInputStream = ByteArrayInputStream(encryptedBytes)
restore.initializeState(VERSION, token, name, packageInfo) 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 { headerReader.readVersion(inputStream, VERSION) } returns VERSION
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream every { outputFactory.getOutputStream(fileDescriptor) } returns outputStream
@ -248,7 +248,7 @@ internal class FullRestoreTest : RestoreTest() {
} }
private fun initInputStream() { private fun initInputStream() {
coEvery { plugin.getInputStream(token, name) } returns inputStream coEvery { backend.load(handle) } returns inputStream
every { headerReader.readVersion(inputStream, VERSION) } returns VERSION every { headerReader.readVersion(inputStream, VERSION) } returns VERSION
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream every { crypto.newDecryptingStream(inputStream, ad) } returns decryptedInputStream
} }

View file

@ -16,7 +16,6 @@ import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.header.VersionHeader import com.stevesoltys.seedvault.header.VersionHeader
import com.stevesoltys.seedvault.header.getADForKV import com.stevesoltys.seedvault.header.getADForKV
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.transport.backup.KVDb import com.stevesoltys.seedvault.transport.backup.KVDb
import com.stevesoltys.seedvault.transport.backup.KvDbManager import com.stevesoltys.seedvault.transport.backup.KvDbManager
@ -29,6 +28,7 @@ import io.mockk.mockkStatic
import io.mockk.verify import io.mockk.verify
import io.mockk.verifyAll import io.mockk.verifyAll
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.Backend
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
@ -42,7 +42,7 @@ import kotlin.random.Random
internal class KVRestoreTest : RestoreTest() { internal class KVRestoreTest : RestoreTest() {
private val storagePluginManager: StoragePluginManager = mockk() private val storagePluginManager: StoragePluginManager = mockk()
private val plugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private val legacyPlugin = mockk<LegacyStoragePlugin>() private val legacyPlugin = mockk<LegacyStoragePlugin>()
private val dbManager = mockk<KvDbManager>() private val dbManager = mockk<KvDbManager>()
@ -74,7 +74,7 @@ internal class KVRestoreTest : RestoreTest() {
// for InputStream#readBytes() // for InputStream#readBytes()
mockkStatic("kotlin.io.ByteStreamsKt") mockkStatic("kotlin.io.ByteStreamsKt")
every { storagePluginManager.appPlugin } returns plugin every { storagePluginManager.backend } returns backend
} }
@Test @Test
@ -88,7 +88,7 @@ internal class KVRestoreTest : RestoreTest() {
fun `unexpected version aborts with error`() = runBlocking { fun `unexpected version aborts with error`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) restore.initializeState(VERSION, token, name, packageInfo)
coEvery { plugin.getInputStream(token, name) } returns inputStream coEvery { backend.load(handle) } returns inputStream
every { every {
headerReader.readVersion(inputStream, VERSION) headerReader.readVersion(inputStream, VERSION)
} throws UnsupportedVersionException(Byte.MAX_VALUE) } throws UnsupportedVersionException(Byte.MAX_VALUE)
@ -103,7 +103,7 @@ internal class KVRestoreTest : RestoreTest() {
fun `newDecryptingStream throws`() = runBlocking { fun `newDecryptingStream throws`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) 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 { headerReader.readVersion(inputStream, VERSION) } returns VERSION
every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException() every { crypto.newDecryptingStream(inputStream, ad) } throws GeneralSecurityException()
every { dbManager.deleteDb(packageInfo.packageName, true) } returns true every { dbManager.deleteDb(packageInfo.packageName, true) } returns true
@ -121,7 +121,7 @@ internal class KVRestoreTest : RestoreTest() {
fun `writeEntityHeader throws`() = runBlocking { fun `writeEntityHeader throws`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) 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 { headerReader.readVersion(inputStream, VERSION) } returns VERSION
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream
every { every {
@ -146,7 +146,7 @@ internal class KVRestoreTest : RestoreTest() {
fun `two records get restored`() = runBlocking { fun `two records get restored`() = runBlocking {
restore.initializeState(VERSION, token, name, packageInfo) 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 { headerReader.readVersion(inputStream, VERSION) } returns VERSION
every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream every { crypto.newDecryptingStream(inputStream, ad) } returns decryptInputStream
every { every {

View file

@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.MetadataReader import com.stevesoltys.seedvault.metadata.MetadataReader
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.plugins.EncryptedMetadata import com.stevesoltys.seedvault.plugins.EncryptedMetadata
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.getAvailableBackups
import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
@ -30,8 +30,10 @@ import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.Backend
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
@ -45,7 +47,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
private val notificationManager: BackupNotificationManager = mockk() private val notificationManager: BackupNotificationManager = mockk()
private val storagePluginManager: StoragePluginManager = mockk() private val storagePluginManager: StoragePluginManager = mockk()
private val plugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
private val kv = mockk<KVRestore>() private val kv = mockk<KVRestore>()
private val full = mockk<FullRestore>() private val full = mockk<FullRestore>()
private val metadataReader = mockk<MetadataReader>() private val metadataReader = mockk<MetadataReader>()
@ -78,14 +80,15 @@ internal class RestoreCoordinatorTest : TransportTest() {
metadata.packageMetadataMap[packageInfo2.packageName] = metadata.packageMetadataMap[packageInfo2.packageName] =
PackageMetadata(backupType = BackupType.FULL) PackageMetadata(backupType = BackupType.FULL)
every { storagePluginManager.appPlugin } returns plugin mockkStatic("com.stevesoltys.seedvault.plugins.BackendExtKt")
every { storagePluginManager.backend } returns backend
} }
@Test @Test
fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking { fun `getAvailableRestoreSets() builds set from plugin response`() = runBlocking {
val encryptedMetadata = EncryptedMetadata(token) { inputStream } val encryptedMetadata = EncryptedMetadata(token) { inputStream }
coEvery { plugin.getAvailableBackups() } returns sequenceOf( coEvery { backend.getAvailableBackups() } returns sequenceOf(
encryptedMetadata, encryptedMetadata,
EncryptedMetadata(token + 1) { inputStream } EncryptedMetadata(token + 1) { inputStream }
) )
@ -123,7 +126,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test @Test
fun `startRestore() fetches metadata if missing`() = runBlocking { fun `startRestore() fetches metadata if missing`() = runBlocking {
coEvery { plugin.getAvailableBackups() } returns sequenceOf( coEvery { backend.getAvailableBackups() } returns sequenceOf(
EncryptedMetadata(token) { inputStream }, EncryptedMetadata(token) { inputStream },
EncryptedMetadata(token + 1) { inputStream } EncryptedMetadata(token + 1) { inputStream }
) )
@ -136,7 +139,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test @Test
fun `startRestore() errors if metadata is not matching token`() = runBlocking { fun `startRestore() errors if metadata is not matching token`() = runBlocking {
coEvery { plugin.getAvailableBackups() } returns sequenceOf( coEvery { backend.getAvailableBackups() } returns sequenceOf(
EncryptedMetadata(token + 42) { inputStream } EncryptedMetadata(token + 42) { inputStream }
) )
every { metadataReader.readMetadata(inputStream, token + 42) } returns metadata every { metadataReader.readMetadata(inputStream, token + 42) } returns metadata

View file

@ -30,6 +30,7 @@ import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verifyOrder import io.mockk.verifyOrder
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.Backend
import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.fail import org.junit.jupiter.api.Assertions.fail
@ -58,7 +59,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
@Suppress("Deprecation") @Suppress("Deprecation")
private val legacyPlugin = mockk<LegacyStoragePlugin>() private val legacyPlugin = mockk<LegacyStoragePlugin>()
private val backupPlugin = mockk<StoragePlugin<*>>() private val backend = mockk<Backend>()
private val kvRestore = KVRestore( private val kvRestore = KVRestore(
pluginManager = storagePluginManager, pluginManager = storagePluginManager,
legacyPlugin = legacyPlugin, legacyPlugin = legacyPlugin,
@ -123,7 +124,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
private val key264 = key2.encodeBase64() private val key264 = key2.encodeBase64()
init { init {
every { storagePluginManager.appPlugin } returns backupPlugin every { storagePluginManager.backend } returns backend
} }
@Test @Test

View file

@ -14,9 +14,7 @@ import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED 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.StoragePluginManager
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
@ -30,6 +28,8 @@ import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import io.mockk.verifyAll import io.mockk.verifyAll
import kotlinx.coroutines.runBlocking 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 org.junit.jupiter.api.Test
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
@ -41,7 +41,7 @@ internal class ApkBackupManagerTest : TransportTest() {
private val apkBackup: ApkBackup = mockk() private val apkBackup: ApkBackup = mockk()
private val iconManager: IconManager = mockk() private val iconManager: IconManager = mockk()
private val storagePluginManager: StoragePluginManager = mockk() private val storagePluginManager: StoragePluginManager = mockk()
private val plugin: StoragePlugin<*> = mockk() private val backend: Backend = mockk()
private val nm: BackupNotificationManager = mockk() private val nm: BackupNotificationManager = mockk()
private val apkBackupManager = ApkBackupManager( private val apkBackupManager = ApkBackupManager(
@ -59,7 +59,7 @@ internal class ApkBackupManagerTest : TransportTest() {
private val packageMetadata: PackageMetadata = mockk() private val packageMetadata: PackageMetadata = mockk()
init { init {
every { storagePluginManager.appPlugin } returns plugin every { storagePluginManager.backend } returns backend
} }
@Test @Test
@ -258,7 +258,7 @@ internal class ApkBackupManagerTest : TransportTest() {
// final upload // final upload
every { settingsManager.getToken() } returns token every { settingsManager.getToken() } returns token
coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream coEvery { backend.save(LegacyAppBackupFile.Metadata(token)) } returns metadataOutputStream
every { every {
metadataManager.uploadMetadata(metadataOutputStream) metadataManager.uploadMetadata(metadataOutputStream)
} throws IOException() andThenThrows SecurityException() andThenJust Runs } throws IOException() andThenThrows SecurityException() andThenJust Runs
@ -277,7 +277,7 @@ internal class ApkBackupManagerTest : TransportTest() {
private suspend fun expectUploadIcons() { private suspend fun expectUploadIcons() {
every { settingsManager.getToken() } returns token every { settingsManager.getToken() } returns token
val stream = ByteArrayOutputStream() 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 every { iconManager.uploadIcons(token, stream) } just Runs
} }
@ -288,7 +288,7 @@ internal class ApkBackupManagerTest : TransportTest() {
private fun expectFinalUpload() { private fun expectFinalUpload() {
every { settingsManager.getToken() } returns token 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 { metadataManager.uploadMetadata(metadataOutputStream) } just Runs
every { metadataOutputStream.close() } just Runs every { metadataOutputStream.close() } just Runs
} }

View file

@ -1 +1 @@
org.slf4j.simpleLogger.defaultLogLevel=trace #org.slf4j.simpleLogger.defaultLogLevel=debug

View file

@ -6,8 +6,8 @@
package org.calyxos.seedvault.core.backends package org.calyxos.seedvault.core.backends
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import okio.BufferedSink import java.io.InputStream
import okio.BufferedSource import java.io.OutputStream
import kotlin.reflect.KClass import kotlin.reflect.KClass
public interface Backend { public interface Backend {
@ -25,9 +25,9 @@ public interface Backend {
*/ */
public suspend fun getFreeSpace(): Long? 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( public suspend fun list(
topLevelFolder: TopLevelFolder?, topLevelFolder: TopLevelFolder?,

View file

@ -56,8 +56,8 @@ public abstract class BackendTest {
assertNotNull(metadata) assertNotNull(metadata)
assertNotNull(snapshot) assertNotNull(snapshot)
assertArrayEquals(bytes1, plugin.load(metadata as FileHandle).readByteArray()) assertArrayEquals(bytes1, plugin.load(metadata as FileHandle).readAllBytes())
assertArrayEquals(bytes2, plugin.load(snapshot as FileHandle).readByteArray()) assertArrayEquals(bytes2, plugin.load(snapshot as FileHandle).readAllBytes())
val blobName = Random.nextBytes(32).toHexString() val blobName = Random.nextBytes(32).toHexString()
var blob: FileBackupFileType.Blob? = null var blob: FileBackupFileType.Blob? = null
@ -77,7 +77,7 @@ public abstract class BackendTest {
} }
} }
assertNotNull(blob) 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 // try listing with top-level folder, should find two files of FileBackupFileType in there
var numFiles = 0 var numFiles = 0

View file

@ -15,11 +15,6 @@ import android.provider.DocumentsContract.renameDocument
import androidx.core.database.getIntOrNull import androidx.core.database.getIntOrNull
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import io.github.oshai.kotlinlogging.KotlinLogging 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.Backend
import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT
import org.calyxos.seedvault.core.backends.Constants.FILE_BACKUP_METADATA 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.backends.TopLevelFolder
import org.calyxos.seedvault.core.getBackendContext import org.calyxos.seedvault.core.getBackendContext
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import kotlin.reflect.KClass import kotlin.reflect.KClass
internal const val AUTHORITY_STORAGE = "com.android.externalstorage.documents" internal const val AUTHORITY_STORAGE = "com.android.externalstorage.documents"
@ -84,14 +81,14 @@ public class SafBackend(
} else bytesAvailable } else bytesAvailable
} }
override suspend fun save(handle: FileHandle): BufferedSink { override suspend fun save(handle: FileHandle): OutputStream {
val file = cache.getFile(handle) 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) val file = cache.getFile(handle)
return file.getInputStream(context.contentResolver).source().buffer() return file.getInputStream(context.contentResolver)
} }
override suspend fun list( override suspend fun list(

View file

@ -26,9 +26,6 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody import okhttp3.RequestBody
import okio.BufferedSink 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.Backend
import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT
import org.calyxos.seedvault.core.backends.Constants.FILE_BACKUP_METADATA 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.LegacyAppBackupFile
import org.calyxos.seedvault.core.backends.TopLevelFolder import org.calyxos.seedvault.core.backends.TopLevelFolder
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.PipedInputStream import java.io.PipedInputStream
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -123,7 +122,7 @@ public class WebDavBackend(
return availableBytes return availableBytes
} }
override suspend fun save(handle: FileHandle): BufferedSink { override suspend fun save(handle: FileHandle): OutputStream {
val location = handle.toHttpUrl() val location = handle.toHttpUrl()
val davCollection = DavCollection(okHttpClient, location) val davCollection = DavCollection(okHttpClient, location)
davCollection.ensureFoldersExist(log, folders) davCollection.ensureFoldersExist(log, folders)
@ -152,10 +151,10 @@ public class WebDavBackend(
deferred.await() 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 location = handle.toHttpUrl()
val davCollection = DavCollection(okHttpClient, location) val davCollection = DavCollection(okHttpClient, location)
@ -167,7 +166,7 @@ public class WebDavBackend(
} }
log.debugLog { "load($location) = $response" } log.debugLog { "load($location) = $response" }
if (response.code / 100 != 2) throw IOException("HTTP error ${response.code}") 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( override suspend fun list(

View file

@ -56,13 +56,13 @@ public abstract class SafStoragePlugin(
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun getChunkOutputStream(chunkId: String): OutputStream { override suspend fun getChunkOutputStream(chunkId: String): OutputStream {
val fileHandle = FileBackupFileType.Blob(androidId, chunkId) val fileHandle = FileBackupFileType.Blob(androidId, chunkId)
return delegate.save(fileHandle).outputStream() return delegate.save(fileHandle)
} }
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun getBackupSnapshotOutputStream(timestamp: Long): OutputStream { override suspend fun getBackupSnapshotOutputStream(timestamp: Long): OutputStream {
val fileHandle = FileBackupFileType.Snapshot(androidId, timestamp) val fileHandle = FileBackupFileType.Snapshot(androidId, timestamp)
return delegate.save(fileHandle).outputStream() return delegate.save(fileHandle)
} }
/************************* Restore *******************************/ /************************* Restore *******************************/
@ -84,7 +84,7 @@ public abstract class SafStoragePlugin(
override suspend fun getBackupSnapshotInputStream(storedSnapshot: StoredSnapshot): InputStream { override suspend fun getBackupSnapshotInputStream(storedSnapshot: StoredSnapshot): InputStream {
val androidId = storedSnapshot.androidId val androidId = storedSnapshot.androidId
val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp) val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp)
return delegate.load(handle).inputStream() return delegate.load(handle)
} }
@Throws(IOException::class) @Throws(IOException::class)
@ -93,7 +93,7 @@ public abstract class SafStoragePlugin(
chunkId: String, chunkId: String,
): InputStream { ): InputStream {
val handle = FileBackupFileType.Blob(snapshot.androidId, chunkId) val handle = FileBackupFileType.Blob(snapshot.androidId, chunkId)
return delegate.load(handle).inputStream() return delegate.load(handle)
} }
/************************* Pruning *******************************/ /************************* Pruning *******************************/