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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -25,12 +25,13 @@ import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.DecryptionFailedException
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.MetadataReader
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.getAvailableBackups
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS
import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import org.calyxos.seedvault.core.backends.Backend
import java.io.IOException
/**
@ -67,13 +68,13 @@ internal class RestoreCoordinator(
private val metadataReader: MetadataReader,
) {
private val plugin: StoragePlugin<*> get() = pluginManager.appPlugin
private val backend: Backend get() = pluginManager.backend
private var state: RestoreCoordinatorState? = null
private var backupMetadata: BackupMetadata? = null
private val failedPackages = ArrayList<String>()
suspend fun getAvailableMetadata(): Map<Long, BackupMetadata>? {
val availableBackups = plugin.getAvailableBackups() ?: return null
val availableBackups = backend.getAvailableBackups() ?: return null
val metadataMap = HashMap<Long, BackupMetadata>()
for (encryptedMetadata in availableBackups) {
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.webdav.WebDavHandler
import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties
import com.stevesoltys.seedvault.plugins.webdav.WebDavStoragePlugin
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.storage.StorageBackupJobService
import com.stevesoltys.seedvault.transport.backup.BackupInitializer
@ -27,6 +26,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.backup.BackupJobService
import org.calyxos.seedvault.core.backends.Backend
import java.io.IOException
import java.util.concurrent.TimeUnit
@ -59,9 +59,9 @@ internal class BackupStorageViewModel(
onStorageLocationSet(safStorage.isUsb)
}
override fun onWebDavConfigSet(properties: WebDavProperties, plugin: WebDavStoragePlugin) {
override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) {
webdavHandler.save(properties)
webdavHandler.setPlugin(properties, plugin)
webdavHandler.setPlugin(properties, backend)
scheduleBackupWorkers()
onStorageLocationSet(isUsb = false)
}

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

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

View file

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

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

View file

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

View file

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

View file

@ -25,7 +25,6 @@ import com.stevesoltys.seedvault.metadata.ApkSplit
import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.restore.RestorableBackup
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
@ -44,6 +43,8 @@ import io.mockk.mockkStatic
import io.mockk.verifyOrder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
@ -67,7 +68,7 @@ internal class ApkRestoreTest : TransportTest() {
private val backupManager: IBackupManager = mockk()
private val backupStateManager: BackupStateManager = mockk()
private val storagePluginManager: StoragePluginManager = mockk()
private val storagePlugin: StoragePlugin<*> = mockk()
private val backend: Backend = mockk()
private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
private val apkInstaller: ApkInstaller = mockk()
@ -108,7 +109,7 @@ internal class ApkRestoreTest : TransportTest() {
// as we don't do strict signature checking, we can use a relaxed mock
packageInfo.signingInfo = mockk(relaxed = true)
every { storagePluginManager.appPlugin } returns storagePlugin
every { storagePluginManager.backend } returns backend
// related to starting/stopping service
every { strictContext.packageName } returns "org.foo.bar"
@ -128,8 +129,8 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false
every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
every { storagePlugin.providerPackageName } returns storageProviderPackageName
coEvery { backend.load(handle) } returns apkInputStream
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -151,7 +152,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
apkRestore.installResult.test {
@ -177,7 +178,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
val packageInfo: PackageInfo = mockk()
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
@ -202,9 +203,9 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false
every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
coEvery { backend.load(handle) } returns apkInputStream
every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -222,7 +223,7 @@ internal class ApkRestoreTest : TransportTest() {
coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
} throws SecurityException()
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -249,7 +250,7 @@ internal class ApkRestoreTest : TransportTest() {
coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
} returns installResult
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -285,7 +286,7 @@ internal class ApkRestoreTest : TransportTest() {
coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
} returns installResult
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -300,7 +301,7 @@ internal class ApkRestoreTest : TransportTest() {
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!!
every {
@ -329,7 +330,7 @@ internal class ApkRestoreTest : TransportTest() {
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!!
every { packageInfo.longVersionCode } returns packageMetadata.version!! - 1
@ -369,7 +370,7 @@ internal class ApkRestoreTest : TransportTest() {
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
every { packageInfo.signingInfo.getSignatures() } returns listOf("foobar")
@ -401,7 +402,7 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
cacheBaseApkAndGetInfo(tmpDir)
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
if (willFail) {
every {
@ -476,7 +477,7 @@ internal class ApkRestoreTest : TransportTest() {
every {
splitCompatChecker.isCompatible(deviceName, listOf(split1Name, split2Name))
} returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -502,9 +503,9 @@ internal class ApkRestoreTest : TransportTest() {
every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true
every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName
coEvery {
storagePlugin.getInputStream(token, suffixName)
backend.load(LegacyAppBackupFile.Blob(token, suffixName))
} returns ByteArrayInputStream(getRandomByteArray())
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -531,8 +532,10 @@ internal class ApkRestoreTest : TransportTest() {
every { splitCompatChecker.isCompatible(deviceName, listOf(splitName)) } returns true
every { crypto.getNameForApk(salt, packageName, splitName) } returns suffixName
coEvery { storagePlugin.getInputStream(token, suffixName) } throws IOException()
every { storagePlugin.providerPackageName } returns storageProviderPackageName
coEvery {
backend.load(LegacyAppBackupFile.Blob(token, suffixName))
} throws IOException()
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -573,10 +576,14 @@ internal class ApkRestoreTest : TransportTest() {
val suffixName1 = getRandomString()
val suffixName2 = getRandomString()
every { crypto.getNameForApk(salt, packageName, split1Name) } returns suffixName1
coEvery { storagePlugin.getInputStream(token, suffixName1) } returns split1InputStream
coEvery {
backend.load(LegacyAppBackupFile.Blob(token, suffixName1))
} returns split1InputStream
every { crypto.getNameForApk(salt, packageName, split2Name) } returns suffixName2
coEvery { storagePlugin.getInputStream(token, suffixName2) } returns split2InputStream
every { storagePlugin.providerPackageName } returns storageProviderPackageName
coEvery {
backend.load(LegacyAppBackupFile.Blob(token, suffixName2))
} returns split2InputStream
every { backend.providerPackageName } returns storageProviderPackageName
val resultMap = mapOf(
packageName to ApkInstallResult(
@ -602,7 +609,7 @@ internal class ApkRestoreTest : TransportTest() {
every { backupStateManager.isAutoRestoreEnabled } returns false
// set the storage provider package name to match our current package name,
// and ensure that the current package is therefore skipped.
every { storagePlugin.providerPackageName } returns packageName
every { backend.providerPackageName } returns packageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -627,7 +634,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -656,7 +663,7 @@ internal class ApkRestoreTest : TransportTest() {
every { installRestriction.isAllowedToInstallApks() } returns true
every { backupStateManager.isAutoRestoreEnabled } returns true
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
every { backupManager.setAutoRestore(false) } just Runs
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
// cache APK and get icon as well as app name
@ -680,7 +687,7 @@ internal class ApkRestoreTest : TransportTest() {
@Test
fun `no apks get installed when blocked by policy`() = runBlocking {
every { installRestriction.isAllowedToInstallApks() } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName
every { backend.providerPackageName } returns storageProviderPackageName
apkRestore.installResult.test {
awaitItem() // initial empty state
@ -703,7 +710,7 @@ internal class ApkRestoreTest : TransportTest() {
private fun cacheBaseApkAndGetInfo(tmpDir: Path) {
every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
coEvery { backend.load(handle) } returns apkInputStream
every { pm.getPackageArchiveInfo(any(), any<Int>()) } returns packageInfo
every { applicationInfo.loadIcon(pm) } returns icon
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.PackageMetadata
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import com.stevesoltys.seedvault.transport.backup.FullBackup
import com.stevesoltys.seedvault.transport.backup.InputFactory
@ -44,6 +42,8 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.runBlocking
import org.calyxos.seedvault.core.backends.Backend
import org.calyxos.seedvault.core.backends.LegacyAppBackupFile
import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.fail
@ -67,7 +67,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
@Suppress("Deprecation")
private val legacyPlugin = mockk<LegacyStoragePlugin>()
private val backupPlugin = mockk<StoragePlugin<*>>()
private val backend = mockk<Backend>()
private val kvBackup = KVBackup(
pluginManager = storagePluginManager,
settingsManager = settingsManager,
@ -132,7 +132,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
private val realName = cryptoImpl.getNameForPackage(salt, packageInfo.packageName)
init {
every { storagePluginManager.appPlugin } returns backupPlugin
every { storagePluginManager.backend } returns backend
}
@Test
@ -161,7 +161,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
apkBackup.backupApkIfNecessary(packageInfo, any())
} returns packageMetadata
coEvery {
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
backend.save(LegacyAppBackupFile.Metadata(token))
} returns metadataOutputStream
every {
metadataManager.onApkBackedUp(packageInfo, packageMetadata)
@ -179,7 +179,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
// upload DB
coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream
coEvery {
backend.save(LegacyAppBackupFile.Blob(token, realName))
} returns bOutputStream
// finish K/V backup
assertEquals(TRANSPORT_OK, backup.finishBackup())
@ -198,7 +200,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// restore finds the backed up key and writes the decrypted value
val backupDataOutput = mockk<BackupDataOutput>()
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray())
coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream
coEvery {
backend.load(LegacyAppBackupFile.Blob(token, name))
} returns rInputStream
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137
every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size
@ -237,7 +241,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns null
every { settingsManager.getToken() } returns token
coEvery {
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
backend.save(LegacyAppBackupFile.Metadata(token))
} returns metadataOutputStream
every {
metadataManager.onPackageBackedUp(
@ -252,7 +256,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
// upload DB
coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream
coEvery {
backend.save(LegacyAppBackupFile.Blob(token, realName))
} returns bOutputStream
// finish K/V backup
assertEquals(TRANSPORT_OK, backup.finishBackup())
@ -271,7 +277,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// restore finds the backed up key and writes the decrypted value
val backupDataOutput = mockk<BackupDataOutput>()
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray())
coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream
coEvery {
backend.load(LegacyAppBackupFile.Blob(token, name))
} returns rInputStream
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
every { backupDataOutput.writeEntityHeader(key, appData.size) } returns 1137
every { backupDataOutput.writeEntityData(appData, appData.size) } returns appData.size
@ -294,14 +302,16 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// return streams from plugin and app data
val bOutputStream = ByteArrayOutputStream()
val bInputStream = ByteArrayInputStream(appData)
coEvery { backupPlugin.getOutputStream(token, realName) } returns bOutputStream
coEvery {
backend.save(LegacyAppBackupFile.Blob(token, realName))
} returns bOutputStream
every { inputFactory.getInputStream(fileDescriptor) } returns bInputStream
every { settingsManager.isQuotaUnlimited() } returns false
coEvery { apkBackup.backupApkIfNecessary(packageInfo, any()) } returns packageMetadata
every { settingsManager.getToken() } returns token
every { metadataManager.salt } returns salt
coEvery {
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
backend.save(LegacyAppBackupFile.Metadata(token))
} returns metadataOutputStream
every { metadataManager.onApkBackedUp(packageInfo, packageMetadata) } just Runs
every {
@ -333,7 +343,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
// reverse the backup streams into restore input
val rInputStream = ByteArrayInputStream(bOutputStream.toByteArray())
val rOutputStream = ByteArrayOutputStream()
coEvery { backupPlugin.getInputStream(token, name) } returns rInputStream
coEvery {
backend.load(LegacyAppBackupFile.Blob(token, name))
} returns rInputStream
every { outputFactory.getOutputStream(fileDescriptor) } returns rOutputStream
// restore data

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

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

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
import androidx.annotation.VisibleForTesting
import okio.BufferedSink
import okio.BufferedSource
import java.io.InputStream
import java.io.OutputStream
import kotlin.reflect.KClass
public interface Backend {
@ -25,9 +25,9 @@ public interface Backend {
*/
public suspend fun getFreeSpace(): Long?
public suspend fun save(handle: FileHandle): BufferedSink
public suspend fun save(handle: FileHandle): OutputStream
public suspend fun load(handle: FileHandle): BufferedSource
public suspend fun load(handle: FileHandle): InputStream
public suspend fun list(
topLevelFolder: TopLevelFolder?,

View file

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

View file

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

View file

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

View file

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