Refactor Storage Plugin API
This commit is contained in:
parent
50066f0317
commit
3c5e4120c7
30 changed files with 219 additions and 268 deletions
|
@ -3,18 +3,14 @@ package com.stevesoltys.seedvault
|
||||||
import androidx.test.core.content.pm.PackageInfoBuilder
|
import androidx.test.core.content.pm.PackageInfoBuilder
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderBackupPlugin
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderFullRestorePlugin
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderKVRestorePlugin
|
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderStoragePlugin
|
||||||
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderRestorePlugin
|
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderLegacyPlugin
|
||||||
import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage
|
import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage
|
||||||
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
|
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
|
||||||
import com.stevesoltys.seedvault.plugins.saf.deleteContents
|
import com.stevesoltys.seedvault.plugins.saf.deleteContents
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -39,14 +35,10 @@ class PluginTest : KoinComponent {
|
||||||
private val mockedSettingsManager: SettingsManager = mockk()
|
private val mockedSettingsManager: SettingsManager = mockk()
|
||||||
private val storage = DocumentsStorage(context, mockedSettingsManager)
|
private val storage = DocumentsStorage(context, mockedSettingsManager)
|
||||||
|
|
||||||
private val backupPlugin: BackupPlugin = DocumentsProviderBackupPlugin(context, storage)
|
private val storagePlugin: StoragePlugin = DocumentsProviderStoragePlugin(context, storage)
|
||||||
|
@Suppress("Deprecation")
|
||||||
private val kvRestorePlugin: KVRestorePlugin =
|
private val legacyStoragePlugin: LegacyStoragePlugin =
|
||||||
DocumentsProviderKVRestorePlugin(context, storage)
|
DocumentsProviderLegacyPlugin(context, storage)
|
||||||
private val fullRestorePlugin: FullRestorePlugin =
|
|
||||||
DocumentsProviderFullRestorePlugin(context, storage)
|
|
||||||
private val restorePlugin: RestorePlugin =
|
|
||||||
DocumentsProviderRestorePlugin(context, storage, kvRestorePlugin, fullRestorePlugin)
|
|
||||||
|
|
||||||
private val token = System.currentTimeMillis() - 365L * 24L * 60L * 60L * 1000L
|
private val token = System.currentTimeMillis() - 365L * 24L * 60L * 60L * 1000L
|
||||||
private val packageInfo = PackageInfoBuilder.newBuilder().setPackageName("org.example").build()
|
private val packageInfo = PackageInfoBuilder.newBuilder().setPackageName("org.example").build()
|
||||||
|
@ -67,7 +59,7 @@ class PluginTest : KoinComponent {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testProviderPackageName() {
|
fun testProviderPackageName() {
|
||||||
assertNotNull(backupPlugin.providerPackageName)
|
assertNotNull(storagePlugin.providerPackageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,38 +72,38 @@ class PluginTest : KoinComponent {
|
||||||
@Test
|
@Test
|
||||||
fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) {
|
fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) {
|
||||||
// no backups available initially
|
// no backups available initially
|
||||||
assertEquals(0, backupPlugin.getAvailableBackups()?.toList()?.size)
|
assertEquals(0, storagePlugin.getAvailableBackups()?.toList()?.size)
|
||||||
val uri = settingsManager.getStorage()?.getDocumentFile(context)?.uri ?: error("no storage")
|
val uri = settingsManager.getStorage()?.getDocumentFile(context)?.uri ?: error("no storage")
|
||||||
assertFalse(backupPlugin.hasBackup(uri))
|
assertFalse(storagePlugin.hasBackup(uri))
|
||||||
|
|
||||||
// prepare returned tokens requested when initializing device
|
// prepare returned tokens requested when initializing device
|
||||||
every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1)
|
every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1)
|
||||||
|
|
||||||
// start new restore set and initialize device afterwards
|
// start new restore set and initialize device afterwards
|
||||||
backupPlugin.startNewRestoreSet(token)
|
storagePlugin.startNewRestoreSet(token)
|
||||||
backupPlugin.initializeDevice()
|
storagePlugin.initializeDevice()
|
||||||
|
|
||||||
// write metadata (needed for backup to be recognized)
|
// write metadata (needed for backup to be recognized)
|
||||||
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
|
storagePlugin.getOutputStream(token, FILE_BACKUP_METADATA)
|
||||||
.writeAndClose(getRandomByteArray())
|
.writeAndClose(getRandomByteArray())
|
||||||
|
|
||||||
// one backup available now
|
// one backup available now
|
||||||
assertEquals(1, backupPlugin.getAvailableBackups()?.toList()?.size)
|
assertEquals(1, storagePlugin.getAvailableBackups()?.toList()?.size)
|
||||||
assertTrue(backupPlugin.hasBackup(uri))
|
assertTrue(storagePlugin.hasBackup(uri))
|
||||||
|
|
||||||
// initializing again (with another restore set) does add a restore set
|
// initializing again (with another restore set) does add a restore set
|
||||||
backupPlugin.startNewRestoreSet(token + 1)
|
storagePlugin.startNewRestoreSet(token + 1)
|
||||||
backupPlugin.initializeDevice()
|
storagePlugin.initializeDevice()
|
||||||
backupPlugin.getOutputStream(token + 1, FILE_BACKUP_METADATA)
|
storagePlugin.getOutputStream(token + 1, FILE_BACKUP_METADATA)
|
||||||
.writeAndClose(getRandomByteArray())
|
.writeAndClose(getRandomByteArray())
|
||||||
assertEquals(2, backupPlugin.getAvailableBackups()?.toList()?.size)
|
assertEquals(2, storagePlugin.getAvailableBackups()?.toList()?.size)
|
||||||
assertTrue(backupPlugin.hasBackup(uri))
|
assertTrue(storagePlugin.hasBackup(uri))
|
||||||
|
|
||||||
// initializing again (without new restore set) doesn't change number of restore sets
|
// initializing again (without new restore set) doesn't change number of restore sets
|
||||||
backupPlugin.initializeDevice()
|
storagePlugin.initializeDevice()
|
||||||
backupPlugin.getOutputStream(token + 1, FILE_BACKUP_METADATA)
|
storagePlugin.getOutputStream(token + 1, FILE_BACKUP_METADATA)
|
||||||
.writeAndClose(getRandomByteArray())
|
.writeAndClose(getRandomByteArray())
|
||||||
assertEquals(2, backupPlugin.getAvailableBackups()?.toList()?.size)
|
assertEquals(2, storagePlugin.getAvailableBackups()?.toList()?.size)
|
||||||
|
|
||||||
// ensure that the new backup dir exist
|
// ensure that the new backup dir exist
|
||||||
assertTrue(storage.currentSetDir!!.exists())
|
assertTrue(storage.currentSetDir!!.exists())
|
||||||
|
@ -121,15 +113,15 @@ class PluginTest : KoinComponent {
|
||||||
fun testMetadataWriteRead() = runBlocking(Dispatchers.IO) {
|
fun testMetadataWriteRead() = runBlocking(Dispatchers.IO) {
|
||||||
every { mockedSettingsManager.getToken() } returns token
|
every { mockedSettingsManager.getToken() } returns token
|
||||||
|
|
||||||
backupPlugin.startNewRestoreSet(token)
|
storagePlugin.startNewRestoreSet(token)
|
||||||
backupPlugin.initializeDevice()
|
storagePlugin.initializeDevice()
|
||||||
|
|
||||||
// write metadata
|
// write metadata
|
||||||
val metadata = getRandomByteArray()
|
val metadata = getRandomByteArray()
|
||||||
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA).writeAndClose(metadata)
|
storagePlugin.getOutputStream(token, FILE_BACKUP_METADATA).writeAndClose(metadata)
|
||||||
|
|
||||||
// get available backups, expect only one with our token and no error
|
// get available backups, expect only one with our token and no error
|
||||||
var availableBackups = backupPlugin.getAvailableBackups()?.toList()
|
var availableBackups = storagePlugin.getAvailableBackups()?.toList()
|
||||||
check(availableBackups != null)
|
check(availableBackups != null)
|
||||||
assertEquals(1, availableBackups.size)
|
assertEquals(1, availableBackups.size)
|
||||||
assertEquals(token, availableBackups[0].token)
|
assertEquals(token, availableBackups[0].token)
|
||||||
|
@ -138,9 +130,9 @@ class PluginTest : KoinComponent {
|
||||||
assertReadEquals(metadata, availableBackups[0].inputStreamRetriever())
|
assertReadEquals(metadata, availableBackups[0].inputStreamRetriever())
|
||||||
|
|
||||||
// initializing again (without changing storage) keeps restore set with same token
|
// initializing again (without changing storage) keeps restore set with same token
|
||||||
backupPlugin.initializeDevice()
|
storagePlugin.initializeDevice()
|
||||||
backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA).writeAndClose(metadata)
|
storagePlugin.getOutputStream(token, FILE_BACKUP_METADATA).writeAndClose(metadata)
|
||||||
availableBackups = backupPlugin.getAvailableBackups()?.toList()
|
availableBackups = storagePlugin.getAvailableBackups()?.toList()
|
||||||
check(availableBackups != null)
|
check(availableBackups != null)
|
||||||
assertEquals(1, availableBackups.size)
|
assertEquals(1, availableBackups.size)
|
||||||
assertEquals(token, availableBackups[0].token)
|
assertEquals(token, availableBackups[0].token)
|
||||||
|
@ -157,22 +149,25 @@ class PluginTest : KoinComponent {
|
||||||
|
|
||||||
// write random bytes as APK
|
// write random bytes as APK
|
||||||
val apk1 = getRandomByteArray(1337 * 1024)
|
val apk1 = getRandomByteArray(1337 * 1024)
|
||||||
backupPlugin.getOutputStream(token, "${packageInfo.packageName}.apk").writeAndClose(apk1)
|
storagePlugin.getOutputStream(token, "${packageInfo.packageName}.apk").writeAndClose(apk1)
|
||||||
|
|
||||||
// assert that read APK bytes match what was written
|
// assert that read APK bytes match what was written
|
||||||
assertReadEquals(apk1, restorePlugin.getApkInputStream(token, packageInfo.packageName, ""))
|
assertReadEquals(
|
||||||
|
apk1,
|
||||||
|
legacyStoragePlugin.getApkInputStream(token, packageInfo.packageName, "")
|
||||||
|
)
|
||||||
|
|
||||||
// write random bytes as another APK
|
// write random bytes as another APK
|
||||||
val suffix2 = getRandomBase64(23)
|
val suffix2 = getRandomBase64(23)
|
||||||
val apk2 = getRandomByteArray(23 * 1024 * 1024)
|
val apk2 = getRandomByteArray(23 * 1024 * 1024)
|
||||||
|
|
||||||
backupPlugin.getOutputStream(token, "${packageInfo2.packageName}$suffix2.apk")
|
storagePlugin.getOutputStream(token, "${packageInfo2.packageName}$suffix2.apk")
|
||||||
.writeAndClose(apk2)
|
.writeAndClose(apk2)
|
||||||
|
|
||||||
// assert that read APK bytes match what was written
|
// assert that read APK bytes match what was written
|
||||||
assertReadEquals(
|
assertReadEquals(
|
||||||
apk2,
|
apk2,
|
||||||
restorePlugin.getApkInputStream(token, packageInfo2.packageName, suffix2)
|
legacyStoragePlugin.getApkInputStream(token, packageInfo2.packageName, suffix2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,41 +180,41 @@ class PluginTest : KoinComponent {
|
||||||
val name2 = getRandomBase64()
|
val name2 = getRandomBase64()
|
||||||
|
|
||||||
// no data available initially
|
// no data available initially
|
||||||
assertFalse(backupPlugin.hasData(token, name1))
|
assertFalse(storagePlugin.hasData(token, name1))
|
||||||
assertFalse(backupPlugin.hasData(token, name2))
|
assertFalse(storagePlugin.hasData(token, name2))
|
||||||
|
|
||||||
// write full backup data
|
// write full backup data
|
||||||
val data = getRandomByteArray(5 * 1024 * 1024)
|
val data = getRandomByteArray(5 * 1024 * 1024)
|
||||||
backupPlugin.getOutputStream(token, name1).writeAndClose(data)
|
storagePlugin.getOutputStream(token, name1).writeAndClose(data)
|
||||||
|
|
||||||
// data is available now, but only this token
|
// data is available now, but only this token
|
||||||
assertTrue(backupPlugin.hasData(token, name1))
|
assertTrue(storagePlugin.hasData(token, name1))
|
||||||
assertFalse(backupPlugin.hasData(token + 1, name1))
|
assertFalse(storagePlugin.hasData(token + 1, name1))
|
||||||
|
|
||||||
// restore data matches backed up data
|
// restore data matches backed up data
|
||||||
assertReadEquals(data, backupPlugin.getInputStream(token, name1))
|
assertReadEquals(data, storagePlugin.getInputStream(token, name1))
|
||||||
|
|
||||||
// write and check data for second package
|
// write and check data for second package
|
||||||
val data2 = getRandomByteArray(5 * 1024 * 1024)
|
val data2 = getRandomByteArray(5 * 1024 * 1024)
|
||||||
backupPlugin.getOutputStream(token, name2).writeAndClose(data2)
|
storagePlugin.getOutputStream(token, name2).writeAndClose(data2)
|
||||||
assertTrue(backupPlugin.hasData(token, name2))
|
assertTrue(storagePlugin.hasData(token, name2))
|
||||||
assertReadEquals(data2, backupPlugin.getInputStream(token, name2))
|
assertReadEquals(data2, storagePlugin.getInputStream(token, name2))
|
||||||
|
|
||||||
// remove data of first package again and ensure that no more data is found
|
// remove data of first package again and ensure that no more data is found
|
||||||
backupPlugin.removeData(token, name1)
|
storagePlugin.removeData(token, name1)
|
||||||
assertFalse(backupPlugin.hasData(token, name1))
|
assertFalse(storagePlugin.hasData(token, name1))
|
||||||
|
|
||||||
// second package is still there
|
// second package is still there
|
||||||
assertTrue(backupPlugin.hasData(token, name2))
|
assertTrue(storagePlugin.hasData(token, name2))
|
||||||
|
|
||||||
// ensure that it gets deleted as well
|
// ensure that it gets deleted as well
|
||||||
backupPlugin.removeData(token, name2)
|
storagePlugin.removeData(token, name2)
|
||||||
assertFalse(backupPlugin.hasData(token, name2))
|
assertFalse(storagePlugin.hasData(token, name2))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initStorage(token: Long) = runBlocking {
|
private fun initStorage(token: Long) = runBlocking {
|
||||||
every { mockedSettingsManager.getToken() } returns token
|
every { mockedSettingsManager.getToken() } returns token
|
||||||
backupPlugin.initializeDevice()
|
storagePlugin.initializeDevice()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package com.stevesoltys.seedvault.transport.restore
|
package com.stevesoltys.seedvault.plugins
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
interface KVRestorePlugin {
|
@Deprecated("Only for old v0 backup format")
|
||||||
|
interface LegacyStoragePlugin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if there is data stored for the given package.
|
* Return true if there is data stored for the given package.
|
||||||
|
@ -36,4 +37,19 @@ interface KVRestorePlugin {
|
||||||
key: String
|
key: String
|
||||||
): InputStream
|
): InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if there is data stored for the given package.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
suspend fun hasDataForFullPackage(token: Long, packageInfo: PackageInfo): Boolean
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
suspend fun getInputStreamForPackage(token: Long, packageInfo: PackageInfo): InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an [InputStream] for the given token, for reading an APK that is to be restored.
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
suspend fun getApkInputStream(token: Long, packageName: String, suffix: String): InputStream
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.stevesoltys.seedvault.transport.backup
|
package com.stevesoltys.seedvault.plugins
|
||||||
|
|
||||||
import android.app.backup.RestoreSet
|
import android.app.backup.RestoreSet
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -7,7 +7,7 @@ import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
interface BackupPlugin {
|
interface StoragePlugin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new [RestoreSet] with the given token.
|
* Start a new [RestoreSet] with the given token.
|
|
@ -1,33 +0,0 @@
|
||||||
package com.stevesoltys.seedvault.plugins.saf
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext", "Deprecation")
|
|
||||||
@Deprecated("Use only for v0 restore")
|
|
||||||
internal class DocumentsProviderFullRestorePlugin(
|
|
||||||
private val context: Context,
|
|
||||||
private val documentsStorage: DocumentsStorage
|
|
||||||
) : FullRestorePlugin {
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override suspend fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean {
|
|
||||||
val backupDir = documentsStorage.getFullBackupDir(token) ?: return false
|
|
||||||
return backupDir.findFileBlocking(context, packageInfo.packageName) != null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override suspend fun getInputStreamForPackage(
|
|
||||||
token: Long,
|
|
||||||
packageInfo: PackageInfo
|
|
||||||
): InputStream {
|
|
||||||
val backupDir = documentsStorage.getFullBackupDir(token) ?: throw IOException()
|
|
||||||
val packageFile =
|
|
||||||
backupDir.findFileBlocking(context, packageInfo.packageName) ?: throw IOException()
|
|
||||||
return documentsStorage.getInputStream(packageFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,17 +2,19 @@ package com.stevesoltys.seedvault.plugins.saf
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext", "Deprecation")
|
@WorkerThread
|
||||||
@Deprecated("Use only for v0 restore")
|
@Suppress("BlockingMethodInNonBlockingContext", "Deprecation") // all methods do I/O
|
||||||
internal class DocumentsProviderKVRestorePlugin(
|
internal class DocumentsProviderLegacyPlugin(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val storage: DocumentsStorage
|
private val storage: DocumentsStorage
|
||||||
) : KVRestorePlugin {
|
) : LegacyStoragePlugin {
|
||||||
|
|
||||||
private var packageDir: DocumentFile? = null
|
private var packageDir: DocumentFile? = null
|
||||||
private var packageChildren: List<DocumentFile>? = null
|
private var packageChildren: List<DocumentFile>? = null
|
||||||
|
@ -59,4 +61,33 @@ internal class DocumentsProviderKVRestorePlugin(
|
||||||
return storage.getInputStream(keyFile)
|
return storage.getInputStream(keyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override suspend fun hasDataForFullPackage(token: Long, packageInfo: PackageInfo): Boolean {
|
||||||
|
val backupDir = storage.getFullBackupDir(token) ?: return false
|
||||||
|
return backupDir.findFileBlocking(context, packageInfo.packageName) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override suspend fun getInputStreamForPackage(
|
||||||
|
token: Long,
|
||||||
|
packageInfo: PackageInfo
|
||||||
|
): InputStream {
|
||||||
|
val backupDir = storage.getFullBackupDir(token) ?: throw IOException()
|
||||||
|
val packageFile =
|
||||||
|
backupDir.findFileBlocking(context, packageInfo.packageName) ?: throw IOException()
|
||||||
|
return storage.getInputStream(packageFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override suspend fun getApkInputStream(
|
||||||
|
token: Long,
|
||||||
|
packageName: String,
|
||||||
|
suffix: String
|
||||||
|
): InputStream {
|
||||||
|
val setDir = storage.getSetDir(token) ?: throw IOException()
|
||||||
|
val file = setDir.findFileBlocking(context, "$packageName$suffix.apk")
|
||||||
|
?: throw FileNotFoundException()
|
||||||
|
return storage.getInputStream(file)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,18 +1,14 @@
|
||||||
package com.stevesoltys.seedvault.plugins.saf
|
package com.stevesoltys.seedvault.plugins.saf
|
||||||
|
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val documentsProviderModule = module {
|
val documentsProviderModule = module {
|
||||||
single { DocumentsStorage(androidContext(), get()) }
|
single { DocumentsStorage(androidContext(), get()) }
|
||||||
|
|
||||||
single<BackupPlugin> { DocumentsProviderBackupPlugin(androidContext(), get()) }
|
single<StoragePlugin> { DocumentsProviderStoragePlugin(androidContext(), get()) }
|
||||||
|
@Suppress("Deprecation")
|
||||||
single<KVRestorePlugin> { DocumentsProviderKVRestorePlugin(androidContext(), get()) }
|
single<LegacyStoragePlugin> { DocumentsProviderLegacyPlugin(androidContext(), get()) }
|
||||||
single<FullRestorePlugin> { DocumentsProviderFullRestorePlugin(androidContext(), get()) }
|
|
||||||
single<RestorePlugin> { DocumentsProviderRestorePlugin(androidContext(), get(), get(), get()) }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
package com.stevesoltys.seedvault.plugins.saf
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext") // all methods do I/O
|
|
||||||
internal class DocumentsProviderRestorePlugin(
|
|
||||||
private val context: Context,
|
|
||||||
private val storage: DocumentsStorage,
|
|
||||||
override val kvRestorePlugin: KVRestorePlugin,
|
|
||||||
override val fullRestorePlugin: FullRestorePlugin
|
|
||||||
) : RestorePlugin {
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override suspend fun getApkInputStream(
|
|
||||||
token: Long,
|
|
||||||
packageName: String,
|
|
||||||
suffix: String
|
|
||||||
): InputStream {
|
|
||||||
val setDir = storage.getSetDir(token) ?: throw IOException()
|
|
||||||
val file = setDir.findFileBlocking(context, "$packageName$suffix.apk")
|
|
||||||
?: throw FileNotFoundException()
|
|
||||||
return storage.getInputStream(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -5,20 +5,20 @@ import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.EncryptedMetadata
|
||||||
import com.stevesoltys.seedvault.transport.backup.EncryptedMetadata
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
private val TAG = DocumentsProviderBackupPlugin::class.java.simpleName
|
private val TAG = DocumentsProviderStoragePlugin::class.java.simpleName
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class DocumentsProviderBackupPlugin(
|
internal class DocumentsProviderStoragePlugin(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val storage: DocumentsStorage
|
private val storage: DocumentsStorage
|
||||||
) : BackupPlugin {
|
) : StoragePlugin {
|
||||||
|
|
||||||
private val packageManager: PackageManager = context.packageManager
|
private val packageManager: PackageManager = context.packageManager
|
||||||
|
|
|
@ -8,16 +8,16 @@ import android.util.Log
|
||||||
import com.stevesoltys.seedvault.crypto.Crypto
|
import com.stevesoltys.seedvault.crypto.Crypto
|
||||||
import com.stevesoltys.seedvault.metadata.ApkSplit
|
import com.stevesoltys.seedvault.metadata.ApkSplit
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||||
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.restore.RestorableBackup
|
import com.stevesoltys.seedvault.restore.RestorableBackup
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.backup.copyStreamsAndGetHash
|
import com.stevesoltys.seedvault.transport.backup.copyStreamsAndGetHash
|
||||||
import com.stevesoltys.seedvault.transport.backup.getSignatures
|
import com.stevesoltys.seedvault.transport.backup.getSignatures
|
||||||
import com.stevesoltys.seedvault.transport.backup.isSystemApp
|
import com.stevesoltys.seedvault.transport.backup.isSystemApp
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.flow.FlowCollector
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
@ -28,8 +28,9 @@ private val TAG = ApkRestore::class.java.simpleName
|
||||||
|
|
||||||
internal class ApkRestore(
|
internal class ApkRestore(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val backupPlugin: BackupPlugin,
|
private val storagePlugin: StoragePlugin,
|
||||||
private val restorePlugin: RestorePlugin,
|
@Suppress("Deprecation")
|
||||||
|
private val legacyStoragePlugin: LegacyStoragePlugin,
|
||||||
private val crypto: Crypto,
|
private val crypto: Crypto,
|
||||||
private val splitCompatChecker: ApkSplitCompatibilityChecker,
|
private val splitCompatChecker: ApkSplitCompatibilityChecker,
|
||||||
private val apkInstaller: ApkInstaller
|
private val apkInstaller: ApkInstaller
|
||||||
|
@ -157,7 +158,7 @@ internal class ApkRestore(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves APK splits from [RestorePlugin] and caches them locally.
|
* Retrieves APK splits from [StoragePlugin] and caches them locally.
|
||||||
*
|
*
|
||||||
* @throws SecurityException if a split has an unexpected SHA-256 hash.
|
* @throws SecurityException if a split has an unexpected SHA-256 hash.
|
||||||
* @return a list of all APKs that need to be installed
|
* @return a list of all APKs that need to be installed
|
||||||
|
@ -195,7 +196,7 @@ internal class ApkRestore(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an APK from the [RestorePlugin] and caches it locally
|
* Retrieves an APK from the [StoragePlugin] and caches it locally
|
||||||
* while calculating its SHA-256 hash.
|
* while calculating its SHA-256 hash.
|
||||||
*
|
*
|
||||||
* @return a [Pair] of the cached [File] and SHA-256 hash.
|
* @return a [Pair] of the cached [File] and SHA-256 hash.
|
||||||
|
@ -214,10 +215,10 @@ internal class ApkRestore(
|
||||||
// copy APK to cache file and calculate SHA-256 hash while we are at it
|
// copy APK to cache file and calculate SHA-256 hash while we are at it
|
||||||
val inputStream = if (version == 0.toByte()) {
|
val inputStream = if (version == 0.toByte()) {
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
restorePlugin.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)
|
||||||
backupPlugin.getInputStream(token, name)
|
storagePlugin.getInputStream(token, name)
|
||||||
}
|
}
|
||||||
val sha256 = copyStreamsAndGetHash(inputStream, cachedApk.outputStream())
|
val sha256 = copyStreamsAndGetHash(inputStream, cachedApk.outputStream())
|
||||||
return Pair(cachedApk, sha256)
|
return Pair(cachedApk, sha256)
|
||||||
|
|
|
@ -29,6 +29,7 @@ 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.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.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
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
|
@ -64,7 +65,7 @@ private class CoordinatorState(
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class BackupCoordinator(
|
internal class BackupCoordinator(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: StoragePlugin,
|
||||||
private val kv: KVBackup,
|
private val kv: KVBackup,
|
||||||
private val full: FullBackup,
|
private val full: FullBackup,
|
||||||
private val apkBackup: ApkBackup,
|
private val apkBackup: ApkBackup,
|
||||||
|
@ -508,7 +509,7 @@ internal class BackupCoordinator(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun BackupPlugin.getMetadataOutputStream(token: Long? = null): OutputStream {
|
private suspend fun StoragePlugin.getMetadataOutputStream(token: Long? = null): OutputStream {
|
||||||
val t = token ?: settingsManager.getToken() ?: throw IOException("no current token")
|
val t = token ?: settingsManager.getToken() ?: throw IOException("no current token")
|
||||||
return getOutputStream(t, FILE_BACKUP_METADATA)
|
return getOutputStream(t, FILE_BACKUP_METADATA)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.util.Log
|
||||||
import com.stevesoltys.seedvault.crypto.Crypto
|
import com.stevesoltys.seedvault.crypto.Crypto
|
||||||
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.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import libcore.io.IoUtils.closeQuietly
|
import libcore.io.IoUtils.closeQuietly
|
||||||
import java.io.EOFException
|
import java.io.EOFException
|
||||||
|
@ -38,7 +39,7 @@ private val TAG = FullBackup::class.java.simpleName
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class FullBackup(
|
internal class FullBackup(
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: StoragePlugin,
|
||||||
private val settingsManager: SettingsManager,
|
private val settingsManager: SettingsManager,
|
||||||
private val inputFactory: InputFactory,
|
private val inputFactory: InputFactory,
|
||||||
private val crypto: Crypto
|
private val crypto: Crypto
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.util.Log
|
||||||
import com.stevesoltys.seedvault.crypto.Crypto
|
import com.stevesoltys.seedvault.crypto.Crypto
|
||||||
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.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
|
@ -31,7 +32,7 @@ private val TAG = KVBackup::class.java.simpleName
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class KVBackup(
|
internal class KVBackup(
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: StoragePlugin,
|
||||||
private val settingsManager: SettingsManager,
|
private val settingsManager: SettingsManager,
|
||||||
private val inputFactory: InputFactory,
|
private val inputFactory: InputFactory,
|
||||||
private val crypto: Crypto,
|
private val crypto: Crypto,
|
||||||
|
|
|
@ -12,7 +12,8 @@ import com.stevesoltys.seedvault.header.HeaderReader
|
||||||
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
|
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
|
||||||
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||||
import com.stevesoltys.seedvault.header.getADForFull
|
import com.stevesoltys.seedvault.header.getADForFull
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import libcore.io.IoUtils.closeQuietly
|
import libcore.io.IoUtils.closeQuietly
|
||||||
import java.io.EOFException
|
import java.io.EOFException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -33,8 +34,9 @@ private val TAG = FullRestore::class.java.simpleName
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class FullRestore(
|
internal class FullRestore(
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: StoragePlugin,
|
||||||
private val legacyPlugin: FullRestorePlugin,
|
@Suppress("Deprecation")
|
||||||
|
private val legacyPlugin: LegacyStoragePlugin,
|
||||||
private val outputFactory: OutputFactory,
|
private val outputFactory: OutputFactory,
|
||||||
private val headerReader: HeaderReader,
|
private val headerReader: HeaderReader,
|
||||||
private val crypto: Crypto
|
private val crypto: Crypto
|
||||||
|
@ -52,7 +54,7 @@ internal class FullRestore(
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@Deprecated("Use BackupPlugin#hasData() instead")
|
@Deprecated("Use BackupPlugin#hasData() instead")
|
||||||
suspend fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean {
|
suspend fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean {
|
||||||
return legacyPlugin.hasDataForPackage(token, packageInfo)
|
return legacyPlugin.hasDataForFullPackage(token, packageInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package com.stevesoltys.seedvault.transport.restore
|
|
||||||
|
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
interface FullRestorePlugin {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if there is data stored for the given package.
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
suspend fun hasDataForPackage(token: Long, packageInfo: PackageInfo): Boolean
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
suspend fun getInputStreamForPackage(token: Long, packageInfo: PackageInfo): InputStream
|
|
||||||
|
|
||||||
}
|
|
|
@ -15,7 +15,8 @@ import com.stevesoltys.seedvault.header.HeaderReader
|
||||||
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||||
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.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
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
|
||||||
|
@ -41,8 +42,9 @@ private val TAG = KVRestore::class.java.simpleName
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class KVRestore(
|
internal class KVRestore(
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: StoragePlugin,
|
||||||
private val legacyPlugin: KVRestorePlugin,
|
@Suppress("Deprecation")
|
||||||
|
private val legacyPlugin: LegacyStoragePlugin,
|
||||||
private val outputFactory: OutputFactory,
|
private val outputFactory: OutputFactory,
|
||||||
private val headerReader: HeaderReader,
|
private val headerReader: HeaderReader,
|
||||||
private val crypto: Crypto,
|
private val crypto: Crypto,
|
||||||
|
|
|
@ -21,7 +21,7 @@ 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.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ internal class RestoreCoordinator(
|
||||||
private val settingsManager: SettingsManager,
|
private val settingsManager: SettingsManager,
|
||||||
private val metadataManager: MetadataManager,
|
private val metadataManager: MetadataManager,
|
||||||
private val notificationManager: BackupNotificationManager,
|
private val notificationManager: BackupNotificationManager,
|
||||||
private val plugin: BackupPlugin,
|
private val plugin: StoragePlugin,
|
||||||
private val kv: KVRestore,
|
private val kv: KVRestore,
|
||||||
private val full: FullRestore,
|
private val full: FullRestore,
|
||||||
private val metadataReader: MetadataReader
|
private val metadataReader: MetadataReader
|
||||||
|
|
|
@ -5,8 +5,8 @@ import org.koin.dsl.module
|
||||||
|
|
||||||
val restoreModule = module {
|
val restoreModule = module {
|
||||||
single { OutputFactory() }
|
single { OutputFactory() }
|
||||||
single { KVRestore(get(), get<RestorePlugin>().kvRestorePlugin, get(), get(), get(), get()) }
|
single { KVRestore(get(), get(), get(), get(), get(), get()) }
|
||||||
single { FullRestore(get(), get<RestorePlugin>().fullRestorePlugin, get(), get(), get()) }
|
single { FullRestore(get(), get(), get(), get(), get()) }
|
||||||
single {
|
single {
|
||||||
RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get())
|
RestoreCoordinator(androidContext(), get(), get(), get(), get(), get(), get(), get(), get())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package com.stevesoltys.seedvault.transport.restore
|
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
interface RestorePlugin {
|
|
||||||
|
|
||||||
val kvRestorePlugin: KVRestorePlugin
|
|
||||||
|
|
||||||
val fullRestorePlugin: FullRestorePlugin
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an [InputStream] for the given token, for reading an APK that is to be restored.
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
@Deprecated("Use only for v0 restores")
|
|
||||||
suspend fun getApkInputStream(token: Long, packageName: String, suffix: String): InputStream
|
|
||||||
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.plugins.saf.DIRECTORY_ROOT
|
import com.stevesoltys.seedvault.plugins.saf.DIRECTORY_ROOT
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -16,7 +16,7 @@ private val TAG = RestoreStorageViewModel::class.java.simpleName
|
||||||
|
|
||||||
internal class RestoreStorageViewModel(
|
internal class RestoreStorageViewModel(
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
private val backupPlugin: BackupPlugin,
|
private val storagePlugin: StoragePlugin,
|
||||||
settingsManager: SettingsManager
|
settingsManager: SettingsManager
|
||||||
) : StorageViewModel(app, settingsManager) {
|
) : StorageViewModel(app, settingsManager) {
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ internal class RestoreStorageViewModel(
|
||||||
override fun onLocationSet(uri: Uri) {
|
override fun onLocationSet(uri: Uri) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val hasBackup = try {
|
val hasBackup = try {
|
||||||
backupPlugin.hasBackup(uri)
|
storagePlugin.hasBackup(uri)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "Error reading URI: $uri", e)
|
Log.e(TAG, "Error reading URI: $uri", e)
|
||||||
false
|
false
|
||||||
|
|
|
@ -12,11 +12,11 @@ import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class BackupPluginTest : BackupTest() {
|
internal class StoragePluginTest : BackupTest() {
|
||||||
|
|
||||||
private val storage = mockk<DocumentsStorage>()
|
private val storage = mockk<DocumentsStorage>()
|
||||||
|
|
||||||
private val plugin = DocumentsProviderBackupPlugin(context, storage)
|
private val plugin = DocumentsProviderStoragePlugin(context, storage)
|
||||||
|
|
||||||
private val setDir: DocumentFile = mockk()
|
private val setDir: DocumentFile = mockk()
|
||||||
private val backupFile: DocumentFile = mockk()
|
private val backupFile: DocumentFile = mockk()
|
|
@ -11,11 +11,11 @@ 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.metadata.PackageState
|
import com.stevesoltys.seedvault.metadata.PackageState
|
||||||
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.restore.RestorableBackup
|
import com.stevesoltys.seedvault.restore.RestorableBackup
|
||||||
import com.stevesoltys.seedvault.transport.TransportTest
|
import com.stevesoltys.seedvault.transport.TransportTest
|
||||||
import com.stevesoltys.seedvault.transport.backup.ApkBackup
|
import com.stevesoltys.seedvault.transport.backup.ApkBackup
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -46,16 +46,17 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
||||||
private val strictContext: Context = mockk<Context>().apply {
|
private val strictContext: Context = mockk<Context>().apply {
|
||||||
every { packageManager } returns pm
|
every { packageManager } returns pm
|
||||||
}
|
}
|
||||||
private val backupPlugin: BackupPlugin = mockk()
|
@Suppress("Deprecation")
|
||||||
private val restorePlugin: RestorePlugin = mockk()
|
private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
|
||||||
|
private val storagePlugin: StoragePlugin = mockk()
|
||||||
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
|
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
|
||||||
private val apkInstaller: ApkInstaller = mockk()
|
private val apkInstaller: ApkInstaller = mockk()
|
||||||
|
|
||||||
private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager)
|
private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager)
|
||||||
private val apkRestore: ApkRestore = ApkRestore(
|
private val apkRestore: ApkRestore = ApkRestore(
|
||||||
context = strictContext,
|
context = strictContext,
|
||||||
backupPlugin = backupPlugin,
|
storagePlugin = storagePlugin,
|
||||||
restorePlugin = restorePlugin,
|
legacyStoragePlugin = legacyStoragePlugin,
|
||||||
crypto = crypto,
|
crypto = crypto,
|
||||||
splitCompatChecker = splitCompatChecker,
|
splitCompatChecker = splitCompatChecker,
|
||||||
apkInstaller = apkInstaller
|
apkInstaller = apkInstaller
|
||||||
|
@ -129,7 +130,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
||||||
|
|
||||||
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 { backupPlugin.getInputStream(token, name) } returns inputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns inputStream
|
||||||
every { pm.getPackageArchiveInfo(capture(apkPath), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(capture(apkPath), any()) } returns packageInfo
|
||||||
every {
|
every {
|
||||||
@Suppress("UNRESOLVED_REFERENCE")
|
@Suppress("UNRESOLVED_REFERENCE")
|
||||||
|
@ -143,7 +144,7 @@ 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 { backupPlugin.getInputStream(token, suffixName) } returns splitInputStream
|
coEvery { storagePlugin.getInputStream(token, suffixName) } returns splitInputStream
|
||||||
coEvery {
|
coEvery {
|
||||||
apkInstaller.install(capture(cacheFiles), packageName, installerName, any())
|
apkInstaller.install(capture(cacheFiles), packageName, installerName, any())
|
||||||
} returns MutableInstallResult(1).apply {
|
} returns MutableInstallResult(1).apply {
|
||||||
|
|
|
@ -14,6 +14,8 @@ import com.stevesoltys.seedvault.getRandomString
|
||||||
import com.stevesoltys.seedvault.metadata.ApkSplit
|
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.StoragePlugin
|
||||||
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
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
|
||||||
|
@ -21,8 +23,6 @@ import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED
|
||||||
import com.stevesoltys.seedvault.transport.TransportTest
|
import com.stevesoltys.seedvault.transport.TransportTest
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestorePlugin
|
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
@ -50,15 +50,15 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
private val strictContext: Context = mockk<Context>().apply {
|
private val strictContext: Context = mockk<Context>().apply {
|
||||||
every { packageManager } returns pm
|
every { packageManager } returns pm
|
||||||
}
|
}
|
||||||
private val backupPlugin: BackupPlugin = mockk()
|
private val storagePlugin: StoragePlugin = mockk()
|
||||||
private val restorePlugin: RestorePlugin = 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()
|
||||||
|
|
||||||
private val apkRestore: ApkRestore = ApkRestore(
|
private val apkRestore: ApkRestore = ApkRestore(
|
||||||
strictContext,
|
strictContext,
|
||||||
backupPlugin,
|
storagePlugin,
|
||||||
restorePlugin,
|
legacyStoragePlugin,
|
||||||
crypto,
|
crypto,
|
||||||
splitCompatChecker,
|
splitCompatChecker,
|
||||||
apkInstaller
|
apkInstaller
|
||||||
|
@ -96,7 +96,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
|
|
||||||
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 { backupPlugin.getInputStream(token, name) } returns apkInputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||||
|
|
||||||
apkRestore.restore(backup).collectIndexed { i, value ->
|
apkRestore.restore(backup).collectIndexed { i, value ->
|
||||||
assertQueuedFailFinished(i, value)
|
assertQueuedFailFinished(i, value)
|
||||||
|
@ -110,7 +110,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
|
|
||||||
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 { backupPlugin.getInputStream(token, name) } returns apkInputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||||
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
||||||
|
|
||||||
apkRestore.restore(backup).collectIndexed { i, value ->
|
apkRestore.restore(backup).collectIndexed { i, value ->
|
||||||
|
@ -169,7 +169,9 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
|
|
||||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
coEvery { restorePlugin.getApkInputStream(token, packageName, "") } returns apkInputStream
|
coEvery {
|
||||||
|
legacyStoragePlugin.getApkInputStream(token, packageName, "")
|
||||||
|
} returns apkInputStream
|
||||||
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
||||||
every {
|
every {
|
||||||
@Suppress("UNRESOLVED_REFERENCE")
|
@Suppress("UNRESOLVED_REFERENCE")
|
||||||
|
@ -299,7 +301,7 @@ 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 {
|
||||||
backupPlugin.getInputStream(token, suffixName)
|
storagePlugin.getInputStream(token, suffixName)
|
||||||
} returns ByteArrayInputStream(getRandomByteArray())
|
} returns ByteArrayInputStream(getRandomByteArray())
|
||||||
|
|
||||||
apkRestore.restore(backup).collectIndexed { i, value ->
|
apkRestore.restore(backup).collectIndexed { i, value ->
|
||||||
|
@ -322,7 +324,7 @@ 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 { backupPlugin.getInputStream(token, suffixName) } throws IOException()
|
coEvery { storagePlugin.getInputStream(token, suffixName) } throws IOException()
|
||||||
|
|
||||||
apkRestore.restore(backup).collectIndexed { i, value ->
|
apkRestore.restore(backup).collectIndexed { i, value ->
|
||||||
assertQueuedProgressFailFinished(i, value)
|
assertQueuedProgressFailFinished(i, value)
|
||||||
|
@ -358,9 +360,9 @@ 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 { backupPlugin.getInputStream(token, suffixName1) } returns split1InputStream
|
coEvery { storagePlugin.getInputStream(token, suffixName1) } returns split1InputStream
|
||||||
every { crypto.getNameForApk(salt, packageName, split2Name) } returns suffixName2
|
every { crypto.getNameForApk(salt, packageName, split2Name) } returns suffixName2
|
||||||
coEvery { backupPlugin.getInputStream(token, suffixName2) } returns split2InputStream
|
coEvery { storagePlugin.getInputStream(token, suffixName2) } returns split2InputStream
|
||||||
|
|
||||||
coEvery {
|
coEvery {
|
||||||
apkInstaller.install(match { it.size == 3 }, packageName, installerName, any())
|
apkInstaller.install(match { it.size == 3 }, packageName, installerName, any())
|
||||||
|
@ -387,7 +389,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 { backupPlugin.getInputStream(token, name) } returns apkInputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||||
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
||||||
every {
|
every {
|
||||||
@Suppress("UNRESOLVED_REFERENCE")
|
@Suppress("UNRESOLVED_REFERENCE")
|
||||||
|
|
|
@ -16,19 +16,18 @@ 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.metadata.PackageState.UNKNOWN_ERROR
|
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
|
||||||
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
|
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
|
||||||
import com.stevesoltys.seedvault.transport.backup.ApkBackup
|
import com.stevesoltys.seedvault.transport.backup.ApkBackup
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
|
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
|
||||||
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
|
||||||
import com.stevesoltys.seedvault.transport.backup.KVBackup
|
import com.stevesoltys.seedvault.transport.backup.KVBackup
|
||||||
import com.stevesoltys.seedvault.transport.backup.PackageService
|
import com.stevesoltys.seedvault.transport.backup.PackageService
|
||||||
import com.stevesoltys.seedvault.transport.backup.TestKvDbManager
|
import com.stevesoltys.seedvault.transport.backup.TestKvDbManager
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestore
|
import com.stevesoltys.seedvault.transport.restore.FullRestore
|
||||||
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestore
|
import com.stevesoltys.seedvault.transport.restore.KVRestore
|
||||||
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.restore.OutputFactory
|
import com.stevesoltys.seedvault.transport.restore.OutputFactory
|
||||||
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
import com.stevesoltys.seedvault.transport.restore.RestoreCoordinator
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
|
@ -61,7 +60,9 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
private val notificationManager = mockk<BackupNotificationManager>()
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
private val dbManager = TestKvDbManager()
|
private val dbManager = TestKvDbManager()
|
||||||
|
|
||||||
private val backupPlugin = mockk<BackupPlugin>()
|
@Suppress("Deprecation")
|
||||||
|
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
||||||
|
private val backupPlugin = mockk<StoragePlugin>()
|
||||||
private val kvBackup =
|
private val kvBackup =
|
||||||
KVBackup(backupPlugin, settingsManager, inputFactory, cryptoImpl, dbManager)
|
KVBackup(backupPlugin, settingsManager, inputFactory, cryptoImpl, dbManager)
|
||||||
private val fullBackup = FullBackup(backupPlugin, settingsManager, inputFactory, cryptoImpl)
|
private val fullBackup = FullBackup(backupPlugin, settingsManager, inputFactory, cryptoImpl)
|
||||||
|
@ -80,18 +81,16 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
notificationManager
|
notificationManager
|
||||||
)
|
)
|
||||||
|
|
||||||
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
|
||||||
private val kvRestore = KVRestore(
|
private val kvRestore = KVRestore(
|
||||||
backupPlugin,
|
backupPlugin,
|
||||||
kvRestorePlugin,
|
legacyPlugin,
|
||||||
outputFactory,
|
outputFactory,
|
||||||
headerReader,
|
headerReader,
|
||||||
cryptoImpl,
|
cryptoImpl,
|
||||||
dbManager
|
dbManager
|
||||||
)
|
)
|
||||||
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
|
||||||
private val fullRestore =
|
private val fullRestore =
|
||||||
FullRestore(backupPlugin, fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
FullRestore(backupPlugin, legacyPlugin, outputFactory, headerReader, cryptoImpl)
|
||||||
private val restore = RestoreCoordinator(
|
private val restore = RestoreCoordinator(
|
||||||
context,
|
context,
|
||||||
crypto,
|
crypto,
|
||||||
|
|
|
@ -22,6 +22,7 @@ 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.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.saf.FILE_BACKUP_METADATA
|
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
|
||||||
import com.stevesoltys.seedvault.settings.Storage
|
import com.stevesoltys.seedvault.settings.Storage
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
|
@ -44,7 +45,7 @@ import kotlin.random.Random
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class BackupCoordinatorTest : BackupTest() {
|
internal class BackupCoordinatorTest : BackupTest() {
|
||||||
|
|
||||||
private val plugin = mockk<BackupPlugin>()
|
private val plugin = mockk<StoragePlugin>()
|
||||||
private val kv = mockk<KVBackup>()
|
private val kv = mockk<KVBackup>()
|
||||||
private val full = mockk<FullBackup>()
|
private val full = mockk<FullBackup>()
|
||||||
private val apkBackup = mockk<ApkBackup>()
|
private val apkBackup = mockk<ApkBackup>()
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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 io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
@ -23,7 +24,7 @@ import kotlin.random.Random
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class FullBackupTest : BackupTest() {
|
internal class FullBackupTest : BackupTest() {
|
||||||
|
|
||||||
private val plugin = mockk<BackupPlugin>()
|
private val plugin = mockk<StoragePlugin>()
|
||||||
private val backup = FullBackup(plugin, settingsManager, inputFactory, crypto)
|
private val backup = FullBackup(plugin, settingsManager, inputFactory, crypto)
|
||||||
|
|
||||||
private val bytes = ByteArray(23).apply { Random.nextBytes(this) }
|
private val bytes = ByteArray(23).apply { Random.nextBytes(this) }
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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 io.mockk.CapturingSlot
|
import io.mockk.CapturingSlot
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
@ -31,7 +32,7 @@ import kotlin.random.Random
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class KVBackupTest : BackupTest() {
|
internal class KVBackupTest : BackupTest() {
|
||||||
|
|
||||||
private val plugin = mockk<BackupPlugin>()
|
private val plugin = mockk<StoragePlugin>()
|
||||||
private val dataInput = mockk<BackupDataInput>()
|
private val dataInput = mockk<BackupDataInput>()
|
||||||
private val dbManager = mockk<KvDbManager>()
|
private val dbManager = mockk<KvDbManager>()
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@ import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||||
import com.stevesoltys.seedvault.header.VERSION
|
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.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import io.mockk.CapturingSlot
|
import io.mockk.CapturingSlot
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
@ -33,8 +34,8 @@ import kotlin.random.Random
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class FullRestoreTest : RestoreTest() {
|
internal class FullRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
private val plugin = mockk<BackupPlugin>()
|
private val plugin = mockk<StoragePlugin>()
|
||||||
private val legacyPlugin = mockk<FullRestorePlugin>()
|
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
||||||
private val restore = FullRestore(plugin, legacyPlugin, outputFactory, headerReader, crypto)
|
private val restore = FullRestore(plugin, legacyPlugin, outputFactory, headerReader, crypto)
|
||||||
|
|
||||||
private val encrypted = getRandomByteArray()
|
private val encrypted = getRandomByteArray()
|
||||||
|
@ -50,7 +51,7 @@ internal class FullRestoreTest : RestoreTest() {
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
fun `v0 hasDataForPackage() delegates to plugin`() = runBlocking {
|
fun `v0 hasDataForPackage() delegates to plugin`() = runBlocking {
|
||||||
val result = Random.nextBoolean()
|
val result = Random.nextBoolean()
|
||||||
coEvery { legacyPlugin.hasDataForPackage(token, packageInfo) } returns result
|
coEvery { legacyPlugin.hasDataForFullPackage(token, packageInfo) } returns result
|
||||||
assertEquals(result, restore.hasDataForPackage(token, packageInfo))
|
assertEquals(result, restore.hasDataForPackage(token, packageInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,10 @@ import com.stevesoltys.seedvault.header.UnsupportedVersionException
|
||||||
import com.stevesoltys.seedvault.header.VERSION
|
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.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
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 com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
@ -35,8 +36,8 @@ import kotlin.random.Random
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
internal class KVRestoreTest : RestoreTest() {
|
internal class KVRestoreTest : RestoreTest() {
|
||||||
|
|
||||||
private val plugin = mockk<BackupPlugin>()
|
private val plugin = mockk<StoragePlugin>()
|
||||||
private val legacyPlugin = mockk<KVRestorePlugin>()
|
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
||||||
private val dbManager = mockk<KvDbManager>()
|
private val dbManager = mockk<KvDbManager>()
|
||||||
private val output = mockk<BackupDataOutput>()
|
private val output = mockk<BackupDataOutput>()
|
||||||
private val restore =
|
private val restore =
|
||||||
|
|
|
@ -16,8 +16,8 @@ import com.stevesoltys.seedvault.metadata.MetadataReader
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||||
import com.stevesoltys.seedvault.settings.Storage
|
import com.stevesoltys.seedvault.settings.Storage
|
||||||
import com.stevesoltys.seedvault.transport.TransportTest
|
import com.stevesoltys.seedvault.transport.TransportTest
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.transport.backup.EncryptedMetadata
|
import com.stevesoltys.seedvault.plugins.EncryptedMetadata
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
@ -39,7 +39,7 @@ import kotlin.random.Random
|
||||||
internal class RestoreCoordinatorTest : TransportTest() {
|
internal class RestoreCoordinatorTest : TransportTest() {
|
||||||
|
|
||||||
private val notificationManager: BackupNotificationManager = mockk()
|
private val notificationManager: BackupNotificationManager = mockk()
|
||||||
private val plugin = mockk<BackupPlugin>()
|
private val plugin = mockk<StoragePlugin>()
|
||||||
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>()
|
||||||
|
|
|
@ -13,10 +13,11 @@ import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl
|
||||||
import com.stevesoltys.seedvault.encodeBase64
|
import com.stevesoltys.seedvault.encodeBase64
|
||||||
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
import com.stevesoltys.seedvault.header.HeaderReaderImpl
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
import com.stevesoltys.seedvault.metadata.MetadataReaderImpl
|
||||||
|
import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.toByteArrayFromHex
|
import com.stevesoltys.seedvault.toByteArrayFromHex
|
||||||
import com.stevesoltys.seedvault.transport.TransportTest
|
import com.stevesoltys.seedvault.transport.TransportTest
|
||||||
import com.stevesoltys.seedvault.transport.backup.BackupPlugin
|
|
||||||
import com.stevesoltys.seedvault.transport.backup.KvDbManager
|
import com.stevesoltys.seedvault.transport.backup.KvDbManager
|
||||||
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
@ -49,19 +50,19 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
private val metadataReader = MetadataReaderImpl(cryptoImpl)
|
||||||
private val notificationManager = mockk<BackupNotificationManager>()
|
private val notificationManager = mockk<BackupNotificationManager>()
|
||||||
|
|
||||||
private val backupPlugin = mockk<BackupPlugin>()
|
@Suppress("Deprecation")
|
||||||
private val kvRestorePlugin = mockk<KVRestorePlugin>()
|
private val legacyPlugin = mockk<LegacyStoragePlugin>()
|
||||||
|
private val backupPlugin = mockk<StoragePlugin>()
|
||||||
private val kvRestore = KVRestore(
|
private val kvRestore = KVRestore(
|
||||||
backupPlugin,
|
backupPlugin,
|
||||||
kvRestorePlugin,
|
legacyPlugin,
|
||||||
outputFactory,
|
outputFactory,
|
||||||
headerReader,
|
headerReader,
|
||||||
cryptoImpl,
|
cryptoImpl,
|
||||||
dbManager
|
dbManager
|
||||||
)
|
)
|
||||||
private val fullRestorePlugin = mockk<FullRestorePlugin>()
|
|
||||||
private val fullRestore =
|
private val fullRestore =
|
||||||
FullRestore(backupPlugin, fullRestorePlugin, outputFactory, headerReader, cryptoImpl)
|
FullRestore(backupPlugin, legacyPlugin, outputFactory, headerReader, cryptoImpl)
|
||||||
private val restore = RestoreCoordinator(
|
private val restore = RestoreCoordinator(
|
||||||
context,
|
context,
|
||||||
crypto,
|
crypto,
|
||||||
|
@ -161,7 +162,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||||
|
|
||||||
// find data for K/V backup
|
// find data for K/V backup
|
||||||
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
|
coEvery { legacyPlugin.hasDataForPackage(token, packageInfo) } returns true
|
||||||
|
|
||||||
val restoreDescription = restore.nextRestorePackage() ?: fail()
|
val restoreDescription = restore.nextRestorePackage() ?: fail()
|
||||||
assertEquals(packageInfo.packageName, restoreDescription.packageName)
|
assertEquals(packageInfo.packageName, restoreDescription.packageName)
|
||||||
|
@ -171,10 +172,10 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
val backupDataOutput = mockk<BackupDataOutput>()
|
val backupDataOutput = mockk<BackupDataOutput>()
|
||||||
val rInputStream = ByteArrayInputStream(encryptedAppData)
|
val rInputStream = ByteArrayInputStream(encryptedAppData)
|
||||||
val rInputStream2 = ByteArrayInputStream(encryptedAppData2)
|
val rInputStream2 = ByteArrayInputStream(encryptedAppData2)
|
||||||
coEvery { kvRestorePlugin.listRecords(token, packageInfo) } returns listOf(key64, key264)
|
coEvery { legacyPlugin.listRecords(token, packageInfo) } returns listOf(key64, key264)
|
||||||
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
|
every { outputFactory.getBackupDataOutput(fileDescriptor) } returns backupDataOutput
|
||||||
coEvery {
|
coEvery {
|
||||||
kvRestorePlugin.getInputStreamForRecord(
|
legacyPlugin.getInputStreamForRecord(
|
||||||
token,
|
token,
|
||||||
packageInfo,
|
packageInfo,
|
||||||
key64
|
key64
|
||||||
|
@ -183,7 +184,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
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
|
||||||
coEvery {
|
coEvery {
|
||||||
kvRestorePlugin.getInputStreamForRecord(
|
legacyPlugin.getInputStreamForRecord(
|
||||||
token,
|
token,
|
||||||
packageInfo,
|
packageInfo,
|
||||||
key264
|
key264
|
||||||
|
@ -212,8 +213,8 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
assertEquals(TRANSPORT_OK, restore.startRestore(token, arrayOf(packageInfo)))
|
||||||
|
|
||||||
// find data only for full backup
|
// find data only for full backup
|
||||||
coEvery { kvRestorePlugin.hasDataForPackage(token, packageInfo) } returns false
|
coEvery { legacyPlugin.hasDataForPackage(token, packageInfo) } returns false
|
||||||
coEvery { fullRestorePlugin.hasDataForPackage(token, packageInfo) } returns true
|
coEvery { legacyPlugin.hasDataForFullPackage(token, packageInfo) } returns true
|
||||||
|
|
||||||
val restoreDescription = restore.nextRestorePackage() ?: fail()
|
val restoreDescription = restore.nextRestorePackage() ?: fail()
|
||||||
assertEquals(packageInfo.packageName, restoreDescription.packageName)
|
assertEquals(packageInfo.packageName, restoreDescription.packageName)
|
||||||
|
@ -223,7 +224,7 @@ internal class RestoreV0IntegrationTest : TransportTest() {
|
||||||
val inputStream = ByteArrayInputStream(encryptedData)
|
val inputStream = ByteArrayInputStream(encryptedData)
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
coEvery {
|
coEvery {
|
||||||
fullRestorePlugin.getInputStreamForPackage(
|
legacyPlugin.getInputStreamForPackage(
|
||||||
token,
|
token,
|
||||||
packageInfo
|
packageInfo
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue