Remove legacy backup plugin code

This commit is contained in:
Torsten Grote 2021-09-23 10:01:49 +02:00 committed by Chirayu Desai
parent 1885021c1c
commit 183e34afd2
13 changed files with 46 additions and 450 deletions

View file

@ -4,20 +4,14 @@ 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.saf.DocumentsProviderBackupPlugin
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderFullBackup
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderFullRestorePlugin import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderFullRestorePlugin
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderKVBackup
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderKVRestorePlugin import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderKVRestorePlugin
import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderRestorePlugin import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderRestorePlugin
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.MAX_KEY_LENGTH
import com.stevesoltys.seedvault.plugins.saf.MAX_KEY_LENGTH_NEXTCLOUD
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.backup.BackupPlugin
import com.stevesoltys.seedvault.transport.backup.FullBackupPlugin
import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
import com.stevesoltys.seedvault.transport.restore.RestorePlugin import com.stevesoltys.seedvault.transport.restore.RestorePlugin
@ -35,7 +29,6 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.inject import org.koin.core.inject
import java.io.IOException
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
@ -46,14 +39,7 @@ 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 kvBackupPlugin: KVBackupPlugin = DocumentsProviderKVBackup(context, storage) private val backupPlugin: BackupPlugin = DocumentsProviderBackupPlugin(context, storage)
private val fullBackupPlugin: FullBackupPlugin = DocumentsProviderFullBackup(context, storage)
private val backupPlugin: BackupPlugin = DocumentsProviderBackupPlugin(
context,
storage,
kvBackupPlugin,
fullBackupPlugin
)
private val kvRestorePlugin: KVRestorePlugin = private val kvRestorePlugin: KVRestorePlugin =
DocumentsProviderKVRestorePlugin(context, storage) DocumentsProviderKVRestorePlugin(context, storage)
@ -127,9 +113,8 @@ class PluginTest : KoinComponent {
.writeAndClose(getRandomByteArray()) .writeAndClose(getRandomByteArray())
assertEquals(2, backupPlugin.getAvailableBackups()?.toList()?.size) assertEquals(2, backupPlugin.getAvailableBackups()?.toList()?.size)
// ensure that the new backup dirs exist // ensure that the new backup dir exist
assertTrue(storage.currentKvBackupDir!!.exists()) assertTrue(storage.currentSetDir!!.exists())
assertTrue(storage.currentFullBackupDir!!.exists())
} }
@Test @Test
@ -165,7 +150,8 @@ class PluginTest : KoinComponent {
} }
@Test @Test
fun testApkWriteRead() = runBlocking { @Suppress("Deprecation")
fun v0testApkWriteRead() = runBlocking {
// initialize storage with given token // initialize storage with given token
initStorage(token) initStorage(token)
@ -191,157 +177,44 @@ class PluginTest : KoinComponent {
} }
@Test @Test
fun testKvBackupRestore() = runBlocking { fun testBackupRestore() = runBlocking {
// define shortcuts
val kvBackup = backupPlugin.kvBackupPlugin
val kvRestore = restorePlugin.kvRestorePlugin
// initialize storage with given token // initialize storage with given token
initStorage(token) initStorage(token)
// no data available for given package val name1 = getRandomBase64()
assertFalse(kvBackup.hasDataForPackage(packageInfo)) val name2 = getRandomBase64()
assertFalse(kvRestore.hasDataForPackage(token, packageInfo))
// define key/value pair records
val record1 = Pair(getRandomBase64(23), getRandomByteArray(1337))
val record2 = Pair(getRandomBase64(42), getRandomByteArray(42 * 1024))
val record3 = Pair(getRandomBase64(128), getRandomByteArray(5 * 1024 * 1024))
// write first record
kvBackup.getOutputStreamForRecord(packageInfo, record1.first).writeAndClose(record1.second)
// data is now available for current token and given package, but not for different token
assertTrue(kvBackup.hasDataForPackage(packageInfo))
assertTrue(kvRestore.hasDataForPackage(token, packageInfo))
assertFalse(kvRestore.hasDataForPackage(token + 1, packageInfo))
// record for package is found and returned properly
var records = kvRestore.listRecords(token, packageInfo)
assertEquals(1, records.size)
assertEquals(record1.first, records[0])
assertReadEquals(
record1.second,
kvRestore.getInputStreamForRecord(token, packageInfo, record1.first)
)
// write second and third record
kvBackup.getOutputStreamForRecord(packageInfo, record2.first).writeAndClose(record2.second)
kvBackup.getOutputStreamForRecord(packageInfo, record3.first).writeAndClose(record3.second)
// all records for package are found and returned properly
assertTrue(kvRestore.hasDataForPackage(token, packageInfo))
records = kvRestore.listRecords(token, packageInfo)
assertEquals(listOf(record1.first, record2.first, record3.first).sorted(), records.sorted())
assertReadEquals(
record1.second,
kvRestore.getInputStreamForRecord(token, packageInfo, record1.first)
)
assertReadEquals(
record2.second,
kvRestore.getInputStreamForRecord(token, packageInfo, record2.first)
)
assertReadEquals(
record3.second,
kvRestore.getInputStreamForRecord(token, packageInfo, record3.first)
)
// delete record3 and ensure that the other two are still found
kvBackup.deleteRecord(packageInfo, record3.first)
assertTrue(kvRestore.hasDataForPackage(token, packageInfo))
records = kvRestore.listRecords(token, packageInfo)
assertEquals(listOf(record1.first, record2.first).sorted(), records.sorted())
// remove all data of package and ensure that it is gone
kvBackup.removeDataOfPackage(packageInfo)
assertFalse(kvBackup.hasDataForPackage(packageInfo))
assertFalse(kvRestore.hasDataForPackage(token, packageInfo))
}
@Test
fun testMaxKvKeyLength() = runBlocking {
// define shortcuts
val kvBackup = backupPlugin.kvBackupPlugin
val kvRestore = restorePlugin.kvRestorePlugin
// initialize storage with given token
initStorage(token)
assertFalse(kvBackup.hasDataForPackage(packageInfo))
// FIXME get Nextcloud to have the same limit
// Since Nextcloud is using WebDAV and that seems to have undefined lower file name limits
// we might have to lower our maximum to accommodate for that.
val max = if (isNextcloud()) MAX_KEY_LENGTH_NEXTCLOUD else MAX_KEY_LENGTH
val maxOver = if (isNextcloud()) max + 10 else max + 1
// define record with maximum key length and one above the maximum
val recordMax = Pair(getRandomBase64(max), getRandomByteArray(1024))
val recordOver = Pair(getRandomBase64(maxOver), getRandomByteArray(1024))
// write max record
kvBackup.getOutputStreamForRecord(packageInfo, recordMax.first)
.writeAndClose(recordMax.second)
// max record is found correctly
assertTrue(kvRestore.hasDataForPackage(token, packageInfo))
val records = kvRestore.listRecords(token, packageInfo)
assertEquals(listOf(recordMax.first), records)
// write exceeding key length record
if (isNextcloud()) {
// Nextcloud simply refuses to write long filenames
coAssertThrows(IOException::class.java) {
kvBackup.getOutputStreamForRecord(packageInfo, recordOver.first)
.writeAndClose(recordOver.second)
}
} else {
coAssertThrows(IllegalStateException::class.java) {
kvBackup.getOutputStreamForRecord(packageInfo, recordOver.first)
.writeAndClose(recordOver.second)
}
}
}
@Test
fun testFullBackupRestore() = runBlocking {
// define shortcuts
val fullBackup = backupPlugin.fullBackupPlugin
val fullRestore = restorePlugin.fullRestorePlugin
// initialize storage with given token
initStorage(token)
// no data available initially // no data available initially
assertFalse(fullRestore.hasDataForPackage(token, packageInfo)) assertFalse(backupPlugin.hasData(token, name1))
assertFalse(fullRestore.hasDataForPackage(token, packageInfo2)) assertFalse(backupPlugin.hasData(token, name2))
// write full backup data // write full backup data
val data = getRandomByteArray(5 * 1024 * 1024) val data = getRandomByteArray(5 * 1024 * 1024)
fullBackup.getOutputStream(packageInfo).writeAndClose(data) backupPlugin.getOutputStream(token, name1).writeAndClose(data)
// data is available now, but only this token // data is available now, but only this token
assertTrue(fullRestore.hasDataForPackage(token, packageInfo)) assertTrue(backupPlugin.hasData(token, name1))
assertFalse(fullRestore.hasDataForPackage(token + 1, packageInfo)) assertFalse(backupPlugin.hasData(token + 1, name1))
// restore data matches backed up data // restore data matches backed up data
assertReadEquals(data, fullRestore.getInputStreamForPackage(token, packageInfo)) assertReadEquals(data, backupPlugin.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)
fullBackup.getOutputStream(packageInfo2).writeAndClose(data2) backupPlugin.getOutputStream(token, name2).writeAndClose(data2)
assertTrue(fullRestore.hasDataForPackage(token, packageInfo2)) assertTrue(backupPlugin.hasData(token, name2))
assertReadEquals(data2, fullRestore.getInputStreamForPackage(token, packageInfo2)) assertReadEquals(data2, backupPlugin.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
fullBackup.removeDataOfPackage(packageInfo) backupPlugin.removeData(token, name1)
assertFalse(fullRestore.hasDataForPackage(token, packageInfo)) assertFalse(backupPlugin.hasData(token, name1))
// second package is still there // second package is still there
assertTrue(fullRestore.hasDataForPackage(token, packageInfo2)) assertTrue(backupPlugin.hasData(token, name2))
// ensure that it gets deleted as well // ensure that it gets deleted as well
fullBackup.removeDataOfPackage(packageInfo2) backupPlugin.removeData(token, name2)
assertFalse(fullRestore.hasDataForPackage(token, packageInfo2)) assertFalse(backupPlugin.hasData(token, name2))
} }
private fun initStorage(token: Long) = runBlocking { private fun initStorage(token: Long) = runBlocking {
@ -349,8 +222,4 @@ class PluginTest : KoinComponent {
backupPlugin.initializeDevice() backupPlugin.initializeDevice()
} }
private fun isNextcloud(): Boolean {
return backupPlugin.providerPackageName?.startsWith("com.nextcloud") ?: false
}
} }

View file

@ -7,8 +7,6 @@ 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.transport.backup.BackupPlugin
import com.stevesoltys.seedvault.transport.backup.EncryptedMetadata import com.stevesoltys.seedvault.transport.backup.EncryptedMetadata
import com.stevesoltys.seedvault.transport.backup.FullBackupPlugin
import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -19,9 +17,7 @@ private val TAG = DocumentsProviderBackupPlugin::class.java.simpleName
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
internal class DocumentsProviderBackupPlugin( internal class DocumentsProviderBackupPlugin(
private val context: Context, private val context: Context,
private val storage: DocumentsStorage, private val storage: DocumentsStorage
override val kvBackupPlugin: KVBackupPlugin,
override val fullBackupPlugin: FullBackupPlugin
) : BackupPlugin { ) : BackupPlugin {
private val packageManager: PackageManager = context.packageManager private val packageManager: PackageManager = context.packageManager
@ -45,8 +41,7 @@ internal class DocumentsProviderBackupPlugin(
storage.reset(null) storage.reset(null)
// create backup folders // create backup folders
storage.currentKvBackupDir ?: throw IOException() storage.currentSetDir ?: throw IOException()
storage.currentFullBackupDir ?: throw IOException()
} }
@Throws(IOException::class) @Throws(IOException::class)

View file

@ -1,37 +0,0 @@
package com.stevesoltys.seedvault.plugins.saf
import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log
import com.stevesoltys.seedvault.transport.backup.DEFAULT_QUOTA_FULL_BACKUP
import com.stevesoltys.seedvault.transport.backup.FullBackupPlugin
import java.io.IOException
import java.io.OutputStream
private val TAG = DocumentsProviderFullBackup::class.java.simpleName
@Suppress("BlockingMethodInNonBlockingContext")
internal class DocumentsProviderFullBackup(
private val context: Context,
private val storage: DocumentsStorage
) : FullBackupPlugin {
override fun getQuota() = DEFAULT_QUOTA_FULL_BACKUP
@Throws(IOException::class)
override suspend fun getOutputStream(targetPackage: PackageInfo): OutputStream {
val file = storage.currentFullBackupDir?.createOrGetFile(context, targetPackage.packageName)
?: throw IOException()
return storage.getOutputStream(file)
}
@Throws(IOException::class)
override suspend fun removeDataOfPackage(packageInfo: PackageInfo) {
val packageName = packageInfo.packageName
Log.i(TAG, "Deleting $packageName...")
val file = storage.currentFullBackupDir?.findFileBlocking(context, packageName)
?: return
if (!file.delete()) throw IOException("Failed to delete $packageName")
}
}

View file

@ -6,7 +6,8 @@ import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext", "Deprecation")
@Deprecated("Use only for v0 restore")
internal class DocumentsProviderFullRestorePlugin( internal class DocumentsProviderFullRestorePlugin(
private val context: Context, private val context: Context,
private val documentsStorage: DocumentsStorage private val documentsStorage: DocumentsStorage

View file

@ -1,105 +0,0 @@
package com.stevesoltys.seedvault.plugins.saf
import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.transport.backup.DEFAULT_QUOTA_KEY_VALUE_BACKUP
import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import java.io.IOException
import java.io.OutputStream
const val MAX_KEY_LENGTH = 255
const val MAX_KEY_LENGTH_NEXTCLOUD = 225
@Suppress("BlockingMethodInNonBlockingContext")
internal class DocumentsProviderKVBackup(
private val context: Context,
private val storage: DocumentsStorage
) : KVBackupPlugin {
private var packageFile: DocumentFile? = null
private var packageChildren: List<DocumentFile>? = null
override fun getQuota(): Long = DEFAULT_QUOTA_KEY_VALUE_BACKUP
@Throws(IOException::class)
override suspend fun hasDataForPackage(packageInfo: PackageInfo): Boolean {
// get the folder for the package (or create it) and all files in it
val dir =
storage.getOrCreateKVBackupDir().createOrGetDirectory(context, packageInfo.packageName)
val children = dir.listFilesBlocking(context)
// cache package file for subsequent operations
packageFile = dir
// also cache children as doing this for every record is super slow
packageChildren = children
return children.isNotEmpty()
}
@Throws(IOException::class)
override suspend fun getOutputStreamForRecord(
packageInfo: PackageInfo,
key: String
): OutputStream {
// check maximum key lengths
check(key.length <= MAX_KEY_LENGTH) {
"Key $key for ${packageInfo.packageName} is too long: ${key.length} chars."
}
if (key.length > MAX_KEY_LENGTH_NEXTCLOUD) {
Log.e(
DocumentsProviderKVBackup::class.java.simpleName,
"Key $key for ${packageInfo.packageName} is too long: ${key.length} chars."
)
}
// get dir and children from cache
val packageFile = this.packageFile
?: throw AssertionError("No cached packageFile for ${packageInfo.packageName}")
packageFile.assertRightFile(packageInfo)
val children = packageChildren
?: throw AssertionError("No cached children for ${packageInfo.packageName}")
// get file for key from cache,
val keyFile = children.find { it.name == key } // try cache first
?: packageFile.createFile(MIME_TYPE, key) // assume it doesn't exist, create it
?: packageFile.createOrGetFile(context, key) // cache was stale, so try to find it
check(keyFile.name == key) { "Key file named ${keyFile.name}, but should be $key" }
return storage.getOutputStream(keyFile)
}
@Throws(IOException::class)
override suspend fun deleteRecord(packageInfo: PackageInfo, key: String) {
val packageFile = this.packageFile
?: throw AssertionError("No cached packageFile for ${packageInfo.packageName}")
packageFile.assertRightFile(packageInfo)
val children = packageChildren
?: throw AssertionError("No cached children for ${packageInfo.packageName}")
// try to find file for given key and delete it if found
val keyFile = children.find { it.name == key } // try to find in cache
?: packageFile.findFileBlocking(context, key) // fall-back to provider
?: return // not found, nothing left to do
keyFile.delete()
// we don't update the children cache as deleted records
// are not expected to get re-added in the same backup pass
}
@Throws(IOException::class)
override suspend fun removeDataOfPackage(packageInfo: PackageInfo) {
val packageFile = this.packageFile
?: throw AssertionError("No cached packageFile for ${packageInfo.packageName}")
packageFile.assertRightFile(packageInfo)
// We are not using the cached children here in case they are stale.
// This operation isn't frequent, so we don't need to heavily optimize it.
packageFile.deleteContents(context)
// clear children cache
packageChildren = ArrayList()
}
override fun packageFinished(packageInfo: PackageInfo) {
packageFile = null
packageChildren = null
}
}

View file

@ -7,7 +7,8 @@ import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext", "Deprecation")
@Deprecated("Use only for v0 restore")
internal class DocumentsProviderKVRestorePlugin( internal class DocumentsProviderKVRestorePlugin(
private val context: Context, private val context: Context,
private val storage: DocumentsStorage private val storage: DocumentsStorage

View file

@ -1,8 +1,6 @@
package com.stevesoltys.seedvault.plugins.saf package com.stevesoltys.seedvault.plugins.saf
import com.stevesoltys.seedvault.transport.backup.BackupPlugin import com.stevesoltys.seedvault.transport.backup.BackupPlugin
import com.stevesoltys.seedvault.transport.backup.FullBackupPlugin
import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin
import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin
import com.stevesoltys.seedvault.transport.restore.RestorePlugin import com.stevesoltys.seedvault.transport.restore.RestorePlugin
@ -12,9 +10,7 @@ import org.koin.dsl.module
val documentsProviderModule = module { val documentsProviderModule = module {
single { DocumentsStorage(androidContext(), get()) } single { DocumentsStorage(androidContext(), get()) }
single<KVBackupPlugin> { DocumentsProviderKVBackup(androidContext(), get()) } single<BackupPlugin> { DocumentsProviderBackupPlugin(androidContext(), get()) }
single<FullBackupPlugin> { DocumentsProviderFullBackup(androidContext(), get()) }
single<BackupPlugin> { DocumentsProviderBackupPlugin(androidContext(), get(), get(), get()) }
single<KVRestorePlugin> { DocumentsProviderKVRestorePlugin(androidContext(), get()) } single<KVRestorePlugin> { DocumentsProviderKVRestorePlugin(androidContext(), get()) }
single<FullRestorePlugin> { DocumentsProviderFullRestorePlugin(androidContext(), get()) } single<FullRestorePlugin> { DocumentsProviderFullRestorePlugin(androidContext(), get()) }

View file

@ -27,7 +27,9 @@ import java.io.OutputStream
import kotlin.coroutines.resume import kotlin.coroutines.resume
const val DIRECTORY_ROOT = ".SeedVaultAndroidBackup" const val DIRECTORY_ROOT = ".SeedVaultAndroidBackup"
@Deprecated("")
const val DIRECTORY_FULL_BACKUP = "full" const val DIRECTORY_FULL_BACKUP = "full"
@Deprecated("")
const val DIRECTORY_KEY_VALUE_BACKUP = "kv" const val DIRECTORY_KEY_VALUE_BACKUP = "kv"
const val FILE_BACKUP_METADATA = ".backup.metadata" const val FILE_BACKUP_METADATA = ".backup.metadata"
const val FILE_NO_MEDIA = ".nomedia" const val FILE_NO_MEDIA = ".nomedia"
@ -73,7 +75,7 @@ internal class DocumentsStorage(
return field return field
} }
private var currentSetDir: DocumentFile? = null var currentSetDir: DocumentFile? = null
get() = runBlocking { get() = runBlocking {
if (field == null) { if (field == null) {
if (currentToken == 0L) return@runBlocking null if (currentToken == 0L) return@runBlocking null
@ -86,32 +88,7 @@ internal class DocumentsStorage(
} }
field field
} }
private set
var currentFullBackupDir: DocumentFile? = null
get() = runBlocking {
if (field == null) {
field = try {
currentSetDir?.createOrGetDirectory(context, DIRECTORY_FULL_BACKUP)
} catch (e: IOException) {
Log.e(TAG, "Error creating full backup dir.", e)
null
}
}
field
}
var currentKvBackupDir: DocumentFile? = null
get() = runBlocking {
if (field == null) {
field = try {
currentSetDir?.createOrGetDirectory(context, DIRECTORY_KEY_VALUE_BACKUP)
} catch (e: IOException) {
Log.e(TAG, "Error creating K/V backup dir.", e)
null
}
}
field
}
/** /**
* Resets this storage abstraction, forcing it to re-fetch cached values on next access. * Resets this storage abstraction, forcing it to re-fetch cached values on next access.
@ -121,8 +98,6 @@ internal class DocumentsStorage(
currentToken = newToken currentToken = newToken
rootBackupDir = null rootBackupDir = null
currentSetDir = null currentSetDir = null
currentKvBackupDir = null
currentFullBackupDir = null
} }
fun getAuthority(): String? = storage?.uri?.authority fun getAuthority(): String? = storage?.uri?.authority
@ -134,23 +109,16 @@ internal class DocumentsStorage(
} }
@Throws(IOException::class) @Throws(IOException::class)
suspend fun getKVBackupDir(token: Long = currentToken ?: error("no token")): DocumentFile? { @Suppress("Deprecation")
if (token == currentToken) return currentKvBackupDir ?: throw IOException() @Deprecated("Use only for v0 restore")
suspend fun getKVBackupDir(token: Long): DocumentFile? {
return getSetDir(token)?.findFileBlocking(context, DIRECTORY_KEY_VALUE_BACKUP) return getSetDir(token)?.findFileBlocking(context, DIRECTORY_KEY_VALUE_BACKUP)
} }
@Throws(IOException::class) @Throws(IOException::class)
suspend fun getOrCreateKVBackupDir( @Suppress("Deprecation")
token: Long = currentToken ?: error("no token") @Deprecated("Use only for v0 restore")
): DocumentFile { suspend fun getFullBackupDir(token: Long): DocumentFile? {
if (token == currentToken) return currentKvBackupDir ?: throw IOException()
val setDir = getSetDir(token) ?: throw IOException()
return setDir.createOrGetDirectory(context, DIRECTORY_KEY_VALUE_BACKUP)
}
@Throws(IOException::class)
suspend fun getFullBackupDir(token: Long = currentToken ?: error("no token")): DocumentFile? {
if (token == currentToken) return currentFullBackupDir ?: throw IOException()
return getSetDir(token)?.findFileBlocking(context, DIRECTORY_FULL_BACKUP) return getSetDir(token)?.findFileBlocking(context, DIRECTORY_FULL_BACKUP)
} }

View file

@ -9,12 +9,6 @@ import java.io.OutputStream
interface BackupPlugin { interface BackupPlugin {
@Deprecated("Use methods in this interface instead")
val kvBackupPlugin: KVBackupPlugin
@Deprecated("Use methods in this interface instead")
val fullBackupPlugin: FullBackupPlugin
/** /**
* Start a new [RestoreSet] with the given token. * Start a new [RestoreSet] with the given token.
* *

View file

@ -1,22 +0,0 @@
package com.stevesoltys.seedvault.transport.backup
import android.content.pm.PackageInfo
import java.io.IOException
import java.io.OutputStream
@Deprecated("Use BackupPlugin instead")
interface FullBackupPlugin {
fun getQuota(): Long
// TODO consider using a salted hash for the package name to not leak it to the storage server
@Throws(IOException::class)
suspend fun getOutputStream(targetPackage: PackageInfo): OutputStream
/**
* Remove all data associated with the given package.
*/
@Throws(IOException::class)
suspend fun removeDataOfPackage(packageInfo: PackageInfo)
}

View file

@ -1,52 +0,0 @@
package com.stevesoltys.seedvault.transport.backup
import android.content.pm.PackageInfo
import java.io.IOException
import java.io.OutputStream
@Deprecated("Use BackupPlugin instead")
interface KVBackupPlugin {
/**
* Get quota for key/value backups.
*/
fun getQuota(): Long
// TODO consider using a salted hash for the package name (and key) to not leak it to the storage server
/**
* Return true if there are records stored for the given package.
* This is always called first per [PackageInfo], before subsequent methods.
*
* Independent of the return value, the storage should now be prepared to store K/V pairs.
* E.g. file-based plugins should a create a directory for the package, if none exists.
*/
@Throws(IOException::class)
suspend fun hasDataForPackage(packageInfo: PackageInfo): Boolean
/**
* Return an [OutputStream] for the given package and key
* which will receive the record's encrypted value.
*/
@Throws(IOException::class)
suspend fun getOutputStreamForRecord(packageInfo: PackageInfo, key: String): OutputStream
/**
* Delete the record for the given package identified by the given key.
*/
@Throws(IOException::class)
suspend fun deleteRecord(packageInfo: PackageInfo, key: String)
/**
* Remove all data associated with the given package,
* but be prepared to receive new records afterwards with [getOutputStreamForRecord].
*/
@Throws(IOException::class)
suspend fun removeDataOfPackage(packageInfo: PackageInfo)
/**
* The package finished backup.
* This can be an opportunity to clear existing caches or to do other clean-up work.
*/
fun packageFinished(packageInfo: PackageInfo)
}

View file

@ -1,6 +1,5 @@
package com.stevesoltys.seedvault package com.stevesoltys.seedvault
import com.stevesoltys.seedvault.plugins.saf.MAX_KEY_LENGTH_NEXTCLOUD
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
@ -32,7 +31,7 @@ fun getRandomString(size: Int = Random.nextInt(1, 255)): String {
private val base64CharPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') // + '+' + '_' + '=' private val base64CharPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') // + '+' + '_' + '='
@Suppress("MagicNumber") @Suppress("MagicNumber")
fun getRandomBase64(size: Int = Random.nextInt(1, MAX_KEY_LENGTH_NEXTCLOUD)): String { fun getRandomBase64(size: Int = Random.nextInt(1, 64)): String {
return (1..size) return (1..size)
.map { Random.nextInt(0, base64CharPool.size) } .map { Random.nextInt(0, base64CharPool.size) }
.map(base64CharPool::get) .map(base64CharPool::get)

View file

@ -2,8 +2,6 @@ package com.stevesoltys.seedvault.plugins.saf
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.transport.backup.BackupTest import com.stevesoltys.seedvault.transport.backup.BackupTest
import com.stevesoltys.seedvault.transport.backup.FullBackupPlugin
import com.stevesoltys.seedvault.transport.backup.KVBackupPlugin
import io.mockk.Runs import io.mockk.Runs
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
@ -17,19 +15,11 @@ import org.junit.jupiter.api.Test
internal class BackupPluginTest : BackupTest() { internal class BackupPluginTest : BackupTest() {
private val storage = mockk<DocumentsStorage>() private val storage = mockk<DocumentsStorage>()
private val kvBackupPlugin: KVBackupPlugin = mockk<DocumentsProviderKVBackup>()
private val fullBackupPlugin: FullBackupPlugin = mockk<DocumentsProviderFullBackup>()
private val plugin = DocumentsProviderBackupPlugin( private val plugin = DocumentsProviderBackupPlugin(context, storage)
context,
storage,
kvBackupPlugin,
fullBackupPlugin
)
private val setDir: DocumentFile = mockk() private val setDir: DocumentFile = mockk()
private val kvDir: DocumentFile = mockk() private val backupFile: DocumentFile = mockk()
private val fullDir: DocumentFile = mockk()
init { init {
// to mock extension functions on DocumentFile // to mock extension functions on DocumentFile
@ -51,13 +41,12 @@ internal class BackupPluginTest : BackupTest() {
every { settingsManager.getToken() } returns token every { settingsManager.getToken() } returns token
coEvery { storage.getSetDir(token) } returns setDir coEvery { storage.getSetDir(token) } returns setDir
// delete contents of current set dir // delete contents of current set dir
coEvery { setDir.listFilesBlocking(context) } returns listOf(kvDir) coEvery { setDir.listFilesBlocking(context) } returns listOf(backupFile)
every { kvDir.delete() } returns true every { backupFile.delete() } returns true
// reset storage // reset storage
every { storage.reset(null) } just Runs every { storage.reset(null) } just Runs
// create kv and full dir // create new set dir
every { storage getProperty "currentKvBackupDir" } returns kvDir every { storage getProperty "currentSetDir" } returns setDir
every { storage getProperty "currentFullBackupDir" } returns fullDir
plugin.initializeDevice() plugin.initializeDevice()
} }