diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt index 2ba8932a..9b4bc464 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt @@ -4,20 +4,14 @@ import androidx.test.core.content.pm.PackageInfoBuilder import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry 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.DocumentsProviderKVBackup import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderKVRestorePlugin import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderRestorePlugin import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage 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.settings.SettingsManager 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.KVRestorePlugin import com.stevesoltys.seedvault.transport.restore.RestorePlugin @@ -35,7 +29,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.koin.core.KoinComponent import org.koin.core.inject -import java.io.IOException @RunWith(AndroidJUnit4::class) @Suppress("BlockingMethodInNonBlockingContext") @@ -46,14 +39,7 @@ class PluginTest : KoinComponent { private val mockedSettingsManager: SettingsManager = mockk() private val storage = DocumentsStorage(context, mockedSettingsManager) - private val kvBackupPlugin: KVBackupPlugin = DocumentsProviderKVBackup(context, storage) - private val fullBackupPlugin: FullBackupPlugin = DocumentsProviderFullBackup(context, storage) - private val backupPlugin: BackupPlugin = DocumentsProviderBackupPlugin( - context, - storage, - kvBackupPlugin, - fullBackupPlugin - ) + private val backupPlugin: BackupPlugin = DocumentsProviderBackupPlugin(context, storage) private val kvRestorePlugin: KVRestorePlugin = DocumentsProviderKVRestorePlugin(context, storage) @@ -127,9 +113,8 @@ class PluginTest : KoinComponent { .writeAndClose(getRandomByteArray()) assertEquals(2, backupPlugin.getAvailableBackups()?.toList()?.size) - // ensure that the new backup dirs exist - assertTrue(storage.currentKvBackupDir!!.exists()) - assertTrue(storage.currentFullBackupDir!!.exists()) + // ensure that the new backup dir exist + assertTrue(storage.currentSetDir!!.exists()) } @Test @@ -165,7 +150,8 @@ class PluginTest : KoinComponent { } @Test - fun testApkWriteRead() = runBlocking { + @Suppress("Deprecation") + fun v0testApkWriteRead() = runBlocking { // initialize storage with given token initStorage(token) @@ -191,157 +177,44 @@ class PluginTest : KoinComponent { } @Test - fun testKvBackupRestore() = runBlocking { - // define shortcuts - val kvBackup = backupPlugin.kvBackupPlugin - val kvRestore = restorePlugin.kvRestorePlugin - + fun testBackupRestore() = runBlocking { // initialize storage with given token initStorage(token) - // no data available for given package - assertFalse(kvBackup.hasDataForPackage(packageInfo)) - 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) + val name1 = getRandomBase64() + val name2 = getRandomBase64() // no data available initially - assertFalse(fullRestore.hasDataForPackage(token, packageInfo)) - assertFalse(fullRestore.hasDataForPackage(token, packageInfo2)) + assertFalse(backupPlugin.hasData(token, name1)) + assertFalse(backupPlugin.hasData(token, name2)) // write full backup data 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 - assertTrue(fullRestore.hasDataForPackage(token, packageInfo)) - assertFalse(fullRestore.hasDataForPackage(token + 1, packageInfo)) + assertTrue(backupPlugin.hasData(token, name1)) + assertFalse(backupPlugin.hasData(token + 1, name1)) // 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 val data2 = getRandomByteArray(5 * 1024 * 1024) - fullBackup.getOutputStream(packageInfo2).writeAndClose(data2) - assertTrue(fullRestore.hasDataForPackage(token, packageInfo2)) - assertReadEquals(data2, fullRestore.getInputStreamForPackage(token, packageInfo2)) + backupPlugin.getOutputStream(token, name2).writeAndClose(data2) + assertTrue(backupPlugin.hasData(token, name2)) + assertReadEquals(data2, backupPlugin.getInputStream(token, name2)) // remove data of first package again and ensure that no more data is found - fullBackup.removeDataOfPackage(packageInfo) - assertFalse(fullRestore.hasDataForPackage(token, packageInfo)) + backupPlugin.removeData(token, name1) + assertFalse(backupPlugin.hasData(token, name1)) // second package is still there - assertTrue(fullRestore.hasDataForPackage(token, packageInfo2)) + assertTrue(backupPlugin.hasData(token, name2)) // ensure that it gets deleted as well - fullBackup.removeDataOfPackage(packageInfo2) - assertFalse(fullRestore.hasDataForPackage(token, packageInfo2)) + backupPlugin.removeData(token, name2) + assertFalse(backupPlugin.hasData(token, name2)) } private fun initStorage(token: Long) = runBlocking { @@ -349,8 +222,4 @@ class PluginTest : KoinComponent { backupPlugin.initializeDevice() } - private fun isNextcloud(): Boolean { - return backupPlugin.providerPackageName?.startsWith("com.nextcloud") ?: false - } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderBackupPlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderBackupPlugin.kt index 3135c74f..7f64a620 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderBackupPlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderBackupPlugin.kt @@ -7,8 +7,6 @@ import android.util.Log import androidx.documentfile.provider.DocumentFile import com.stevesoltys.seedvault.transport.backup.BackupPlugin 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.IOException import java.io.InputStream @@ -19,9 +17,7 @@ private val TAG = DocumentsProviderBackupPlugin::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") internal class DocumentsProviderBackupPlugin( private val context: Context, - private val storage: DocumentsStorage, - override val kvBackupPlugin: KVBackupPlugin, - override val fullBackupPlugin: FullBackupPlugin + private val storage: DocumentsStorage ) : BackupPlugin { private val packageManager: PackageManager = context.packageManager @@ -45,8 +41,7 @@ internal class DocumentsProviderBackupPlugin( storage.reset(null) // create backup folders - storage.currentKvBackupDir ?: throw IOException() - storage.currentFullBackupDir ?: throw IOException() + storage.currentSetDir ?: throw IOException() } @Throws(IOException::class) diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullBackup.kt deleted file mode 100644 index 5ef450bf..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullBackup.kt +++ /dev/null @@ -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") - } - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullRestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullRestorePlugin.kt index 2eef1943..639dd036 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullRestorePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderFullRestorePlugin.kt @@ -6,7 +6,8 @@ import com.stevesoltys.seedvault.transport.restore.FullRestorePlugin import java.io.IOException import java.io.InputStream -@Suppress("BlockingMethodInNonBlockingContext") +@Suppress("BlockingMethodInNonBlockingContext", "Deprecation") +@Deprecated("Use only for v0 restore") internal class DocumentsProviderFullRestorePlugin( private val context: Context, private val documentsStorage: DocumentsStorage diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVBackup.kt deleted file mode 100644 index 6b5d71a3..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVBackup.kt +++ /dev/null @@ -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? = 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 - } - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVRestorePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVRestorePlugin.kt index 648fed54..76e4c25e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVRestorePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderKVRestorePlugin.kt @@ -7,7 +7,8 @@ import com.stevesoltys.seedvault.transport.restore.KVRestorePlugin import java.io.IOException import java.io.InputStream -@Suppress("BlockingMethodInNonBlockingContext") +@Suppress("BlockingMethodInNonBlockingContext", "Deprecation") +@Deprecated("Use only for v0 restore") internal class DocumentsProviderKVRestorePlugin( private val context: Context, private val storage: DocumentsStorage diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt index f2c310af..b3ff0d4e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt @@ -1,8 +1,6 @@ package com.stevesoltys.seedvault.plugins.saf 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.KVRestorePlugin import com.stevesoltys.seedvault.transport.restore.RestorePlugin @@ -12,9 +10,7 @@ import org.koin.dsl.module val documentsProviderModule = module { single { DocumentsStorage(androidContext(), get()) } - single { DocumentsProviderKVBackup(androidContext(), get()) } - single { DocumentsProviderFullBackup(androidContext(), get()) } - single { DocumentsProviderBackupPlugin(androidContext(), get(), get(), get()) } + single { DocumentsProviderBackupPlugin(androidContext(), get()) } single { DocumentsProviderKVRestorePlugin(androidContext(), get()) } single { DocumentsProviderFullRestorePlugin(androidContext(), get()) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt index ddda2c73..7b9c5698 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt @@ -27,7 +27,9 @@ import java.io.OutputStream import kotlin.coroutines.resume const val DIRECTORY_ROOT = ".SeedVaultAndroidBackup" +@Deprecated("") const val DIRECTORY_FULL_BACKUP = "full" +@Deprecated("") const val DIRECTORY_KEY_VALUE_BACKUP = "kv" const val FILE_BACKUP_METADATA = ".backup.metadata" const val FILE_NO_MEDIA = ".nomedia" @@ -73,7 +75,7 @@ internal class DocumentsStorage( return field } - private var currentSetDir: DocumentFile? = null + var currentSetDir: DocumentFile? = null get() = runBlocking { if (field == null) { if (currentToken == 0L) return@runBlocking null @@ -86,32 +88,7 @@ internal class DocumentsStorage( } field } - - 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 - } + private set /** * Resets this storage abstraction, forcing it to re-fetch cached values on next access. @@ -121,8 +98,6 @@ internal class DocumentsStorage( currentToken = newToken rootBackupDir = null currentSetDir = null - currentKvBackupDir = null - currentFullBackupDir = null } fun getAuthority(): String? = storage?.uri?.authority @@ -134,23 +109,16 @@ internal class DocumentsStorage( } @Throws(IOException::class) - suspend fun getKVBackupDir(token: Long = currentToken ?: error("no token")): DocumentFile? { - if (token == currentToken) return currentKvBackupDir ?: throw IOException() + @Suppress("Deprecation") + @Deprecated("Use only for v0 restore") + suspend fun getKVBackupDir(token: Long): DocumentFile? { return getSetDir(token)?.findFileBlocking(context, DIRECTORY_KEY_VALUE_BACKUP) } @Throws(IOException::class) - suspend fun getOrCreateKVBackupDir( - token: Long = currentToken ?: error("no token") - ): 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() + @Suppress("Deprecation") + @Deprecated("Use only for v0 restore") + suspend fun getFullBackupDir(token: Long): DocumentFile? { return getSetDir(token)?.findFileBlocking(context, DIRECTORY_FULL_BACKUP) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupPlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupPlugin.kt index 33d2024e..5157bd1e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupPlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupPlugin.kt @@ -9,12 +9,6 @@ import java.io.OutputStream 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. * diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackupPlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackupPlugin.kt deleted file mode 100644 index 1173cf7d..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackupPlugin.kt +++ /dev/null @@ -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) - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackupPlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackupPlugin.kt deleted file mode 100644 index 8a4cecc3..00000000 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackupPlugin.kt +++ /dev/null @@ -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) - -} diff --git a/app/src/sharedTest/java/com/stevesoltys/seedvault/TestUtils.kt b/app/src/sharedTest/java/com/stevesoltys/seedvault/TestUtils.kt index c72c8304..ef5cea1a 100644 --- a/app/src/sharedTest/java/com/stevesoltys/seedvault/TestUtils.kt +++ b/app/src/sharedTest/java/com/stevesoltys/seedvault/TestUtils.kt @@ -1,6 +1,5 @@ package com.stevesoltys.seedvault -import com.stevesoltys.seedvault.plugins.saf.MAX_KEY_LENGTH_NEXTCLOUD import kotlinx.coroutines.runBlocking import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals @@ -32,7 +31,7 @@ fun getRandomString(size: Int = Random.nextInt(1, 255)): String { private val base64CharPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') // + '+' + '_' + '=' @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) .map { Random.nextInt(0, base64CharPool.size) } .map(base64CharPool::get) diff --git a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/BackupPluginTest.kt b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/BackupPluginTest.kt index 61a56bd4..fa8b2e50 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/BackupPluginTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/BackupPluginTest.kt @@ -2,8 +2,6 @@ package com.stevesoltys.seedvault.plugins.saf import androidx.documentfile.provider.DocumentFile 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.coEvery import io.mockk.every @@ -17,19 +15,11 @@ import org.junit.jupiter.api.Test internal class BackupPluginTest : BackupTest() { private val storage = mockk() - private val kvBackupPlugin: KVBackupPlugin = mockk() - private val fullBackupPlugin: FullBackupPlugin = mockk() - private val plugin = DocumentsProviderBackupPlugin( - context, - storage, - kvBackupPlugin, - fullBackupPlugin - ) + private val plugin = DocumentsProviderBackupPlugin(context, storage) private val setDir: DocumentFile = mockk() - private val kvDir: DocumentFile = mockk() - private val fullDir: DocumentFile = mockk() + private val backupFile: DocumentFile = mockk() init { // to mock extension functions on DocumentFile @@ -51,13 +41,12 @@ internal class BackupPluginTest : BackupTest() { every { settingsManager.getToken() } returns token coEvery { storage.getSetDir(token) } returns setDir // delete contents of current set dir - coEvery { setDir.listFilesBlocking(context) } returns listOf(kvDir) - every { kvDir.delete() } returns true + coEvery { setDir.listFilesBlocking(context) } returns listOf(backupFile) + every { backupFile.delete() } returns true // reset storage every { storage.reset(null) } just Runs - // create kv and full dir - every { storage getProperty "currentKvBackupDir" } returns kvDir - every { storage getProperty "currentFullBackupDir" } returns fullDir + // create new set dir + every { storage getProperty "currentSetDir" } returns setDir plugin.initializeDevice() }