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