diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt index 6c0fbdf54..6212e8558 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt @@ -56,7 +56,7 @@ class KoinInstrumentationTestApp : App() { apkRestore = get(), iconManager = get(), storageBackup = get(), - pluginManager = get(), + backendManager = get(), fileSelectionManager = get(), ) ) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt index 10039f4a5..b5313c013 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/PluginTest.kt @@ -9,16 +9,17 @@ import androidx.test.core.content.pm.PackageInfoBuilder import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderLegacyPlugin -import com.stevesoltys.seedvault.plugins.saf.DocumentsProviderStoragePlugin -import com.stevesoltys.seedvault.plugins.saf.DocumentsStorage -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.getAvailableBackups +import com.stevesoltys.seedvault.backend.saf.DocumentsProviderLegacyPlugin +import com.stevesoltys.seedvault.backend.saf.DocumentsStorage import com.stevesoltys.seedvault.settings.SettingsManager import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import org.calyxos.seedvault.core.backends.LegacyAppBackupFile +import org.calyxos.seedvault.core.backends.saf.SafBackend import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -38,11 +39,10 @@ class PluginTest : KoinComponent { private val mockedSettingsManager: SettingsManager = mockk() private val storage = DocumentsStorage( appContext = context, - settingsManager = mockedSettingsManager, - safStorage = settingsManager.getSafStorage() ?: error("No SAF storage"), + safStorage = settingsManager.getSafProperties() ?: error("No SAF storage"), ) - private val storagePlugin = DocumentsProviderStoragePlugin(context, storage.safStorage) + private val backend = SafBackend(context, storage.safStorage) @Suppress("Deprecation") private val legacyStoragePlugin: LegacyStoragePlugin = DocumentsProviderLegacyPlugin(context) { @@ -55,29 +55,30 @@ class PluginTest : KoinComponent { @Before fun setup() = runBlocking { - every { mockedSettingsManager.getSafStorage() } returns settingsManager.getSafStorage() - storagePlugin.removeAll() + every { + mockedSettingsManager.getSafProperties() + } returns settingsManager.getSafProperties() + backend.removeAll() } @After fun tearDown() = runBlocking { - storagePlugin.removeAll() - Unit + backend.removeAll() } @Test fun testProviderPackageName() { - assertNotNull(storagePlugin.providerPackageName) + assertNotNull(backend.providerPackageName) } @Test fun testTest() = runBlocking(Dispatchers.IO) { - assertTrue(storagePlugin.test()) + assertTrue(backend.test()) } @Test fun testGetFreeSpace() = runBlocking(Dispatchers.IO) { - val freeBytes = storagePlugin.getFreeSpace() ?: error("no free space retrieved") + val freeBytes = backend.getFreeSpace() ?: error("no free space retrieved") assertTrue(freeBytes > 0) } @@ -91,49 +92,39 @@ class PluginTest : KoinComponent { @Test fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) { // no backups available initially - assertEquals(0, storagePlugin.getAvailableBackups()?.toList()?.size) + assertEquals(0, backend.getAvailableBackups()?.toList()?.size) // prepare returned tokens requested when initializing device every { mockedSettingsManager.getToken() } returnsMany listOf(token, token + 1, token + 1) - // start new restore set and initialize device afterwards - storagePlugin.startNewRestoreSet(token) - storagePlugin.initializeDevice() - // write metadata (needed for backup to be recognized) - storagePlugin.getOutputStream(token, FILE_BACKUP_METADATA) + backend.save(LegacyAppBackupFile.Metadata(token)) .writeAndClose(getRandomByteArray()) // one backup available now - assertEquals(1, storagePlugin.getAvailableBackups()?.toList()?.size) + assertEquals(1, backend.getAvailableBackups()?.toList()?.size) // initializing again (with another restore set) does add a restore set - storagePlugin.startNewRestoreSet(token + 1) - storagePlugin.initializeDevice() - storagePlugin.getOutputStream(token + 1, FILE_BACKUP_METADATA) + backend.save(LegacyAppBackupFile.Metadata(token + 1)) .writeAndClose(getRandomByteArray()) - assertEquals(2, storagePlugin.getAvailableBackups()?.toList()?.size) + assertEquals(2, backend.getAvailableBackups()?.toList()?.size) // initializing again (without new restore set) doesn't change number of restore sets - storagePlugin.initializeDevice() - storagePlugin.getOutputStream(token + 1, FILE_BACKUP_METADATA) + backend.save(LegacyAppBackupFile.Metadata(token + 1)) .writeAndClose(getRandomByteArray()) - assertEquals(2, storagePlugin.getAvailableBackups()?.toList()?.size) + assertEquals(2, backend.getAvailableBackups()?.toList()?.size) } @Test fun testMetadataWriteRead() = runBlocking(Dispatchers.IO) { every { mockedSettingsManager.getToken() } returns token - storagePlugin.startNewRestoreSet(token) - storagePlugin.initializeDevice() - // write metadata val metadata = getRandomByteArray() - storagePlugin.getOutputStream(token, FILE_BACKUP_METADATA).writeAndClose(metadata) + backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata) // get available backups, expect only one with our token and no error - var availableBackups = storagePlugin.getAvailableBackups()?.toList() + var availableBackups = backend.getAvailableBackups()?.toList() check(availableBackups != null) assertEquals(1, availableBackups.size) assertEquals(token, availableBackups[0].token) @@ -142,9 +133,8 @@ class PluginTest : KoinComponent { assertReadEquals(metadata, availableBackups[0].inputStreamRetriever()) // initializing again (without changing storage) keeps restore set with same token - storagePlugin.initializeDevice() - storagePlugin.getOutputStream(token, FILE_BACKUP_METADATA).writeAndClose(metadata) - availableBackups = storagePlugin.getAvailableBackups()?.toList() + backend.save(LegacyAppBackupFile.Metadata(token)).writeAndClose(metadata) + availableBackups = backend.getAvailableBackups()?.toList() check(availableBackups != null) assertEquals(1, availableBackups.size) assertEquals(token, availableBackups[0].token) @@ -161,7 +151,8 @@ class PluginTest : KoinComponent { // write random bytes as APK val apk1 = getRandomByteArray(1337 * 1024) - storagePlugin.getOutputStream(token, "${packageInfo.packageName}.apk").writeAndClose(apk1) + backend.save(LegacyAppBackupFile.Blob(token, "${packageInfo.packageName}.apk")) + .writeAndClose(apk1) // assert that read APK bytes match what was written assertReadEquals( @@ -173,7 +164,7 @@ class PluginTest : KoinComponent { val suffix2 = getRandomBase64(23) val apk2 = getRandomByteArray(23 * 1024 * 1024) - storagePlugin.getOutputStream(token, "${packageInfo2.packageName}$suffix2.apk") + backend.save(LegacyAppBackupFile.Blob(token, "${packageInfo2.packageName}$suffix2.apk")) .writeAndClose(apk2) // assert that read APK bytes match what was written @@ -193,26 +184,25 @@ class PluginTest : KoinComponent { // write full backup data val data = getRandomByteArray(5 * 1024 * 1024) - storagePlugin.getOutputStream(token, name1).writeAndClose(data) + backend.save(LegacyAppBackupFile.Blob(token, name1)).writeAndClose(data) // restore data matches backed up data - assertReadEquals(data, storagePlugin.getInputStream(token, name1)) + assertReadEquals(data, backend.load(LegacyAppBackupFile.Blob(token, name1))) // write and check data for second package val data2 = getRandomByteArray(5 * 1024 * 1024) - storagePlugin.getOutputStream(token, name2).writeAndClose(data2) - assertReadEquals(data2, storagePlugin.getInputStream(token, name2)) + backend.save(LegacyAppBackupFile.Blob(token, name2)).writeAndClose(data2) + assertReadEquals(data2, backend.load(LegacyAppBackupFile.Blob(token, name2))) // remove data of first package again and ensure that no more data is found - storagePlugin.removeData(token, name1) + backend.remove(LegacyAppBackupFile.Blob(token, name1)) // ensure that it gets deleted as well - storagePlugin.removeData(token, name2) + backend.remove(LegacyAppBackupFile.Blob(token, name2)) } private fun initStorage(token: Long) = runBlocking { every { mockedSettingsManager.getToken() } returns token - storagePlugin.initializeDevice() } } diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/plugins/saf/SafBackendTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/backend/saf/SafBackendTest.kt similarity index 80% rename from app/src/androidTest/java/com/stevesoltys/seedvault/plugins/saf/SafBackendTest.kt rename to app/src/androidTest/java/com/stevesoltys/seedvault/backend/saf/SafBackendTest.kt index 8442332a9..e42fd4cb7 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/plugins/saf/SafBackendTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/backend/saf/SafBackendTest.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest @@ -13,7 +13,7 @@ import kotlinx.coroutines.runBlocking import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.BackendTest import org.calyxos.seedvault.core.backends.saf.SafBackend -import org.calyxos.seedvault.core.backends.saf.SafConfig +import org.calyxos.seedvault.core.backends.saf.SafProperties import org.junit.Test import org.junit.runner.RunWith import org.koin.core.component.KoinComponent @@ -27,15 +27,15 @@ class SafBackendTest : BackendTest(), KoinComponent { private val settingsManager by inject() override val plugin: Backend get() { - val safStorage = settingsManager.getSafStorage() ?: error("No SAF storage") - val safConfig = SafConfig( + val safStorage = settingsManager.getSafProperties() ?: error("No SAF storage") + val safProperties = SafProperties( config = safStorage.config, name = safStorage.name, isUsb = safStorage.isUsb, requiresNetwork = safStorage.requiresNetwork, rootId = safStorage.rootId, ) - return SafBackend(context, safConfig, ".SeedvaultTest") + return SafBackend(context, safProperties, ".SeedvaultTest") } @Test diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt index 7de32560f..3017a83d1 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt @@ -23,7 +23,7 @@ internal class BackupRestoreTest : SeedvaultLargeTest() { confirmCode() } - if (settingsManager.getSafStorage() == null) { + if (settingsManager.getSafProperties() == null) { chooseStorageLocation() } else { changeBackupLocation() diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt index 971b07ad4..2231ef0c9 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/transport/backup/PackageServiceTest.kt @@ -9,7 +9,7 @@ import android.content.pm.PackageInfo import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.settings.AppStatus import com.stevesoltys.seedvault.settings.SettingsManager import io.mockk.every @@ -30,9 +30,9 @@ class PackageServiceTest : KoinComponent { private val settingsManager: SettingsManager by inject() - private val storagePluginManager: StoragePluginManager by inject() + private val backendManager: BackendManager by inject() - private val backend: Backend get() = storagePluginManager.backend + private val backend: Backend get() = backendManager.backend @Test fun testNotAllowedPackages() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index aea38f1c3..a62710ecc 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -24,9 +24,9 @@ import com.stevesoltys.seedvault.crypto.cryptoModule import com.stevesoltys.seedvault.header.headerModule import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.metadata.metadataModule -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.storagePluginModuleSaf -import com.stevesoltys.seedvault.plugins.webdav.storagePluginModuleWebDav +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.saf.storagePluginModuleSaf +import com.stevesoltys.seedvault.backend.webdav.storagePluginModuleWebDav import com.stevesoltys.seedvault.restore.install.installModule import com.stevesoltys.seedvault.restore.restoreUiModule import com.stevesoltys.seedvault.settings.AppListRetriever @@ -42,6 +42,7 @@ import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel import com.stevesoltys.seedvault.ui.storage.RestoreStorageViewModel import com.stevesoltys.seedvault.worker.AppBackupWorker import com.stevesoltys.seedvault.worker.workerModule +import org.calyxos.seedvault.core.backends.BackendFactory import org.koin.android.ext.android.inject import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger @@ -61,7 +62,8 @@ open class App : Application() { private val appModule = module { single { SettingsManager(this@App) } single { BackupNotificationManager(this@App) } - single { StoragePluginManager(this@App, get(), get(), get()) } + single { BackendManager(this@App, get(), get()) } + single { BackendFactory(this@App) } single { BackupStateManager(this@App) } single { Clock() } factory { IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) } @@ -72,7 +74,7 @@ open class App : Application() { app = this@App, settingsManager = get(), keyManager = get(), - pluginManager = get(), + backendManager = get(), metadataManager = get(), appListRetriever = get(), storageBackup = get(), @@ -91,7 +93,7 @@ open class App : Application() { safHandler = get(), webDavHandler = get(), settingsManager = get(), - storagePluginManager = get(), + backendManager = get(), ) } viewModel { RestoreStorageViewModel(this@App, get(), get(), get(), get()) } @@ -146,7 +148,7 @@ open class App : Application() { private val settingsManager: SettingsManager by inject() private val metadataManager: MetadataManager by inject() private val backupManager: IBackupManager by inject() - private val pluginManager: StoragePluginManager by inject() + private val backendManager: BackendManager by inject() private val backupStateManager: BackupStateManager by inject() /** @@ -170,13 +172,13 @@ open class App : Application() { protected open fun migrateToOwnScheduling() { if (!backupStateManager.isFrameworkSchedulingEnabled) { // already on own scheduling // fix things for removable drive users who had a job scheduled here before - if (pluginManager.isOnRemovableDrive) AppBackupWorker.unschedule(applicationContext) + if (backendManager.isOnRemovableDrive) AppBackupWorker.unschedule(applicationContext) return } if (backupManager.currentTransport == TRANSPORT_ID) { backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), false) - if (backupManager.isBackupEnabled && !pluginManager.isOnRemovableDrive) { + if (backupManager.isBackupEnabled && !backendManager.isOnRemovableDrive) { AppBackupWorker.schedule(applicationContext, settingsManager, UPDATE) } // cancel old D2D worker diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/BackendExt.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendExt.kt similarity index 70% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/BackendExt.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/BackendExt.kt index ffd3909e5..ffe16ba6a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/BackendExt.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendExt.kt @@ -3,11 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins +package com.stevesoltys.seedvault.backend import android.util.Log +import at.bitfire.dav4jvm.exception.HttpException import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.LegacyAppBackupFile +import java.io.IOException +import java.io.InputStream import java.io.OutputStream suspend fun Backend.getMetadataOutputStream(token: Long): OutputStream { @@ -35,3 +38,16 @@ suspend fun Backend.getAvailableBackups(): Sequence? { null } } + +fun Exception.isOutOfSpace(): Boolean { + return when (this) { + is IOException -> message?.contains("No space left on device") == true || + (cause as? HttpException)?.code == 507 + + is HttpException -> code == 507 + + else -> false + } +} + +class EncryptedMetadata(val token: Long, val inputStreamRetriever: suspend () -> InputStream) diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePluginManager.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendManager.kt similarity index 63% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePluginManager.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/BackendManager.kt index 5dacebc6b..545339a55 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePluginManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/BackendManager.kt @@ -3,30 +3,28 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins +package com.stevesoltys.seedvault.backend import android.content.Context import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.getStorageContext import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.saf.SafFactory -import com.stevesoltys.seedvault.plugins.webdav.WebDavFactory import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.StoragePluginType import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.BackendFactory +import org.calyxos.seedvault.core.backends.BackendProperties import org.calyxos.seedvault.core.backends.saf.SafBackend -class StoragePluginManager( +class BackendManager( private val context: Context, private val settingsManager: SettingsManager, - safFactory: SafFactory, - webDavFactory: WebDavFactory, + backendFactory: BackendFactory, ) { private var mBackend: Backend? - private var mFilesPlugin: org.calyxos.backup.storage.api.StoragePlugin? - private var mStorageProperties: StorageProperties<*>? + private var mBackendProperties: BackendProperties<*>? val backend: Backend @Synchronized @@ -34,48 +32,39 @@ class StoragePluginManager( return mBackend ?: error("App plugin was loaded, but still null") } - val filesPlugin: org.calyxos.backup.storage.api.StoragePlugin + val backendProperties: BackendProperties<*>? @Synchronized get() { - return mFilesPlugin ?: error("Files plugin was loaded, but still null") + return mBackendProperties } - - val storageProperties: StorageProperties<*>? - @Synchronized - get() { - return mStorageProperties - } - val isOnRemovableDrive: Boolean get() = storageProperties?.isUsb == true + val isOnRemovableDrive: Boolean get() = backendProperties?.isUsb == true init { when (settingsManager.storagePluginType) { StoragePluginType.SAF -> { - val safStorage = settingsManager.getSafStorage() ?: error("No SAF storage saved") - mBackend = safFactory.createBackend(safStorage) - mFilesPlugin = safFactory.createFilesStoragePlugin(safStorage) - mStorageProperties = safStorage + val safConfig = settingsManager.getSafProperties() ?: error("No SAF storage saved") + mBackend = backendFactory.createSafBackend(safConfig) + mBackendProperties = safConfig } StoragePluginType.WEB_DAV -> { val webDavProperties = settingsManager.webDavProperties ?: error("No WebDAV config saved") - mBackend = webDavFactory.createBackend(webDavProperties.config) - mFilesPlugin = webDavFactory.createFilesStoragePlugin(webDavProperties.config) - mStorageProperties = webDavProperties + mBackend = backendFactory.createWebDavBackend(webDavProperties.config) + mBackendProperties = webDavProperties } null -> { mBackend = null - mFilesPlugin = null - mStorageProperties = null + mBackendProperties = null } } } fun isValidAppPluginSet(): Boolean { - if (mBackend == null || mFilesPlugin == null) return false + if (mBackend == null) return false if (mBackend is SafBackend) { - val storage = settingsManager.getSafStorage() ?: return false + val storage = settingsManager.getSafProperties() ?: return false if (storage.isUsb) return true return permitDiskReads { storage.getDocumentFile(context).isDirectory @@ -85,20 +74,18 @@ class StoragePluginManager( } /** - * Changes the storage plugins and current [StorageProperties]. + * Changes the storage plugins and current [BackendProperties]. * * IMPORTANT: Do no call this while current plugins are being used, * e.g. while backup/restore operation is still running. */ fun changePlugins( - storageProperties: StorageProperties, backend: Backend, - filesPlugin: org.calyxos.backup.storage.api.StoragePlugin, + storageProperties: BackendProperties, ) { settingsManager.setStorageBackend(backend) - mStorageProperties = storageProperties mBackend = backend - mFilesPlugin = filesPlugin + mBackendProperties = storageProperties } /** @@ -111,7 +98,7 @@ class StoragePluginManager( */ @WorkerThread fun canDoBackupNow(): Boolean { - val storage = storageProperties ?: return false + val storage = backendProperties ?: return false return !isOnUnavailableUsb() && !storage.isUnavailableNetwork(context, settingsManager.useMeteredNetwork) } @@ -126,7 +113,7 @@ class StoragePluginManager( */ @WorkerThread fun isOnUnavailableUsb(): Boolean { - val storage = storageProperties ?: return false + val storage = backendProperties ?: return false val systemContext = context.getStorageContext { storage.isUsb } return storage.isUnavailableUsb(systemContext) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/LegacyStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/LegacyStoragePlugin.kt similarity index 97% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/LegacyStoragePlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/LegacyStoragePlugin.kt index 2f7a4cbae..3d030934e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/LegacyStoragePlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/LegacyStoragePlugin.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins +package com.stevesoltys.seedvault.backend import android.content.pm.PackageInfo import java.io.IOException diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderLegacyPlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsProviderLegacyPlugin.kt similarity index 97% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderLegacyPlugin.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsProviderLegacyPlugin.kt index 5fdc08124..2d849887d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderLegacyPlugin.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsProviderLegacyPlugin.kt @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf import android.content.Context import android.content.pm.PackageInfo import androidx.annotation.WorkerThread import androidx.documentfile.provider.DocumentFile -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsProviderModule.kt similarity index 62% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsProviderModule.kt index 1255d7a02..0689e18f8 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsProviderModule.kt @@ -3,15 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin import com.stevesoltys.seedvault.settings.SettingsManager import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val storagePluginModuleSaf = module { - single { SafFactory(androidContext()) } single { SafHandler(androidContext(), get(), get(), get()) } @Suppress("Deprecation") @@ -19,8 +18,9 @@ val storagePluginModuleSaf = module { DocumentsProviderLegacyPlugin( context = androidContext(), storageGetter = { - val safStorage = get().getSafStorage() ?: error("No SAF storage") - DocumentsStorage(androidContext(), get(), safStorage) + val safProperties = get().getSafProperties() + ?: error("No SAF storage") + DocumentsStorage(androidContext(), safProperties) }, ) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsStorage.kt similarity index 95% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsStorage.kt index e7e4f555c..1f61dbd05 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsStorage.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/DocumentsStorage.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf import android.content.ContentResolver import android.content.Context @@ -20,34 +20,29 @@ import android.util.Log import androidx.annotation.VisibleForTesting import androidx.documentfile.provider.DocumentFile import com.stevesoltys.seedvault.getStorageContext -import com.stevesoltys.seedvault.settings.SettingsManager import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.runBlocking import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout +import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT +import org.calyxos.seedvault.core.backends.saf.SafProperties import org.calyxos.seedvault.core.backends.saf.getTreeDocumentFile import java.io.IOException import java.io.InputStream 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" -const val MIME_TYPE = "application/octet-stream" private val TAG = DocumentsStorage::class.java.simpleName internal class DocumentsStorage( private val appContext: Context, - private val settingsManager: SettingsManager, - internal val safStorage: SafStorage, + internal val safStorage: SafProperties, ) { /** diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt similarity index 62% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafHandler.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt index 4b3f6b6f8..b9db52f52 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafHandler.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf import android.content.Context import android.content.Context.USB_SERVICE @@ -14,34 +14,42 @@ import android.net.Uri import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.getAvailableBackups import com.stevesoltys.seedvault.isMassStorage -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.getAvailableBackups import com.stevesoltys.seedvault.settings.FlashDrive import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.storage.StorageOption +import org.calyxos.seedvault.core.backends.BackendFactory +import org.calyxos.seedvault.core.backends.saf.SafProperties import java.io.IOException private const val TAG = "SafHandler" internal class SafHandler( private val context: Context, - private val safFactory: SafFactory, + private val backendFactory: BackendFactory, private val settingsManager: SettingsManager, - private val storagePluginManager: StoragePluginManager, + private val backendManager: BackendManager, ) { - fun onConfigReceived(uri: Uri, safOption: StorageOption.SafOption): SafStorage { + fun onConfigReceived(uri: Uri, safOption: StorageOption.SafOption): SafProperties { // persist permission to access backup folder across reboots val takeFlags = FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION context.contentResolver.takePersistableUriPermission(uri, takeFlags) - val name = if (safOption.isInternal()) { - "${safOption.title} (${context.getString(R.string.settings_backup_location_internal)})" - } else { - safOption.title - } - return SafStorage(uri, name, safOption.isUsb, safOption.requiresNetwork, safOption.rootId) + return SafProperties( + config = uri, + name = if (safOption.isInternal()) { + val brackets = context.getString(R.string.settings_backup_location_internal) + "${safOption.title} ($brackets)" + } else { + safOption.title + }, + isUsb = safOption.isUsb, + requiresNetwork = safOption.requiresNetwork, + rootId = safOption.rootId, + ) } /** @@ -50,16 +58,16 @@ internal class SafHandler( */ @WorkerThread @Throws(IOException::class) - suspend fun hasAppBackup(safStorage: SafStorage): Boolean { - val appPlugin = safFactory.createBackend(safStorage) + suspend fun hasAppBackup(safProperties: SafProperties): Boolean { + val appPlugin = backendFactory.createSafBackend(safProperties) val backups = appPlugin.getAvailableBackups() return backups != null && backups.iterator().hasNext() } - fun save(safStorage: SafStorage) { - settingsManager.setSafStorage(safStorage) + fun save(safProperties: SafProperties) { + settingsManager.setSafProperties(safProperties) - if (safStorage.isUsb) { + if (safProperties.isUsb) { Log.d(TAG, "Selected storage is a removable USB device.") val wasSaved = saveUsbDevice() // reset stored flash drive, if we did not update it @@ -67,7 +75,7 @@ internal class SafHandler( } else { settingsManager.setFlashDrive(null) } - Log.d(TAG, "New storage location saved: ${safStorage.uri}") + Log.d(TAG, "New storage location saved: ${safProperties.uri}") } private fun saveUsbDevice(): Boolean { @@ -84,11 +92,10 @@ internal class SafHandler( return false } - fun setPlugin(safStorage: SafStorage) { - storagePluginManager.changePlugins( - storageProperties = safStorage, - backend = safFactory.createBackend(safStorage), - filesPlugin = safFactory.createFilesStoragePlugin(safStorage), + fun setPlugin(safProperties: SafProperties) { + backendManager.changePlugins( + backend = backendFactory.createSafBackend(safProperties), + storageProperties = safProperties, ) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorageOptions.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafStorageOptions.kt similarity index 98% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorageOptions.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafStorageOptions.kt index b20511c14..73fb3bf86 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorageOptions.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/SafStorageOptions.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf import android.content.Context import android.content.Intent @@ -14,7 +14,7 @@ import android.provider.DocumentsContract import android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME import android.provider.DocumentsContract.Document.COLUMN_DOCUMENT_ID import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.saf.StorageRootResolver.getIcon +import com.stevesoltys.seedvault.backend.saf.StorageRootResolver.getIcon import com.stevesoltys.seedvault.ui.storage.AUTHORITY_DAVX5 import com.stevesoltys.seedvault.ui.storage.AUTHORITY_NEXTCLOUD import com.stevesoltys.seedvault.ui.storage.AUTHORITY_ROUND_SYNC diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/StorageRootResolver.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/StorageRootResolver.kt similarity index 99% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/saf/StorageRootResolver.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/saf/StorageRootResolver.kt index 605fdf872..12dc1dc5d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/StorageRootResolver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/saf/StorageRootResolver.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf import android.Manifest.permission.MANAGE_DOCUMENTS import android.content.Context diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavHandler.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt similarity index 85% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavHandler.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt index 904cc9704..17d6894a6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavHandler.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavHandler.kt @@ -3,20 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.webdav +package com.stevesoltys.seedvault.backend.webdav import android.content.Context import android.util.Log import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.getAvailableBackups +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.getAvailableBackups import com.stevesoltys.seedvault.settings.SettingsManager import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import okhttp3.HttpUrl.Companion.toHttpUrl import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.BackendFactory import org.calyxos.seedvault.core.backends.webdav.WebDavConfig +import org.calyxos.seedvault.core.backends.webdav.WebDavProperties import java.io.IOException internal sealed interface WebDavConfigState { @@ -34,9 +36,9 @@ private val TAG = WebDavHandler::class.java.simpleName internal class WebDavHandler( private val context: Context, - private val webDavFactory: WebDavFactory, + private val backendFactory: BackendFactory, private val settingsManager: SettingsManager, - private val storagePluginManager: StoragePluginManager, + private val backendManager: BackendManager, ) { companion object { @@ -54,7 +56,7 @@ internal class WebDavHandler( suspend fun onConfigReceived(config: WebDavConfig) { mConfigState.value = WebDavConfigState.Checking - val backend = webDavFactory.createBackend(config) + val backend = backendFactory.createWebDavBackend(config) try { if (backend.test()) { val properties = createWebDavProperties(context, config) @@ -88,10 +90,9 @@ internal class WebDavHandler( } fun setPlugin(properties: WebDavProperties, backend: Backend) { - storagePluginManager.changePlugins( - storageProperties = properties, + backendManager.changePlugins( backend = backend, - filesPlugin = webDavFactory.createFilesStoragePlugin(properties.config), + storageProperties = properties, ) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavModule.kt b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavModule.kt similarity index 74% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavModule.kt rename to app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavModule.kt index cc50a1573..f0fe8910f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/backend/webdav/WebDavModule.kt @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.webdav +package com.stevesoltys.seedvault.backend.webdav import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val storagePluginModuleWebDav = module { - single { WebDavFactory(androidContext()) } single { WebDavHandler(androidContext(), get(), get(), get()) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePlugin.kt deleted file mode 100644 index e719e3572..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/StoragePlugin.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins - -import android.app.backup.RestoreSet -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream - -interface StoragePlugin { - - /** - * Returns true if the plugin is working, or false if it isn't. - * @throws Exception any kind of exception to provide more info on the error - */ - suspend fun test(): Boolean - - /** - * Retrieves the available storage space in bytes. - * @return the number of bytes available or null if the number is unknown. - * Returning a negative number or zero to indicate unknown is discouraged. - */ - suspend fun getFreeSpace(): Long? - - /** - * Start a new [RestoreSet] with the given token. - * - * This is typically followed by a call to [initializeDevice]. - */ - @Throws(IOException::class) - suspend fun startNewRestoreSet(token: Long) - - /** - * Initialize the storage for this device, erasing all stored data in the current [RestoreSet]. - */ - @Throws(IOException::class) - suspend fun initializeDevice() - - /** - * Return a raw byte stream for writing data for the given name. - */ - @Throws(IOException::class) - suspend fun getOutputStream(token: Long, name: String): OutputStream - - /** - * Return a raw byte stream with data for the given name. - */ - @Throws(IOException::class) - suspend fun getInputStream(token: Long, name: String): InputStream - - /** - * Remove all data associated with the given name. - */ - @Throws(IOException::class) - suspend fun removeData(token: Long, name: String) - - /** - * Get the set of all backups currently available for restore. - * - * @return metadata for the set of restore images available, - * or null if an error occurred (the attempt should be rescheduled). - **/ - suspend fun getAvailableBackups(): Sequence? - - /** - * Returns the package name of the app that provides the backend storage - * which is used for the current backup location. - * - * Plugins are advised to cache this as it will be requested frequently. - * - * @return null if no package name could be found - */ - val providerPackageName: String? - -} - -class EncryptedMetadata(val token: Long, val inputStreamRetriever: suspend () -> InputStream) - -internal val tokenRegex = Regex("([0-9]{13})") // good until the year 2286 -internal val chunkFolderRegex = Regex("[a-f0-9]{2}") diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/StorageProperties.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/StorageProperties.kt deleted file mode 100644 index 5fa3cfd41..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/StorageProperties.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins - -import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET -import androidx.annotation.WorkerThread -import at.bitfire.dav4jvm.exception.HttpException -import java.io.IOException - -abstract class StorageProperties { - abstract val config: T - abstract val name: String - abstract val isUsb: Boolean - abstract val requiresNetwork: Boolean - - @WorkerThread - abstract fun isUnavailableUsb(context: Context): Boolean - - /** - * Returns true if this is storage that requires network access, - * but it isn't available right now. - */ - fun isUnavailableNetwork(context: Context, allowMetered: Boolean): Boolean { - return requiresNetwork && !hasUnmeteredInternet(context, allowMetered) - } - - private fun hasUnmeteredInternet(context: Context, allowMetered: Boolean): Boolean { - val cm = context.getSystemService(ConnectivityManager::class.java) ?: return false - val isMetered = cm.isActiveNetworkMetered - val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false - return capabilities.hasCapability(NET_CAPABILITY_INTERNET) && (allowMetered || !isMetered) - } -} - -fun Exception.isOutOfSpace(): Boolean { - return when (this) { - is IOException -> message?.contains("No space left on device") == true || - (cause as? HttpException)?.code == 507 - - is HttpException -> code == 507 - - else -> false - } -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt deleted file mode 100644 index 3c83eda09..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/DocumentsProviderStoragePlugin.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.saf - -import android.content.Context -import android.net.Uri -import android.util.Log -import com.stevesoltys.seedvault.plugins.EncryptedMetadata -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.worker.FILE_BACKUP_ICONS -import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT -import org.calyxos.seedvault.core.backends.LegacyAppBackupFile -import org.calyxos.seedvault.core.backends.saf.SafBackend -import org.calyxos.seedvault.core.backends.saf.SafConfig -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream - -private val TAG = DocumentsProviderStoragePlugin::class.java.simpleName - -internal class DocumentsProviderStoragePlugin( - appContext: Context, - safStorage: SafStorage, - root: String = DIRECTORY_ROOT, -) : StoragePlugin { - - private val safConfig = SafConfig( - config = safStorage.config, - name = safStorage.name, - isUsb = safStorage.isUsb, - requiresNetwork = safStorage.requiresNetwork, - rootId = safStorage.rootId, - ) - private val delegate: SafBackend = SafBackend(appContext, safConfig, root) - - override suspend fun test(): Boolean { - return delegate.test() - } - - override suspend fun getFreeSpace(): Long? { - return delegate.getFreeSpace() - } - - @Throws(IOException::class) - override suspend fun startNewRestoreSet(token: Long) { - // no-op - } - - @Throws(IOException::class) - override suspend fun initializeDevice() { - // no-op - } - - @Throws(IOException::class) - override suspend fun getOutputStream(token: Long, name: String): OutputStream { - val handle = when (name) { - FILE_BACKUP_METADATA -> LegacyAppBackupFile.Metadata(token) - FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) - else -> LegacyAppBackupFile.Blob(token, name) - } - return delegate.save(handle) - } - - @Throws(IOException::class) - override suspend fun getInputStream(token: Long, name: String): InputStream { - val handle = when (name) { - FILE_BACKUP_METADATA -> LegacyAppBackupFile.Metadata(token) - FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) - else -> LegacyAppBackupFile.Blob(token, name) - } - return delegate.load(handle) - } - - @Throws(IOException::class) - override suspend fun removeData(token: Long, name: String) { - val handle = when (name) { - FILE_BACKUP_METADATA -> LegacyAppBackupFile.Metadata(token) - FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) - else -> LegacyAppBackupFile.Blob(token, name) - } - delegate.remove(handle) - } - - override suspend fun getAvailableBackups(): Sequence? { - return try { - // get all restore set tokens in root folder that have a metadata file - val tokens = ArrayList() - delegate.list(null, LegacyAppBackupFile.Metadata::class) { fileInfo -> - val handle = fileInfo.fileHandle as LegacyAppBackupFile.Metadata - tokens.add(handle.token) - } - val tokenIterator = tokens.iterator() - return generateSequence { - if (!tokenIterator.hasNext()) return@generateSequence null // end sequence - val token = tokenIterator.next() - EncryptedMetadata(token) { - getInputStream(token, FILE_BACKUP_METADATA) - } - } - } catch (e: Exception) { - Log.e(TAG, "Error getting available backups: ", e) - null - } - } - - suspend fun removeAll() = delegate.removeAll() - - override val providerPackageName: String? get() = delegate.providerPackageName - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafFactory.kt deleted file mode 100644 index c87057539..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafFactory.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.saf - -import android.content.Context -import com.stevesoltys.seedvault.storage.SeedvaultSafStoragePlugin -import org.calyxos.seedvault.core.backends.Backend -import org.calyxos.seedvault.core.backends.saf.SafBackend - -class SafFactory( - private val context: Context, -) { - - internal fun createBackend(safStorage: SafStorage): Backend { - return SafBackend(context, safStorage.toSafConfig()) - } - - internal fun createFilesStoragePlugin( - safStorage: SafStorage, - ): org.calyxos.backup.storage.api.StoragePlugin { - return SeedvaultSafStoragePlugin(context, safStorage) - } - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorage.kt deleted file mode 100644 index 5018e5c56..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/saf/SafStorage.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.saf - -import android.content.Context -import android.net.Uri -import android.provider.DocumentsContract.Root.COLUMN_ROOT_ID -import androidx.annotation.WorkerThread -import androidx.documentfile.provider.DocumentFile -import com.stevesoltys.seedvault.plugins.StorageProperties -import org.calyxos.seedvault.core.backends.saf.SafConfig - -data class SafStorage( - override val config: Uri, - override val name: String, - override val isUsb: Boolean, - override val requiresNetwork: Boolean, - /** - * The [COLUMN_ROOT_ID] for the [uri]. - * This is only nullable for historic reasons, because we didn't always store it. - */ - val rootId: String?, -) : StorageProperties() { - - val uri: Uri = config - - fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, config) - ?: throw AssertionError("Should only happen on API < 21.") - - /** - * Returns true if this is USB storage that is not available, false otherwise. - * - * Must be run off UI thread (ideally I/O). - */ - @WorkerThread - override fun isUnavailableUsb(context: Context): Boolean { - return isUsb && !getDocumentFile(context).isDirectory - } - - fun toSafConfig() = SafConfig( - config = config, - name = name, - isUsb = isUsb, - requiresNetwork = requiresNetwork, - rootId = rootId, - ) -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavFactory.kt deleted file mode 100644 index 1b8859a49..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavFactory.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.webdav - -import android.annotation.SuppressLint -import android.content.Context -import android.provider.Settings -import org.calyxos.seedvault.core.backends.Backend -import org.calyxos.seedvault.core.backends.webdav.WebDavBackend -import org.calyxos.seedvault.core.backends.webdav.WebDavConfig - -class WebDavFactory( - private val context: Context, -) { - - fun createBackend(config: WebDavConfig): Backend = WebDavBackend(config) - - fun createFilesStoragePlugin( - config: WebDavConfig, - ): org.calyxos.backup.storage.api.StoragePlugin { - @SuppressLint("HardwareIds") - val androidId = - Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) - return com.stevesoltys.seedvault.storage.WebDavStoragePlugin( - androidId = androidId, - webDavConfig = config, - ) - } - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStorage.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStorage.kt deleted file mode 100644 index b71750060..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStorage.kt +++ /dev/null @@ -1,259 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.webdav - -import android.util.Log -import at.bitfire.dav4jvm.BasicDigestAuthHandler -import at.bitfire.dav4jvm.DavCollection -import at.bitfire.dav4jvm.MultiResponseCallback -import at.bitfire.dav4jvm.Property -import at.bitfire.dav4jvm.PropertyFactory -import at.bitfire.dav4jvm.PropertyRegistry -import at.bitfire.dav4jvm.Response -import at.bitfire.dav4jvm.Response.HrefRelation.SELF -import at.bitfire.dav4jvm.exception.HttpException -import at.bitfire.dav4jvm.property.webdav.DisplayName -import at.bitfire.dav4jvm.property.webdav.NS_WEBDAV -import at.bitfire.dav4jvm.property.webdav.ResourceType -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.runBlocking -import okhttp3.ConnectionSpec -import okhttp3.HttpUrl -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.RequestBody -import okio.BufferedSink -import org.calyxos.seedvault.core.backends.webdav.WebDavConfig -import org.xmlpull.v1.XmlPullParser -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.io.PipedInputStream -import java.io.PipedOutputStream -import java.util.concurrent.TimeUnit -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -const val DEBUG_LOG = true -const val DIRECTORY_ROOT = ".SeedVaultAndroidBackup" - -@OptIn(DelicateCoroutinesApi::class) -internal abstract class WebDavStorage( - webDavConfig: WebDavConfig, - root: String = DIRECTORY_ROOT, -) { - - companion object { - val TAG: String = WebDavStorage::class.java.simpleName - } - - private val authHandler = BasicDigestAuthHandler( - domain = null, // Optional, to only authenticate against hosts with this domain. - username = webDavConfig.username, - password = webDavConfig.password, - ) - protected val okHttpClient = OkHttpClient.Builder() - .followRedirects(false) - .authenticator(authHandler) - .addNetworkInterceptor(authHandler) - .connectTimeout(30, TimeUnit.SECONDS) - .writeTimeout(60, TimeUnit.SECONDS) - .readTimeout(240, TimeUnit.SECONDS) - .pingInterval(45, TimeUnit.SECONDS) - .connectionSpecs(listOf(ConnectionSpec.MODERN_TLS)) - .retryOnConnectionFailure(true) - .build() - - protected val baseUrl = webDavConfig.url - protected val url = "${webDavConfig.url}/$root" - - init { - PropertyRegistry.register(GetLastModified.Factory) - } - - @Throws(IOException::class) - protected suspend fun getOutputStream(location: HttpUrl): OutputStream { - val davCollection = DavCollection(okHttpClient, location) - - val pipedInputStream = PipedInputStream() - val pipedOutputStream = PipedCloseActionOutputStream(pipedInputStream) - - val body = object : RequestBody() { - override fun isOneShot(): Boolean = true - override fun contentType() = "application/octet-stream".toMediaType() - override fun writeTo(sink: BufferedSink) { - pipedInputStream.use { inputStream -> - sink.outputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - } - } - } - val deferred = GlobalScope.async(Dispatchers.IO) { - davCollection.put(body) { response -> - debugLog { "getOutputStream($location) = $response" } - } - } - pipedOutputStream.doOnClose { - runBlocking { // blocking i/o wait - deferred.await() - } - } - return pipedOutputStream - } - - @Throws(IOException::class) - protected fun getInputStream(location: HttpUrl): InputStream { - val davCollection = DavCollection(okHttpClient, location) - - val response = davCollection.get(accept = "", headers = null) - debugLog { "getInputStream($location) = $response" } - if (response.code / 100 != 2) throw IOException("HTTP error ${response.code}") - return response.body?.byteStream() ?: throw IOException() - } - - /** - * Tries to do [DavCollection.propfind] with a depth of `2` which is not in RFC4918. - * Since `infinity` isn't supported by nginx either, - * we fallback to iterating over all folders found with depth `1` - * and do another PROPFIND on those, passing the given [callback]. - */ - protected fun DavCollection.propfindDepthTwo(callback: MultiResponseCallback) { - try { - propfind( - depth = 2, // this isn't defined in RFC4918 - reqProp = arrayOf(DisplayName.NAME, ResourceType.NAME), - callback = callback, - ) - } catch (e: HttpException) { - if (e.isUnsupportedPropfind()) { - Log.i(TAG, "Got ${e.response}, trying two depth=1 PROPFINDs...") - propfindFakeTwo(callback) - } else { - throw e - } - } - } - - private fun DavCollection.propfindFakeTwo(callback: MultiResponseCallback) { - propfind( - depth = 1, - reqProp = arrayOf(DisplayName.NAME, ResourceType.NAME), - ) { response, relation -> - debugLog { "propFindFakeTwo() = $response" } - // This callback will be called for everything in the folder - callback.onResponse(response, relation) - if (relation != SELF && response.isFolder()) { - DavCollection(okHttpClient, response.href).propfind( - depth = 1, - reqProp = arrayOf(DisplayName.NAME, ResourceType.NAME), - callback = callback, - ) - } - } - } - - protected fun HttpException.isUnsupportedPropfind(): Boolean { - // nginx returns 400 for depth=2 - if (code == 400) { - return true - } - // lighttpd returns 403 with error as if we used infinity - if (code == 403 && responseBody?.contains("propfind-finite-depth") == true) { - return true - } - return false - } - - protected suspend fun DavCollection.createFolder(xmlBody: String? = null): okhttp3.Response { - return try { - suspendCoroutine { cont -> - mkCol(xmlBody) { response -> - cont.resume(response) - } - } - } catch (e: Exception) { - if (e is IOException) throw e - else throw IOException(e) - } - } - - protected inline fun debugLog(block: () -> String) { - if (DEBUG_LOG) Log.d(TAG, block()) - } - - protected fun Response.isFolder(): Boolean { - return this[ResourceType::class.java]?.types?.contains(ResourceType.COLLECTION) == true - } - - private class PipedCloseActionOutputStream( - inputStream: PipedInputStream, - ) : PipedOutputStream(inputStream) { - - private var onClose: (() -> Unit)? = null - - override fun write(b: Int) { - try { - super.write(b) - } catch (e: Exception) { - try { - onClose?.invoke() - } catch (closeException: Exception) { - e.addSuppressed(closeException) - } - throw e - } - } - - override fun write(b: ByteArray?, off: Int, len: Int) { - try { - super.write(b, off, len) - } catch (e: Exception) { - try { - onClose?.invoke() - } catch (closeException: Exception) { - e.addSuppressed(closeException) - } - throw e - } - } - - @Throws(IOException::class) - override fun close() { - super.close() - try { - onClose?.invoke() - } catch (e: Exception) { - if (e is IOException) throw e - else throw IOException(e) - } - } - - fun doOnClose(function: () -> Unit) { - this.onClose = function - } - } - -} - -/** - * A fake version of [at.bitfire.dav4jvm.property.webdav.GetLastModified] which we register - * so we don't need to depend on `org.apache.commons.lang3` which is used for date parsing. - */ -class GetLastModified : Property { - companion object { - @JvmField - val NAME = Property.Name(NS_WEBDAV, "getlastmodified") - } - - object Factory : PropertyFactory { - override fun getName() = NAME - override fun create(parser: XmlPullParser): GetLastModified? = null - } -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePlugin.kt deleted file mode 100644 index 47554dfdd..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePlugin.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.webdav - -import android.util.Log -import com.stevesoltys.seedvault.plugins.EncryptedMetadata -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA -import com.stevesoltys.seedvault.worker.FILE_BACKUP_ICONS -import org.calyxos.seedvault.core.backends.LegacyAppBackupFile -import org.calyxos.seedvault.core.backends.webdav.WebDavBackend -import org.calyxos.seedvault.core.backends.webdav.WebDavConfig -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream - -internal class WebDavStoragePlugin( - webDavConfig: WebDavConfig, - root: String = DIRECTORY_ROOT, -) : WebDavStorage(webDavConfig, root), StoragePlugin { - - private val delegate = WebDavBackend(webDavConfig, root) - - override suspend fun test(): Boolean { - return delegate.test() - } - - override suspend fun getFreeSpace(): Long? { - return delegate.getFreeSpace() - } - - @Throws(IOException::class) - override suspend fun startNewRestoreSet(token: Long) { - // no-op - } - - @Throws(IOException::class) - override suspend fun initializeDevice() { - // no-op - } - - @Throws(IOException::class) - override suspend fun getOutputStream(token: Long, name: String): OutputStream { - val handle = when (name) { - FILE_BACKUP_METADATA -> LegacyAppBackupFile.Metadata(token) - FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) - else -> LegacyAppBackupFile.Blob(token, name) - } - return delegate.save(handle) - } - - @Throws(IOException::class) - override suspend fun getInputStream(token: Long, name: String): InputStream { - val handle = when (name) { - FILE_BACKUP_METADATA -> LegacyAppBackupFile.Metadata(token) - FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) - else -> LegacyAppBackupFile.Blob(token, name) - } - return delegate.load(handle) - } - - @Throws(IOException::class) - override suspend fun removeData(token: Long, name: String) { - val handle = when (name) { - FILE_BACKUP_METADATA -> LegacyAppBackupFile.Metadata(token) - FILE_BACKUP_ICONS -> LegacyAppBackupFile.IconsFile(token) - else -> LegacyAppBackupFile.Blob(token, name) - } - delegate.remove(handle) - } - - override suspend fun getAvailableBackups(): Sequence? { - return try { - // get all restore set tokens in root folder that have a metadata file - val tokens = ArrayList() - delegate.list(null, LegacyAppBackupFile.Metadata::class) { fileInfo -> - val handle = fileInfo.fileHandle as LegacyAppBackupFile.Metadata - tokens.add(handle.token) - } - val tokenIterator = tokens.iterator() - return generateSequence { - if (!tokenIterator.hasNext()) return@generateSequence null // end sequence - val token = tokenIterator.next() - EncryptedMetadata(token) { - getInputStream(token, FILE_BACKUP_METADATA) - } - } - } catch (e: Throwable) { // NoClassDefFound isn't an [Exception], can get thrown by dav4jvm - Log.e(TAG, "Error getting available backups: ", e) - null - } - } - - override val providerPackageName: String? = null // 100% built-in plugin - -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt index eee17a762..e58edc89b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/AppDataRestoreManager.kt @@ -24,7 +24,7 @@ import com.stevesoltys.seedvault.NO_DATA_END_SENTINEL import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.metadata.PackageMetadataMap import com.stevesoltys.seedvault.metadata.PackageState -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.restore.install.isInstalled import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.transport.TRANSPORT_ID @@ -55,7 +55,7 @@ internal class AppDataRestoreManager( private val backupManager: IBackupManager, private val settingsManager: SettingsManager, private val restoreCoordinator: RestoreCoordinator, - private val storagePluginManager: StoragePluginManager, + private val backendManager: BackendManager, ) { private var session: IRestoreSession? = null @@ -99,7 +99,7 @@ internal class AppDataRestoreManager( return } - val providerPackageName = storagePluginManager.backend.providerPackageName + val providerPackageName = backendManager.backend.providerPackageName val observer = RestoreObserver( restoreCoordinator = restoreCoordinator, restorableBackup = restorableBackup, diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt index b46cb8282..22a0b4ee5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/AppSelectionManager.kt @@ -14,7 +14,7 @@ import com.stevesoltys.seedvault.NO_DATA_END_SENTINEL import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SYSTEM import com.stevesoltys.seedvault.ui.systemData import com.stevesoltys.seedvault.worker.IconManager @@ -37,7 +37,7 @@ private val TAG = AppSelectionManager::class.simpleName internal class AppSelectionManager( private val context: Context, - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, private val iconManager: IconManager, private val coroutineScope: CoroutineScope, private val workDispatcher: CoroutineDispatcher = Dispatchers.IO, @@ -88,7 +88,7 @@ internal class AppSelectionManager( SelectedAppsState(apps = items, allSelected = isSetupWizard, iconsLoaded = false) // download icons coroutineScope.launch(workDispatcher) { - val backend = pluginManager.backend + val backend = backendManager.backend val token = restorableBackup.token val packagesWithIcons = try { backend.load(LegacyAppBackupFile.IconsFile(token)).use { diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreUiModule.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreUiModule.kt index 41abcddff..e1f5a420e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreUiModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreUiModule.kt @@ -23,7 +23,7 @@ val restoreUiModule = module { apkRestore = get(), iconManager = get(), storageBackup = get(), - pluginManager = get(), + backendManager = get(), fileSelectionManager = get(), ) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index ccaca2956..3abfda9cb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -18,7 +18,7 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES @@ -65,19 +65,19 @@ internal class RestoreViewModel( private val apkRestore: ApkRestore, private val iconManager: IconManager, storageBackup: StorageBackup, - pluginManager: StoragePluginManager, + backendManager: BackendManager, override val fileSelectionManager: FileSelectionManager, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, -) : RequireProvisioningViewModel(app, settingsManager, keyManager, pluginManager), +) : RequireProvisioningViewModel(app, settingsManager, keyManager, backendManager), RestorableBackupClickListener, SnapshotViewModel { override val isRestoreOperation = true var isSetupWizard = false private val appSelectionManager = - AppSelectionManager(app, pluginManager, iconManager, viewModelScope) + AppSelectionManager(app, backendManager, iconManager, viewModelScope) private val appDataRestoreManager = AppDataRestoreManager( - app, backupManager, settingsManager, restoreCoordinator, pluginManager + app, backupManager, settingsManager, restoreCoordinator, backendManager ) private val mDisplayFragment = MutableLiveEvent() diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt index 303dcf7ca..6a3e14228 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/install/ApkRestore.kt @@ -16,9 +16,8 @@ import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.crypto.Crypto import com.stevesoltys.seedvault.metadata.ApkSplit import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP @@ -32,6 +31,7 @@ import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.File import java.io.IOException @@ -43,7 +43,7 @@ internal class ApkRestore( private val context: Context, private val backupManager: IBackupManager, private val backupStateManager: BackupStateManager, - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, @Suppress("Deprecation") private val legacyStoragePlugin: LegacyStoragePlugin, private val crypto: Crypto, @@ -53,7 +53,7 @@ internal class ApkRestore( ) { private val pm = context.packageManager - private val backend get() = pluginManager.backend + private val backend get() = backendManager.backend private val mInstallResult = MutableStateFlow(InstallResult()) val installResult = mInstallResult.asStateFlow() @@ -224,7 +224,7 @@ internal class ApkRestore( } /** - * Retrieves APK splits from [StoragePlugin] and caches them locally. + * Retrieves APK splits from [Backend] and caches them locally. * * @throws SecurityException if a split has an unexpected SHA-256 hash. * @return a list of all APKs that need to be installed @@ -262,7 +262,7 @@ internal class ApkRestore( } /** - * Retrieves an APK from the [StoragePlugin] and caches it locally + * Retrieves an APK from the [Backend] and caches it locally * while calculating its SHA-256 hash. * * @return a [Pair] of the cached [File] and SHA-256 hash. diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SchedulingFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SchedulingFragment.kt index bc0fb203d..387e440c0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SchedulingFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SchedulingFragment.kt @@ -17,7 +17,7 @@ import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE import androidx.work.ExistingPeriodicWorkPolicy.UPDATE import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.settings.preference.M3ListPreference import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel @@ -27,7 +27,7 @@ class SchedulingFragment : PreferenceFragmentCompat(), private val viewModel: SettingsViewModel by sharedViewModel() private val settingsManager: SettingsManager by inject() - private val storagePluginManager: StoragePluginManager by inject() + private val backendManager: BackendManager by inject() override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { permitDiskReads { @@ -39,7 +39,7 @@ class SchedulingFragment : PreferenceFragmentCompat(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val storage = storagePluginManager.storageProperties + val storage = backendManager.backendProperties if (storage?.isUsb == true) { findPreference("scheduling_category_conditions")?.isEnabled = false } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt index 8ea5949c6..c5933a708 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt @@ -25,12 +25,12 @@ import androidx.work.WorkInfo import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.stevesoltys.seedvault.BackupStateManager import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.StorageProperties import com.stevesoltys.seedvault.restore.RestoreActivity import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.toRelativeTime +import org.calyxos.seedvault.core.backends.BackendProperties import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel import java.util.concurrent.TimeUnit @@ -40,7 +40,7 @@ private val TAG = SettingsFragment::class.java.name class SettingsFragment : PreferenceFragmentCompat() { private val viewModel: SettingsViewModel by sharedViewModel() - private val storagePluginManager: StoragePluginManager by inject() + private val backendManager: BackendManager by inject() private val backupStateManager: BackupStateManager by inject() private val backupManager: IBackupManager by inject() private val notificationManager: BackupNotificationManager by inject() @@ -57,8 +57,8 @@ class SettingsFragment : PreferenceFragmentCompat() { private var menuBackupNow: MenuItem? = null private var menuRestore: MenuItem? = null - private val storageProperties: StorageProperties<*>? - get() = storagePluginManager.storageProperties + private val backendProperties: BackendProperties<*>? + get() = backendManager.backendProperties override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { permitDiskReads { @@ -273,7 +273,7 @@ class SettingsFragment : PreferenceFragmentCompat() { activity?.contentResolver?.let { autoRestore.isChecked = backupStateManager.isAutoRestoreEnabled } - val storage = this.storageProperties + val storage = this.backendProperties if (storage?.isUsb == true) { autoRestore.summary = getString(R.string.settings_auto_restore_summary) + "\n\n" + getString(R.string.settings_auto_restore_summary_usb, storage.name) @@ -285,7 +285,7 @@ class SettingsFragment : PreferenceFragmentCompat() { private fun setBackupLocationSummary() { // get name of storage location backupLocation.summary = - storageProperties?.name ?: getString(R.string.settings_backup_location_none) + backendProperties?.name ?: getString(R.string.settings_backup_location_none) } private fun setAppBackupStatusSummary(lastBackupInMillis: Long?) { @@ -304,7 +304,7 @@ class SettingsFragment : PreferenceFragmentCompat() { * says that nothing is scheduled which can happen when backup destination is on flash drive. */ private fun setAppBackupSchedulingSummary(workInfo: WorkInfo?) { - if (storageProperties?.isUsb == true) { + if (backendProperties?.isUsb == true) { backupScheduling.summary = getString(R.string.settings_backup_status_next_backup_usb) return } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt index 5dee1ff5c..baac5c8d5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt @@ -11,15 +11,15 @@ import android.hardware.usb.UsbDevice import android.net.Uri import androidx.annotation.UiThread import androidx.preference.PreferenceManager +import com.stevesoltys.seedvault.backend.webdav.WebDavHandler.Companion.createWebDavProperties import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.saf.SafStorage -import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler.Companion.createWebDavProperties -import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties import com.stevesoltys.seedvault.transport.backup.BackupCoordinator import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.saf.SafBackend +import org.calyxos.seedvault.core.backends.saf.SafProperties import org.calyxos.seedvault.core.backends.webdav.WebDavBackend import org.calyxos.seedvault.core.backends.webdav.WebDavConfig +import org.calyxos.seedvault.core.backends.webdav.WebDavProperties import java.util.concurrent.ConcurrentSkipListSet internal const val PREF_KEY_TOKEN = "token" @@ -139,17 +139,17 @@ class SettingsManager(private val context: Context) { .apply() } - fun setSafStorage(safStorage: SafStorage) { + fun setSafProperties(safProperties: SafProperties) { prefs.edit() - .putString(PREF_KEY_STORAGE_URI, safStorage.uri.toString()) - .putString(PREF_KEY_STORAGE_ROOT_ID, safStorage.rootId) - .putString(PREF_KEY_STORAGE_NAME, safStorage.name) - .putBoolean(PREF_KEY_STORAGE_IS_USB, safStorage.isUsb) - .putBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, safStorage.requiresNetwork) + .putString(PREF_KEY_STORAGE_URI, safProperties.uri.toString()) + .putString(PREF_KEY_STORAGE_ROOT_ID, safProperties.rootId) + .putString(PREF_KEY_STORAGE_NAME, safProperties.name) + .putBoolean(PREF_KEY_STORAGE_IS_USB, safProperties.isUsb) + .putBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, safProperties.requiresNetwork) .apply() } - fun getSafStorage(): SafStorage? { + fun getSafProperties(): SafProperties? { val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null val uri = Uri.parse(uriStr) val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) @@ -157,7 +157,7 @@ class SettingsManager(private val context: Context) { val isUsb = prefs.getBoolean(PREF_KEY_STORAGE_IS_USB, false) val requiresNetwork = prefs.getBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, false) val rootId = prefs.getString(PREF_KEY_STORAGE_ROOT_ID, null) - return SafStorage(uri, name, isUsb, requiresNetwork, rootId) + return SafProperties(uri, name, isUsb, requiresNetwork, rootId) } fun setFlashDrive(usb: FlashDrive?) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt index f361f80ec..2c1de79d4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt @@ -40,8 +40,7 @@ import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.crypto.KeyManager import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.permitDiskReads -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.SafStorage +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.storage.StorageBackupJobService import com.stevesoltys.seedvault.storage.StorageBackupService import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP @@ -59,6 +58,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.backup.BackupJobService +import org.calyxos.seedvault.core.backends.saf.SafProperties import java.io.IOException import java.lang.Runtime.getRuntime import java.util.concurrent.TimeUnit.HOURS @@ -70,14 +70,14 @@ internal class SettingsViewModel( app: Application, settingsManager: SettingsManager, keyManager: KeyManager, - pluginManager: StoragePluginManager, + backendManager: BackendManager, private val metadataManager: MetadataManager, private val appListRetriever: AppListRetriever, private val storageBackup: StorageBackup, private val backupManager: IBackupManager, private val backupInitializer: BackupInitializer, backupStateManager: BackupStateManager, -) : RequireProvisioningViewModel(app, settingsManager, keyManager, pluginManager) { +) : RequireProvisioningViewModel(app, settingsManager, keyManager, backendManager) { private val contentResolver = app.contentResolver private val connectivityManager: ConnectivityManager? = @@ -158,7 +158,7 @@ internal class SettingsViewModel( } override fun onStorageLocationChanged() { - val storage = pluginManager.storageProperties ?: return + val storage = backendManager.backendProperties ?: return Log.i(TAG, "onStorageLocationChanged (isUsb: ${storage.isUsb})") if (storage.isUsb) { @@ -177,33 +177,33 @@ internal class SettingsViewModel( private fun onBackupRunningStateChanged() { if (isBackupRunning.value) mBackupPossible.postValue(false) else viewModelScope.launch(Dispatchers.IO) { - val canDo = !isBackupRunning.value && !pluginManager.isOnUnavailableUsb() + val canDo = !isBackupRunning.value && !backendManager.isOnUnavailableUsb() mBackupPossible.postValue(canDo) } } private fun onStoragePropertiesChanged() { - val storage = pluginManager.storageProperties ?: return + val properties = backendManager.backendProperties ?: return Log.d(TAG, "onStoragePropertiesChanged") - if (storage is SafStorage) { + if (properties is SafProperties) { // register storage observer try { contentResolver.unregisterContentObserver(storageObserver) - contentResolver.registerContentObserver(storage.uri, false, storageObserver) + contentResolver.registerContentObserver(properties.uri, false, storageObserver) } catch (e: SecurityException) { // This can happen if the app providing the storage was uninstalled. // validLocationIsSet() gets called elsewhere // and prompts for a new storage location. - Log.e(TAG, "Error registering content observer for ${storage.uri}", e) + Log.e(TAG, "Error registering content observer for ${properties.uri}", e) } } // register network observer if needed - if (networkCallback.registered && !storage.requiresNetwork) { + if (networkCallback.registered && !properties.requiresNetwork) { connectivityManager?.unregisterNetworkCallback(networkCallback) networkCallback.registered = false - } else if (!networkCallback.registered && storage.requiresNetwork) { + } else if (!networkCallback.registered && properties.requiresNetwork) { // TODO we may want to warn the user when they start a backup on a metered connection val request = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) @@ -232,7 +232,7 @@ internal class SettingsViewModel( i.putExtra(EXTRA_START_APP_BACKUP, isAppBackupEnabled) startForegroundService(app, i) } else if (isAppBackupEnabled) { - AppBackupWorker.scheduleNow(app, reschedule = !pluginManager.isOnRemovableDrive) + AppBackupWorker.scheduleNow(app, reschedule = !backendManager.isOnRemovableDrive) } } } @@ -313,14 +313,14 @@ internal class SettingsViewModel( fun scheduleAppBackup(existingWorkPolicy: ExistingPeriodicWorkPolicy) { // disable framework scheduling, because another transport may have enabled it backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), false) - if (!pluginManager.isOnRemovableDrive && backupManager.isBackupEnabled) { + if (!backendManager.isOnRemovableDrive && backupManager.isBackupEnabled) { AppBackupWorker.schedule(app, settingsManager, existingWorkPolicy) } } fun scheduleFilesBackup() { - if (!pluginManager.isOnRemovableDrive && settingsManager.isStorageBackupEnabled()) { - val requiresNetwork = pluginManager.storageProperties?.requiresNetwork == true + if (!backendManager.isOnRemovableDrive && settingsManager.isStorageBackupEnabled()) { + val requiresNetwork = backendManager.backendProperties?.requiresNetwork == true BackupJobService.scheduleJob( context = app, jobServiceClass = StorageBackupJobService::class.java, diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/SeedvaultSafStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/SeedvaultSafStoragePlugin.kt deleted file mode 100644 index 6a2b0d3d0..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/SeedvaultSafStoragePlugin.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.storage - -import android.content.Context -import com.stevesoltys.seedvault.plugins.saf.SafStorage -import org.calyxos.backup.storage.plugin.saf.SafStoragePlugin -import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT -import org.calyxos.seedvault.core.backends.saf.SafBackend -import org.calyxos.seedvault.core.backends.saf.SafConfig - -internal class SeedvaultSafStoragePlugin( - appContext: Context, - safStorage: SafStorage, - root: String = DIRECTORY_ROOT, -) : SafStoragePlugin(appContext) { - private val safConfig = SafConfig( - config = safStorage.config, - name = safStorage.name, - isUsb = safStorage.isUsb, - requiresNetwork = safStorage.requiresNetwork, - rootId = safStorage.rootId, - ) - override val delegate: SafBackend = SafBackend(appContext, safConfig, root) -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt index 8675483a5..aa5799f6f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt @@ -6,7 +6,7 @@ package com.stevesoltys.seedvault.storage import android.content.Intent -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.worker.AppBackupWorker import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -44,7 +44,7 @@ internal class StorageBackupService : BackupService() { } override val storageBackup: StorageBackup by inject() - private val storagePluginManager: StoragePluginManager by inject() + private val backendManager: BackendManager by inject() // use lazy delegate because context isn't available during construction time override val backupObserver: BackupObserver by lazy { @@ -63,7 +63,7 @@ internal class StorageBackupService : BackupService() { override fun onBackupFinished(intent: Intent, success: Boolean) { if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) { - val isUsb = storagePluginManager.storageProperties?.isUsb ?: false + val isUsb = backendManager.backendProperties?.isUsb ?: false AppBackupWorker.scheduleNow(applicationContext, reschedule = !isUsb) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/StorageModule.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/StorageModule.kt index 977cd121b..72bc7c0b1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/StorageModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/storage/StorageModule.kt @@ -6,10 +6,10 @@ package com.stevesoltys.seedvault.storage import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import org.calyxos.backup.storage.api.StorageBackup import org.koin.dsl.module val storageModule = module { - single { StorageBackup(get(), { get().backend }, get()) } + single { StorageBackup(get(), { get().backend }, get()) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/WebDavStoragePlugin.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/WebDavStoragePlugin.kt deleted file mode 100644 index 75b1113ea..000000000 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/WebDavStoragePlugin.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.storage - -import com.stevesoltys.seedvault.plugins.webdav.DIRECTORY_ROOT -import org.calyxos.backup.storage.api.StoragePlugin -import org.calyxos.backup.storage.api.StoredSnapshot -import org.calyxos.seedvault.core.backends.FileBackupFileType -import org.calyxos.seedvault.core.backends.TopLevelFolder -import org.calyxos.seedvault.core.backends.webdav.WebDavBackend -import org.calyxos.seedvault.core.backends.webdav.WebDavConfig -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream - -internal class WebDavStoragePlugin( - /** - * The result of Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) - */ - private val androidId: String, - webDavConfig: WebDavConfig, - root: String = DIRECTORY_ROOT, -) : StoragePlugin { - - private val topLevelFolder = TopLevelFolder("$androidId.sv") - private val delegate = WebDavBackend(webDavConfig, root) - - @Throws(IOException::class) - override suspend fun init() { - // no-op - } - - @Throws(IOException::class) - override suspend fun getAvailableChunkIds(): List { - val chunkIds = ArrayList() - delegate.list(topLevelFolder, FileBackupFileType.Blob::class) { fileInfo -> - chunkIds.add(fileInfo.fileHandle.name) - } - return chunkIds - } - - @Throws(IOException::class) - override suspend fun getChunkOutputStream(chunkId: String): OutputStream { - val fileHandle = FileBackupFileType.Blob(androidId, chunkId) - return delegate.save(fileHandle) - } - - @Throws(IOException::class) - override suspend fun getBackupSnapshotOutputStream(timestamp: Long): OutputStream { - val fileHandle = FileBackupFileType.Snapshot(androidId, timestamp) - return delegate.save(fileHandle) - } - - /************************* Restore *******************************/ - - @Throws(IOException::class) - override suspend fun getBackupSnapshotsForRestore(): List { - val snapshots = ArrayList() - delegate.list(null, FileBackupFileType.Snapshot::class) { fileInfo -> - val handle = fileInfo.fileHandle as FileBackupFileType.Snapshot - val folderName = handle.topLevelFolder.name - val timestamp = handle.time - val storedSnapshot = StoredSnapshot(folderName, timestamp) - snapshots.add(storedSnapshot) - } - return snapshots - } - - @Throws(IOException::class) - override suspend fun getBackupSnapshotInputStream(storedSnapshot: StoredSnapshot): InputStream { - val androidId = storedSnapshot.androidId - val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp) - return delegate.load(handle) - } - - @Throws(IOException::class) - override suspend fun getChunkInputStream( - snapshot: StoredSnapshot, - chunkId: String, - ): InputStream { - val handle = FileBackupFileType.Blob(snapshot.androidId, chunkId) - return delegate.load(handle) - } - - /************************* Pruning *******************************/ - - @Throws(IOException::class) - override suspend fun getCurrentBackupSnapshots(): List { - val snapshots = ArrayList() - delegate.list(topLevelFolder, FileBackupFileType.Snapshot::class) { fileInfo -> - val handle = fileInfo.fileHandle as FileBackupFileType.Snapshot - val folderName = handle.topLevelFolder.name - val timestamp = handle.time - val storedSnapshot = StoredSnapshot(folderName, timestamp) - snapshots.add(storedSnapshot) - } - return snapshots - } - - @Throws(IOException::class) - override suspend fun deleteBackupSnapshot(storedSnapshot: StoredSnapshot) { - val androidId = storedSnapshot.androidId - val handle = FileBackupFileType.Snapshot(androidId, storedSnapshot.timestamp) - delegate.remove(handle) - } - - @Throws(IOException::class) - override suspend fun deleteChunks(chunkIds: List) { - chunkIds.forEach { chunkId -> - val androidId = topLevelFolder.name.substringBefore(".sv") - val handle = FileBackupFileType.Blob(androidId, chunkId) - delegate.remove(handle) - } - } -} diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index 2efd5cbec..bf6bce13a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -29,15 +29,12 @@ import com.stevesoltys.seedvault.metadata.PackageState import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.getMetadataOutputStream -import com.stevesoltys.seedvault.plugins.isOutOfSpace -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.getMetadataOutputStream +import com.stevesoltys.seedvault.backend.isOutOfSpace import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import java.io.IOException -import java.io.OutputStream import java.util.concurrent.TimeUnit.DAYS import java.util.concurrent.TimeUnit.HOURS @@ -65,7 +62,7 @@ private class CoordinatorState( @WorkerThread internal class BackupCoordinator( private val context: Context, - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, private val kv: KVBackup, private val full: FullBackup, private val clock: Clock, @@ -75,7 +72,7 @@ internal class BackupCoordinator( private val nm: BackupNotificationManager, ) { - private val backend get() = pluginManager.backend + private val backend get() = backendManager.backend private val state = CoordinatorState( calledInitialize = false, calledClearBackupData = false, @@ -132,7 +129,7 @@ internal class BackupCoordinator( } catch (e: Exception) { Log.e(TAG, "Error initializing device", e) // Show error notification if we needed init or were ready for backups - if (metadataManager.requiresInit || pluginManager.canDoBackupNow()) nm.onBackupError() + if (metadataManager.requiresInit || backendManager.canDoBackupNow()) nm.onBackupError() TRANSPORT_ERROR } @@ -370,7 +367,7 @@ internal class BackupCoordinator( if (result == TRANSPORT_OK) { val isNormalBackup = packageName != MAGIC_PACKAGE_MANAGER // call onPackageBackedUp for @pm@ only if we can do backups right now - if (isNormalBackup || pluginManager.canDoBackupNow()) { + if (isNormalBackup || backendManager.canDoBackupNow()) { try { onPackageBackedUp(packageInfo, BackupType.KV, size) } catch (e: Exception) { @@ -431,7 +428,7 @@ internal class BackupCoordinator( val longBackoff = DAYS.toMillis(30) // back off if there's no storage set - val storage = pluginManager.storageProperties ?: return longBackoff + val storage = backendManager.backendProperties ?: return longBackoff return when { // back off if storage is removable and not available right now storage.isUnavailableUsb(context) -> longBackoff @@ -444,12 +441,4 @@ internal class BackupCoordinator( else -> 0L } } - - private suspend fun StoragePlugin<*>.getMetadataOutputStream( - token: Long? = null, - ): OutputStream { - val t = token ?: settingsManager.getToken() ?: throw IOException("no current token") - return getOutputStream(t, FILE_BACKUP_METADATA) - } - } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt index 2fd9a7d17..bc18e0cbd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupModule.kt @@ -16,13 +16,13 @@ val backupModule = module { context = androidContext(), backupManager = get(), settingsManager = get(), - pluginManager = get(), + backendManager = get(), ) } single { KvDbManagerImpl(androidContext()) } single { KVBackup( - pluginManager = get(), + backendManager = get(), settingsManager = get(), nm = get(), inputFactory = get(), @@ -32,7 +32,7 @@ val backupModule = module { } single { FullBackup( - pluginManager = get(), + backendManager = get(), settingsManager = get(), nm = get(), inputFactory = get(), @@ -42,7 +42,7 @@ val backupModule = module { single { BackupCoordinator( context = androidContext(), - pluginManager = get(), + backendManager = get(), kv = get(), full = get(), clock = get(), diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt index f5d134c42..65f8da7cf 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt @@ -16,8 +16,8 @@ import android.util.Log import com.stevesoltys.seedvault.crypto.Crypto import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.isOutOfSpace +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.isOutOfSpace import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import org.calyxos.seedvault.core.backends.LegacyAppBackupFile @@ -47,14 +47,14 @@ private val TAG = FullBackup::class.java.simpleName @Suppress("BlockingMethodInNonBlockingContext") internal class FullBackup( - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, private val settingsManager: SettingsManager, private val nm: BackupNotificationManager, private val inputFactory: InputFactory, private val crypto: Crypto, ) { - private val backend get() = pluginManager.backend + private val backend get() = backendManager.backend private var state: FullBackupState? = null fun hasState() = state != null diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt index 7144ad8ca..b3a823a53 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt @@ -18,8 +18,8 @@ import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.crypto.Crypto import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.isOutOfSpace +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.isOutOfSpace import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import org.calyxos.seedvault.core.backends.LegacyAppBackupFile @@ -40,7 +40,7 @@ const val DEFAULT_QUOTA_KEY_VALUE_BACKUP = (2 * (5 * 1024 * 1024)).toLong() private val TAG = KVBackup::class.java.simpleName internal class KVBackup( - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, private val settingsManager: SettingsManager, private val nm: BackupNotificationManager, private val inputFactory: InputFactory, @@ -48,7 +48,7 @@ internal class KVBackup( private val dbManager: KvDbManager, ) { - private val backend get() = pluginManager.backend + private val backend get() = backendManager.backend private var state: KVBackupState? = null fun hasState() = state != null @@ -147,7 +147,7 @@ internal class KVBackup( // K/V backups (typically starting with package manager metadata - @pm@) // are scheduled with JobInfo.Builder#setOverrideDeadline() // and thus do not respect backoff. - pluginManager.canDoBackupNow() + backendManager.canDoBackupNow() } else { // all other packages always need upload true diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt index b901d03e4..d409842eb 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/PackageService.kt @@ -27,7 +27,7 @@ import android.util.Log import android.util.Log.INFO import androidx.annotation.WorkerThread import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.settings.SettingsManager import org.calyxos.seedvault.core.backends.Backend @@ -43,12 +43,12 @@ internal class PackageService( private val context: Context, private val backupManager: IBackupManager, private val settingsManager: SettingsManager, - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, ) { private val packageManager: PackageManager = context.packageManager private val myUserId = UserHandle.myUserId() - private val backend: Backend get() = pluginManager.backend + private val backend: Backend get() = backendManager.backend val eligiblePackages: List @WorkerThread diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt index 3f5c2bdc5..ec5631656 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/FullRestore.kt @@ -17,8 +17,8 @@ import com.stevesoltys.seedvault.header.HeaderReader import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH import com.stevesoltys.seedvault.header.UnsupportedVersionException import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin import libcore.io.IoUtils.closeQuietly import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.EOFException @@ -39,7 +39,7 @@ private class FullRestoreState( private val TAG = FullRestore::class.java.simpleName internal class FullRestore( - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, @Suppress("Deprecation") private val legacyPlugin: LegacyStoragePlugin, private val outputFactory: OutputFactory, @@ -47,7 +47,7 @@ internal class FullRestore( private val crypto: Crypto, ) { - private val backend get() = pluginManager.backend + private val backend get() = backendManager.backend private var state: FullRestoreState? = null fun hasState() = state != null diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt index d6ebadb6a..78069d8a0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/KVRestore.kt @@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.header.HeaderReader import com.stevesoltys.seedvault.header.UnsupportedVersionException import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.transport.backup.KVDb import com.stevesoltys.seedvault.transport.backup.KvDbManager import libcore.io.IoUtils.closeQuietly @@ -45,7 +45,7 @@ private class KVRestoreState( private val TAG = KVRestore::class.java.simpleName internal class KVRestore( - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, @Suppress("Deprecation") private val legacyPlugin: LegacyStoragePlugin, private val outputFactory: OutputFactory, @@ -54,7 +54,7 @@ internal class KVRestore( private val dbManager: KvDbManager, ) { - private val backend get() = pluginManager.backend + private val backend get() = backendManager.backend private var state: KVRestoreState? = null /** diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt index b0703cc7e..a166ad1e1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinator.kt @@ -25,8 +25,8 @@ import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.DecryptionFailedException import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.metadata.MetadataReader -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.getAvailableBackups +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.getAvailableBackups import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.transport.D2D_TRANSPORT_FLAGS import com.stevesoltys.seedvault.transport.DEFAULT_TRANSPORT_FLAGS @@ -62,13 +62,13 @@ internal class RestoreCoordinator( private val settingsManager: SettingsManager, private val metadataManager: MetadataManager, private val notificationManager: BackupNotificationManager, - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, private val kv: KVRestore, private val full: FullRestore, private val metadataReader: MetadataReader, ) { - private val backend: Backend get() = pluginManager.backend + private val backend: Backend get() = backendManager.backend private var state: RestoreCoordinatorState? = null private var backupMetadata: BackupMetadata? = null private val failedPackages = ArrayList() @@ -176,7 +176,7 @@ internal class RestoreCoordinator( // check if we even have a backup of that app if (metadataManager.getPackageMetadata(pmPackageName) != null) { // remind user to plug in storage device - val storageName = pluginManager.storageProperties?.name + val storageName = backendManager.backendProperties?.name ?: context.getString(R.string.settings_backup_location_none) notificationManager.onRemovableStorageNotAvailableForRestore( pmPackageName, @@ -359,7 +359,7 @@ internal class RestoreCoordinator( fun isFailedPackage(packageName: String) = packageName in failedPackages private fun isStorageRemovableAndNotAvailable(): Boolean { - val storage = pluginManager.storageProperties ?: return false + val storage = backendManager.backendProperties ?: return false return storage.isUnavailableUsb(context) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt index 505f9944b..a7858eeb7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/RequireProvisioningViewModel.kt @@ -8,14 +8,14 @@ package com.stevesoltys.seedvault.ui import android.app.Application import androidx.lifecycle.AndroidViewModel import com.stevesoltys.seedvault.crypto.KeyManager -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.settings.SettingsManager abstract class RequireProvisioningViewModel( protected val app: Application, protected val settingsManager: SettingsManager, protected val keyManager: KeyManager, - protected val pluginManager: StoragePluginManager, + protected val backendManager: BackendManager, ) : AndroidViewModel(app) { abstract val isRestoreOperation: Boolean @@ -24,7 +24,7 @@ abstract class RequireProvisioningViewModel( internal val chooseBackupLocation: LiveEvent get() = mChooseBackupLocation internal fun chooseBackupLocation() = mChooseBackupLocation.setEvent(true) - internal fun validLocationIsSet() = pluginManager.isValidAppPluginSet() + internal fun validLocationIsSet() = backendManager.isValidAppPluginSet() internal fun recoveryCodeIsSet() = keyManager.hasBackupKey() diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt index afe250774..bacd05187 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt @@ -13,11 +13,10 @@ import android.util.Log import androidx.lifecycle.viewModelScope import androidx.work.ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.SafHandler -import com.stevesoltys.seedvault.plugins.saf.SafStorage -import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler -import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.saf.SafHandler +import com.stevesoltys.seedvault.backend.webdav.WebDavHandler +import org.calyxos.seedvault.core.backends.webdav.WebDavProperties import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.storage.StorageBackupJobService import com.stevesoltys.seedvault.transport.backup.BackupInitializer @@ -27,6 +26,7 @@ import kotlinx.coroutines.launch import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.backup.BackupJobService import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.saf.SafProperties import java.io.IOException import java.util.concurrent.TimeUnit @@ -40,15 +40,15 @@ internal class BackupStorageViewModel( safHandler: SafHandler, webDavHandler: WebDavHandler, settingsManager: SettingsManager, - storagePluginManager: StoragePluginManager, -) : StorageViewModel(app, safHandler, webDavHandler, settingsManager, storagePluginManager) { + backendManager: BackendManager, +) : StorageViewModel(app, safHandler, webDavHandler, settingsManager, backendManager) { override val isRestoreOperation = false - override fun onSafUriSet(safStorage: SafStorage) { - safHandler.save(safStorage) - safHandler.setPlugin(safStorage) - if (safStorage.isUsb) { + override fun onSafUriSet(safProperties: SafProperties) { + safHandler.save(safProperties) + safHandler.setPlugin(safProperties) + if (safProperties.isUsb) { // disable storage backup if new storage is on USB cancelBackupWorkers() } else { @@ -56,7 +56,7 @@ internal class BackupStorageViewModel( // also to update the network requirement of the new storage scheduleBackupWorkers() } - onStorageLocationSet(safStorage.isUsb) + onStorageLocationSet(safProperties.isUsb) } override fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) { @@ -100,7 +100,7 @@ internal class BackupStorageViewModel( } private fun scheduleBackupWorkers() { - val storage = storagePluginManager.storageProperties ?: error("no storage available") + val storage = backendManager.backendProperties ?: error("no storage available") // disable framework scheduling, because another transport may have enabled it backupManager.setFrameworkSchedulingEnabledForUser(UserHandle.myUserId(), false) if (!storage.isUsb) { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt index 9c5f3bbb8..54d774313 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/RestoreStorageViewModel.kt @@ -9,16 +9,16 @@ import android.app.Application import android.util.Log import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.DIRECTORY_ROOT -import com.stevesoltys.seedvault.plugins.saf.SafHandler -import com.stevesoltys.seedvault.plugins.saf.SafStorage -import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler -import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.saf.SafHandler +import com.stevesoltys.seedvault.backend.webdav.WebDavHandler import com.stevesoltys.seedvault.settings.SettingsManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.Constants.DIRECTORY_ROOT +import org.calyxos.seedvault.core.backends.saf.SafProperties +import org.calyxos.seedvault.core.backends.webdav.WebDavProperties import java.io.IOException private val TAG = RestoreStorageViewModel::class.java.simpleName @@ -28,25 +28,25 @@ internal class RestoreStorageViewModel( safHandler: SafHandler, webDavHandler: WebDavHandler, settingsManager: SettingsManager, - storagePluginManager: StoragePluginManager, -) : StorageViewModel(app, safHandler, webDavHandler, settingsManager, storagePluginManager) { + backendManager: BackendManager, +) : StorageViewModel(app, safHandler, webDavHandler, settingsManager, backendManager) { override val isRestoreOperation = true - override fun onSafUriSet(safStorage: SafStorage) { + override fun onSafUriSet(safProperties: SafProperties) { viewModelScope.launch(Dispatchers.IO) { val hasBackup = try { - safHandler.hasAppBackup(safStorage) + safHandler.hasAppBackup(safProperties) } catch (e: IOException) { - Log.e(TAG, "Error reading URI: ${safStorage.uri}", e) + Log.e(TAG, "Error reading URI: ${safProperties.uri}", e) false } if (hasBackup) { - safHandler.save(safStorage) - safHandler.setPlugin(safStorage) + safHandler.save(safProperties) + safHandler.setPlugin(safProperties) mLocationChecked.postEvent(LocationResult()) } else { - Log.w(TAG, "Location was rejected: ${safStorage.uri}") + Log.w(TAG, "Location was rejected: ${safProperties.uri}") // notify the UI that the location was invalid val errorMsg = diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt index 1e0b78e1e..f3f2244a1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageActivity.kt @@ -18,7 +18,7 @@ import androidx.activity.result.contract.ActivityResultContracts.OpenDocumentTre import androidx.annotation.CallSuper import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.saf.StorageRootResolver +import com.stevesoltys.seedvault.backend.saf.StorageRootResolver import com.stevesoltys.seedvault.ui.BackupActivity import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_SETUP_WIZARD diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionFetcher.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionFetcher.kt index 043f235e9..d13ed72b0 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionFetcher.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageOptionFetcher.kt @@ -18,8 +18,8 @@ import android.provider.DocumentsContract.PROVIDER_INTERFACE import android.provider.DocumentsContract.buildRootsUri import android.util.Log import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.saf.SafStorageOptions -import com.stevesoltys.seedvault.plugins.saf.StorageRootResolver +import com.stevesoltys.seedvault.backend.saf.SafStorageOptions +import com.stevesoltys.seedvault.backend.saf.StorageRootResolver import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption private val TAG = StorageOptionFetcher::class.java.simpleName diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt index 9ce2e0207..79a843fca 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt @@ -13,11 +13,10 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.SafHandler -import com.stevesoltys.seedvault.plugins.saf.SafStorage -import com.stevesoltys.seedvault.plugins.webdav.WebDavHandler -import com.stevesoltys.seedvault.plugins.webdav.WebDavProperties +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.saf.SafHandler +import com.stevesoltys.seedvault.backend.webdav.WebDavHandler +import org.calyxos.seedvault.core.backends.webdav.WebDavProperties import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent @@ -25,6 +24,7 @@ import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.saf.SafProperties import org.calyxos.seedvault.core.backends.webdav.WebDavConfig internal abstract class StorageViewModel( @@ -32,7 +32,7 @@ internal abstract class StorageViewModel( protected val safHandler: SafHandler, protected val webdavHandler: WebDavHandler, protected val settingsManager: SettingsManager, - protected val storagePluginManager: StoragePluginManager, + protected val backendManager: BackendManager, ) : AndroidViewModel(app), RemovableStorageListener { private val mStorageOptions = MutableLiveData>() @@ -49,7 +49,7 @@ internal abstract class StorageViewModel( internal var isSetupWizard: Boolean = false internal val hasStorageSet: Boolean - get() = storagePluginManager.storageProperties != null + get() = backendManager.backendProperties != null abstract val isRestoreOperation: Boolean internal fun loadStorageRoots() { @@ -88,7 +88,7 @@ internal abstract class StorageViewModel( onSafUriSet(safStorage) } - abstract fun onSafUriSet(safStorage: SafStorage) + abstract fun onSafUriSet(safProperties: SafProperties) abstract fun onWebDavConfigSet(properties: WebDavProperties, backend: Backend) override fun onCleared() { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt index ebf2278dd..c5aade297 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/WebDavConfigFragment.kt @@ -22,7 +22,7 @@ import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar.LENGTH_LONG import com.google.android.material.textfield.TextInputEditText import com.stevesoltys.seedvault.R -import com.stevesoltys.seedvault.plugins.webdav.WebDavConfigState +import com.stevesoltys.seedvault.backend.webdav.WebDavConfigState import com.stevesoltys.seedvault.ui.INTENT_EXTRA_IS_RESTORE import kotlinx.coroutines.launch import org.koin.androidx.viewmodel.ext.android.getSharedViewModel diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt index f849f9554..3311a728f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackupManager.kt @@ -11,11 +11,9 @@ import android.util.Log import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.getMetadataOutputStream -import com.stevesoltys.seedvault.plugins.isOutOfSpace -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.getMetadataOutputStream +import com.stevesoltys.seedvault.backend.isOutOfSpace import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.isStopped @@ -24,7 +22,6 @@ import com.stevesoltys.seedvault.ui.notification.getAppName import kotlinx.coroutines.delay import org.calyxos.seedvault.core.backends.LegacyAppBackupFile import java.io.IOException -import java.io.OutputStream internal class ApkBackupManager( private val context: Context, @@ -33,7 +30,7 @@ internal class ApkBackupManager( private val packageService: PackageService, private val iconManager: IconManager, private val apkBackup: ApkBackup, - private val pluginManager: StoragePluginManager, + private val backendManager: BackendManager, private val nm: BackupNotificationManager, ) { @@ -58,7 +55,7 @@ internal class ApkBackupManager( // upload all local changes only at the end, // so we don't have to re-upload the metadata val token = settingsManager.getToken() ?: error("no token") - pluginManager.backend.getMetadataOutputStream(token).use { outputStream -> + backendManager.backend.getMetadataOutputStream(token).use { outputStream -> metadataManager.uploadMetadata(outputStream) } } @@ -105,7 +102,7 @@ internal class ApkBackupManager( try { val token = settingsManager.getToken() ?: throw IOException("no current token") val handle = LegacyAppBackupFile.IconsFile(token) - pluginManager.backend.save(handle).use { + backendManager.backend.save(handle).use { iconManager.uploadIcons(token, it) } } catch (e: IOException) { @@ -123,7 +120,7 @@ internal class ApkBackupManager( return try { apkBackup.backupApkIfNecessary(packageInfo) { name -> val token = settingsManager.getToken() ?: throw IOException("no current token") - pluginManager.backend.save(LegacyAppBackupFile.Blob(token, name)) + backendManager.backend.save(LegacyAppBackupFile.Blob(token, name)) }?.let { packageMetadata -> metadataManager.onApkBackedUp(packageInfo, packageMetadata) true @@ -146,11 +143,4 @@ internal class ApkBackupManager( } } } - - private suspend fun StoragePlugin<*>.getMetadataOutputStream( - token: Long? = null, - ): OutputStream { - val t = token ?: settingsManager.getToken() ?: throw IOException("no current token") - return getOutputStream(t, FILE_BACKUP_METADATA) - } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt index e1a7426ff..b7041901e 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/AppBackupWorker.kt @@ -22,7 +22,7 @@ import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_OBSERVER @@ -101,7 +101,7 @@ class AppBackupWorker( private val backupRequester: BackupRequester by inject() private val settingsManager: SettingsManager by inject() private val apkBackupManager: ApkBackupManager by inject() - private val storagePluginManager: StoragePluginManager by inject() + private val backendManager: BackendManager by inject() private val nm: BackupNotificationManager by inject() override suspend fun doWork(): Result { @@ -111,7 +111,7 @@ class AppBackupWorker( } catch (e: Exception) { Log.e(TAG, "Error while running setForeground: ", e) } - val freeSpace = storagePluginManager.getFreeSpace() + val freeSpace = backendManager.getFreeSpace() if (freeSpace != null && freeSpace < MIN_FREE_SPACE) { nm.onInsufficientSpaceError() return Result.failure() diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt index 8c3d0c3e7..dea0b9410 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/WorkerModule.kt @@ -39,7 +39,7 @@ val workerModule = module { packageService = get(), apkBackup = get(), iconManager = get(), - pluginManager = get(), + backendManager = get(), nm = get() ) } diff --git a/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt b/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt index 78e9333ab..305bbc6c5 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/TestApp.kt @@ -13,7 +13,7 @@ import com.stevesoltys.seedvault.crypto.KeyManager import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl import com.stevesoltys.seedvault.header.headerModule import com.stevesoltys.seedvault.metadata.metadataModule -import com.stevesoltys.seedvault.plugins.saf.storagePluginModuleSaf +import com.stevesoltys.seedvault.backend.saf.storagePluginModuleSaf import com.stevesoltys.seedvault.restore.install.installModule import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.transport.backup.PackageService diff --git a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt b/app/src/test/java/com/stevesoltys/seedvault/backend/saf/DocumentFileTest.kt similarity index 97% rename from app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt rename to app/src/test/java/com/stevesoltys/seedvault/backend/saf/DocumentFileTest.kt index 41a3c15cb..b6fbecc8d 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/plugins/saf/DocumentFileTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/backend/saf/DocumentFileTest.kt @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.saf +package com.stevesoltys.seedvault.backend.saf import android.content.Context import android.content.pm.PackageManager diff --git a/app/src/test/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePluginTest.kt b/app/src/test/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePluginTest.kt deleted file mode 100644 index 2786b29a0..000000000 --- a/app/src/test/java/com/stevesoltys/seedvault/plugins/webdav/WebDavStoragePluginTest.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.webdav - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.stevesoltys.seedvault.TestApp -import com.stevesoltys.seedvault.getRandomByteArray -import com.stevesoltys.seedvault.plugins.EncryptedMetadata -import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA -import com.stevesoltys.seedvault.transport.TransportTest -import kotlinx.coroutines.runBlocking -import org.calyxos.seedvault.core.backends.webdav.WebDavConfig -import org.junit.Test -import org.junit.jupiter.api.Assertions.assertArrayEquals -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Assertions.fail -import org.junit.jupiter.api.assertThrows -import org.junit.runner.RunWith -import org.robolectric.annotation.Config - -@RunWith(AndroidJUnit4::class) -@Config( - application = TestApp::class -) -internal class WebDavStoragePluginTest : TransportTest() { - - private val plugin = WebDavStoragePlugin(WebDavTestConfig.getConfig()) - - @Test - fun `test self-test`() = runBlocking { - assertTrue(plugin.test()) - - val plugin2 = WebDavStoragePlugin(WebDavConfig("https://github.com/", "", "")) - val e = assertThrows { - assertFalse(plugin2.test()) - } - println(e) - } - - @Test - fun `test getting free space`() = runBlocking { - val freeBytes = plugin.getFreeSpace() ?: fail() - assertTrue(freeBytes > 0) - } - - @Test - fun `test restore sets and reading+writing`() = runBlocking { - val token = System.currentTimeMillis() - val metadata = getRandomByteArray() - - // need to initialize, to have root .SeedVaultAndroidBackup folder - plugin.initializeDevice() - plugin.startNewRestoreSet(token) - - // initially, we don't have any backups - assertEquals(emptySet(), plugin.getAvailableBackups()?.toSet()) - - // write out the metadata file - plugin.getOutputStream(token, FILE_BACKUP_METADATA).use { - it.write(metadata) - } - - try { - // now we have one backup matching our token - val backups = plugin.getAvailableBackups()?.toSet() ?: fail() - assertEquals(1, backups.size) - assertEquals(token, backups.first().token) - - // read back written data - assertArrayEquals( - metadata, - plugin.getInputStream(token, FILE_BACKUP_METADATA).use { it.readAllBytes() }, - ) - } finally { - // remove data at the end, so consecutive test runs pass - plugin.removeData(token, FILE_BACKUP_METADATA) - } - } - -} diff --git a/app/src/test/java/com/stevesoltys/seedvault/plugins/webdav/WebDavTestConfig.kt b/app/src/test/java/com/stevesoltys/seedvault/plugins/webdav/WebDavTestConfig.kt deleted file mode 100644 index bb6099b8e..000000000 --- a/app/src/test/java/com/stevesoltys/seedvault/plugins/webdav/WebDavTestConfig.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.plugins.webdav - -import org.calyxos.seedvault.core.backends.webdav.WebDavConfig -import org.junit.Assume.assumeFalse -import org.junit.jupiter.api.Assertions.fail - -object WebDavTestConfig { - - fun getConfig(): WebDavConfig { - assumeFalse(System.getenv("NEXTCLOUD_URL").isNullOrEmpty()) - return WebDavConfig( - url = System.getenv("NEXTCLOUD_URL") ?: fail(), - username = System.getenv("NEXTCLOUD_USER") ?: fail(), - password = System.getenv("NEXTCLOUD_PASS") ?: fail(), - ) - } - -} diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt index e0778654b..79d5945cc 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/AppSelectionManagerTest.kt @@ -13,7 +13,7 @@ import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.metadata.BackupMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.ui.PACKAGE_NAME_CONTACTS import com.stevesoltys.seedvault.ui.PACKAGE_NAME_SETTINGS @@ -43,7 +43,7 @@ import kotlin.random.Random @OptIn(ExperimentalCoroutinesApi::class) internal class AppSelectionManagerTest : TransportTest() { - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val iconManager: IconManager = mockk() private val testDispatcher = UnconfinedTestDispatcher() private val scope = TestScope(testDispatcher) @@ -59,7 +59,7 @@ internal class AppSelectionManagerTest : TransportTest() { private val appSelectionManager = AppSelectionManager( context = context, - pluginManager = storagePluginManager, + backendManager = backendManager, iconManager = iconManager, coroutineScope = scope, workDispatcher = testDispatcher, @@ -218,7 +218,7 @@ internal class AppSelectionManagerTest : TransportTest() { @Test fun `test icon loading fails`() = scope.runTest { val backend: Backend = mockk() - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend coEvery { backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token)) } throws IOException() @@ -425,7 +425,7 @@ internal class AppSelectionManagerTest : TransportTest() { private fun expectIconLoading(icons: Set = setOf(packageName1, packageName2)) { val backend: Backend = mockk() val inputStream = ByteArrayInputStream(Random.nextBytes(42)) - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend coEvery { backend.load(LegacyAppBackupFile.IconsFile(backupMetadata.token)) } returns inputStream diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt index b7a7a6a01..719e43549 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt @@ -19,8 +19,8 @@ import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.metadata.ApkSplit import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED @@ -59,7 +59,7 @@ internal class ApkBackupRestoreTest : TransportTest() { every { packageManager } returns pm } - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val backupManager: IBackupManager = mockk() private val backupStateManager: BackupStateManager = mockk() @@ -75,7 +75,7 @@ internal class ApkBackupRestoreTest : TransportTest() { context = strictContext, backupManager = backupManager, backupStateManager = backupStateManager, - pluginManager = storagePluginManager, + backendManager = backendManager, legacyStoragePlugin = legacyStoragePlugin, crypto = crypto, splitCompatChecker = splitCompatChecker, @@ -111,7 +111,7 @@ internal class ApkBackupRestoreTest : TransportTest() { init { mockkStatic(PackageUtils::class) - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt index 48f4eddda..e7ed99184 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkRestoreTest.kt @@ -23,8 +23,8 @@ import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.metadata.ApkSplit import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadataMap -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.restore.RestorableBackup import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP @@ -65,7 +65,7 @@ internal class ApkRestoreTest : TransportTest() { } private val backupManager: IBackupManager = mockk() private val backupStateManager: BackupStateManager = mockk() - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val backend: Backend = mockk() private val legacyStoragePlugin: LegacyStoragePlugin = mockk() private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk() @@ -76,7 +76,7 @@ internal class ApkRestoreTest : TransportTest() { context = strictContext, backupManager = backupManager, backupStateManager = backupStateManager, - pluginManager = storagePluginManager, + backendManager = backendManager, legacyStoragePlugin = legacyStoragePlugin, crypto = crypto, splitCompatChecker = splitCompatChecker, @@ -107,7 +107,7 @@ internal class ApkRestoreTest : TransportTest() { // as we don't do strict signature checking, we can use a relaxed mock packageInfo.signingInfo = mockk(relaxed = true) - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/storage/WebDavStoragePluginTest.kt b/app/src/test/java/com/stevesoltys/seedvault/storage/WebDavStoragePluginTest.kt deleted file mode 100644 index 62bdb785f..000000000 --- a/app/src/test/java/com/stevesoltys/seedvault/storage/WebDavStoragePluginTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.stevesoltys.seedvault.storage - -import com.stevesoltys.seedvault.getRandomByteArray -import com.stevesoltys.seedvault.plugins.webdav.WebDavTestConfig -import com.stevesoltys.seedvault.transport.backup.BackupTest -import kotlinx.coroutines.runBlocking -import org.calyxos.backup.storage.api.StoredSnapshot -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Test - -internal class WebDavStoragePluginTest : BackupTest() { - - private val androidId = "abcdef0123456789" - private val plugin = WebDavStoragePlugin(androidId, WebDavTestConfig.getConfig()) - - private val snapshot = StoredSnapshot("$androidId.sv", System.currentTimeMillis()) - - @Test - fun `test chunks`() = runBlocking { - val chunkId1 = getRandomByteArray(32).toHexString() - val chunkBytes1 = getRandomByteArray() - - // init to create root folder - plugin.init() - - // first we don't have any chunks - assertEquals(emptyList(), plugin.getAvailableChunkIds()) - - // we write out chunk1 - plugin.getChunkOutputStream(chunkId1).use { - it.write(chunkBytes1) - } - - try { - // now we have the ID of chunk1 - assertEquals(listOf(chunkId1), plugin.getAvailableChunkIds()) - - // reading chunk1 matches what we wrote - assertArrayEquals( - chunkBytes1, - plugin.getChunkInputStream(snapshot, chunkId1).readAllBytes(), - ) - } finally { - // delete chunk again - plugin.deleteChunks(listOf(chunkId1)) - } - } - - @Test - fun `test snapshots`() = runBlocking { - val snapshotBytes = getRandomByteArray() - - // init to create root folder - plugin.init() - - // first we don't have any snapshots - assertEquals(emptyList(), plugin.getCurrentBackupSnapshots()) - assertEquals(emptyList(), plugin.getBackupSnapshotsForRestore()) - - // now write one snapshot - plugin.getBackupSnapshotOutputStream(snapshot.timestamp).use { - it.write(snapshotBytes) - } - - try { - // now we have that one snapshot - assertEquals(listOf(snapshot), plugin.getCurrentBackupSnapshots()) - assertEquals(listOf(snapshot), plugin.getBackupSnapshotsForRestore()) - - // read back written snapshot - assertArrayEquals( - snapshotBytes, - plugin.getBackupSnapshotInputStream(snapshot).readAllBytes(), - ) - - // other device writes another snapshot - val androidId2 = "0123456789abcdef" - val otherPlugin = WebDavStoragePlugin(androidId2, WebDavTestConfig.getConfig()) - val otherSnapshot = StoredSnapshot("$androidId2.sv", System.currentTimeMillis()) - val otherSnapshotBytes = getRandomByteArray() - assertEquals(emptyList(), otherPlugin.getAvailableChunkIds()) - otherPlugin.getBackupSnapshotOutputStream(otherSnapshot.timestamp).use { - it.write(otherSnapshotBytes) - } - try { - // now that initial one snapshot is still the only current, but restore has both - assertEquals(listOf(snapshot), plugin.getCurrentBackupSnapshots()) - assertEquals( - setOf(snapshot, otherSnapshot), - plugin.getBackupSnapshotsForRestore().toSet(), // set to avoid sorting issues - ) - } finally { - plugin.deleteBackupSnapshot(otherSnapshot) - } - } finally { - plugin.deleteBackupSnapshot(snapshot) - } - } - -} - -private fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt index 4948206a8..84c6fb617 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt @@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.header.MAX_SEGMENT_CLEARTEXT_LENGTH import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.MetadataReaderImpl import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.transport.backup.BackupCoordinator import com.stevesoltys.seedvault.transport.backup.FullBackup import com.stevesoltys.seedvault.transport.backup.InputFactory @@ -63,13 +63,13 @@ internal class CoordinatorIntegrationTest : TransportTest() { private val metadataReader = MetadataReaderImpl(cryptoImpl) private val notificationManager = mockk() private val dbManager = TestKvDbManager() - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() @Suppress("Deprecation") private val legacyPlugin = mockk() private val backend = mockk() private val kvBackup = KVBackup( - pluginManager = storagePluginManager, + backendManager = backendManager, settingsManager = settingsManager, nm = notificationManager, inputFactory = inputFactory, @@ -77,7 +77,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { dbManager = dbManager, ) private val fullBackup = FullBackup( - pluginManager = storagePluginManager, + backendManager = backendManager, settingsManager = settingsManager, nm = notificationManager, inputFactory = inputFactory, @@ -87,7 +87,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { private val packageService: PackageService = mockk() private val backup = BackupCoordinator( context, - storagePluginManager, + backendManager, kvBackup, fullBackup, clock, @@ -98,7 +98,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { ) private val kvRestore = KVRestore( - storagePluginManager, + backendManager, legacyPlugin, outputFactory, headerReader, @@ -106,14 +106,14 @@ internal class CoordinatorIntegrationTest : TransportTest() { dbManager ) private val fullRestore = - FullRestore(storagePluginManager, legacyPlugin, outputFactory, headerReader, cryptoImpl) + FullRestore(backendManager, legacyPlugin, outputFactory, headerReader, cryptoImpl) private val restore = RestoreCoordinator( context, crypto, settingsManager, metadataManager, notificationManager, - storagePluginManager, + backendManager, kvRestore, fullRestore, metadataReader @@ -132,7 +132,7 @@ internal class CoordinatorIntegrationTest : TransportTest() { private val realName = cryptoImpl.getNameForPackage(salt, packageInfo.packageName) init { - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt index 4382aae31..1f9c04ac8 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt @@ -20,8 +20,7 @@ import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.saf.SafStorage +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.worker.ApkBackup import io.mockk.Runs @@ -33,6 +32,7 @@ import io.mockk.verify import kotlinx.coroutines.runBlocking import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.LegacyAppBackupFile +import org.calyxos.seedvault.core.backends.saf.SafProperties import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.io.IOException @@ -41,7 +41,7 @@ import kotlin.random.Random internal class BackupCoordinatorTest : BackupTest() { - private val pluginManager = mockk() + private val backendManager = mockk() private val kv = mockk() private val full = mockk() private val apkBackup = mockk() @@ -50,7 +50,7 @@ internal class BackupCoordinatorTest : BackupTest() { private val backup = BackupCoordinator( context = context, - pluginManager = pluginManager, + backendManager = backendManager, kv = kv, full = full, clock = clock, @@ -64,7 +64,7 @@ internal class BackupCoordinatorTest : BackupTest() { private val metadataOutputStream = mockk() private val fileDescriptor: ParcelFileDescriptor = mockk() private val packageMetadata: PackageMetadata = mockk() - private val safStorage = SafStorage( + private val safProperties = SafProperties( config = Uri.EMPTY, name = getRandomString(), isUsb = false, @@ -73,7 +73,7 @@ internal class BackupCoordinatorTest : BackupTest() { ) init { - every { pluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test @@ -100,7 +100,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { settingsManager.setNewToken(token) } just Runs every { metadataManager.onDeviceInitialization(token) } throws IOException() every { metadataManager.requiresInit } returns maybeTrue - every { pluginManager.canDoBackupNow() } returns !maybeTrue + every { backendManager.canDoBackupNow() } returns !maybeTrue every { notificationManager.onBackupError() } just Runs assertEquals(TRANSPORT_ERROR, backup.initializeDevice()) @@ -120,7 +120,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { settingsManager.setNewToken(token) } just Runs every { metadataManager.onDeviceInitialization(token) } throws IOException() every { metadataManager.requiresInit } returns false - every { pluginManager.canDoBackupNow() } returns false + every { backendManager.canDoBackupNow() } returns false assertEquals(TRANSPORT_ERROR, backup.initializeDevice()) @@ -136,7 +136,7 @@ internal class BackupCoordinatorTest : BackupTest() { fun `performIncrementalBackup of @pm@ causes re-init when legacy format`() = runBlocking { val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER } - every { pluginManager.canDoBackupNow() } returns true + every { backendManager.canDoBackupNow() } returns true every { metadataManager.requiresInit } returns true // start new restore set @@ -234,7 +234,7 @@ internal class BackupCoordinatorTest : BackupTest() { every { kv.getCurrentSize() } returns 42L coEvery { kv.finishBackup() } returns TRANSPORT_OK - every { pluginManager.canDoBackupNow() } returns false + every { backendManager.canDoBackupNow() } returns false assertEquals(TRANSPORT_OK, backup.finishBackup()) } @@ -300,7 +300,7 @@ internal class BackupCoordinatorTest : BackupTest() { ) } just Runs coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs - every { pluginManager.storageProperties } returns safStorage + every { backendManager.backendProperties } returns safProperties every { settingsManager.useMeteredNetwork } returns false every { metadataOutputStream.close() } just Runs @@ -350,7 +350,7 @@ internal class BackupCoordinatorTest : BackupTest() { ) } just Runs coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs - every { pluginManager.storageProperties } returns safStorage + every { backendManager.backendProperties } returns safProperties every { settingsManager.useMeteredNetwork } returns false every { metadataOutputStream.close() } just Runs diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt index d922d10ce..816c496d1 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/FullBackupTest.kt @@ -11,7 +11,7 @@ import android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED import android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.Runs import io.mockk.coEvery @@ -31,11 +31,11 @@ import kotlin.random.Random internal class FullBackupTest : BackupTest() { - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val backend = mockk() private val notificationManager = mockk() private val backup = FullBackup( - pluginManager = storagePluginManager, + backendManager = backendManager, settingsManager = settingsManager, nm = notificationManager, inputFactory = inputFactory, @@ -47,7 +47,7 @@ internal class FullBackupTest : BackupTest() { private val ad = getADForFull(VERSION, packageInfo.packageName) init { - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt index 85854b6c4..95bfbe90e 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/KVBackupTest.kt @@ -17,7 +17,7 @@ import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.header.MAX_KEY_LENGTH_SIZE import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.CapturingSlot import io.mockk.Runs @@ -39,13 +39,13 @@ import kotlin.random.Random internal class KVBackupTest : BackupTest() { - private val pluginManager = mockk() + private val backendManager = mockk() private val notificationManager = mockk() private val dataInput = mockk() private val dbManager = mockk() private val backup = KVBackup( - pluginManager = pluginManager, + backendManager = backendManager, settingsManager = settingsManager, nm = notificationManager, inputFactory = inputFactory, @@ -62,7 +62,7 @@ internal class KVBackupTest : BackupTest() { private val inputStream = ByteArrayInputStream(dbBytes) init { - every { pluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test @@ -250,7 +250,7 @@ internal class KVBackupTest : BackupTest() { every { dbManager.existsDb(pmPackageInfo.packageName) } returns false every { crypto.getNameForPackage(salt, pmPackageInfo.packageName) } returns name every { dbManager.getDb(pmPackageInfo.packageName) } returns db - every { pluginManager.canDoBackupNow() } returns false + every { backendManager.canDoBackupNow() } returns false every { db.put(key, dataValue) } just Runs getDataInput(listOf(true, false)) diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt index c71696103..c176d78cf 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/FullRestoreTest.kt @@ -16,8 +16,8 @@ import com.stevesoltys.seedvault.header.UnsupportedVersionException import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VersionHeader import com.stevesoltys.seedvault.header.getADForFull -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.BackendManager import io.mockk.CapturingSlot import io.mockk.Runs import io.mockk.coEvery @@ -39,11 +39,11 @@ import kotlin.random.Random internal class FullRestoreTest : RestoreTest() { - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val backend = mockk() private val legacyPlugin = mockk() private val restore = FullRestore( - pluginManager = storagePluginManager, + backendManager = backendManager, legacyPlugin = legacyPlugin, outputFactory = outputFactory, headerReader = headerReader, @@ -55,7 +55,7 @@ internal class FullRestoreTest : RestoreTest() { private val ad = getADForFull(VERSION, packageInfo.packageName) init { - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt index aadd12033..4e3caf9af 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/KVRestoreTest.kt @@ -15,8 +15,8 @@ import com.stevesoltys.seedvault.header.UnsupportedVersionException import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VersionHeader import com.stevesoltys.seedvault.header.getADForKV -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.transport.backup.KVDb import com.stevesoltys.seedvault.transport.backup.KvDbManager import io.mockk.Runs @@ -41,14 +41,14 @@ import kotlin.random.Random internal class KVRestoreTest : RestoreTest() { - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val backend = mockk() @Suppress("DEPRECATION") private val legacyPlugin = mockk() private val dbManager = mockk() private val output = mockk() private val restore = KVRestore( - pluginManager = storagePluginManager, + backendManager = backendManager, legacyPlugin = legacyPlugin, outputFactory = outputFactory, headerReader = headerReader, @@ -74,7 +74,7 @@ internal class KVRestoreTest : RestoreTest() { // for InputStream#readBytes() mockkStatic("kotlin.io.ByteStreamsKt") - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt index 4a4605b03..f187ad001 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreCoordinatorTest.kt @@ -13,16 +13,15 @@ import android.app.backup.RestoreDescription.TYPE_FULL_STREAM import android.app.backup.RestoreDescription.TYPE_KEY_VALUE import android.content.pm.PackageInfo import android.os.ParcelFileDescriptor +import com.stevesoltys.seedvault.backend.BackendManager +import com.stevesoltys.seedvault.backend.EncryptedMetadata +import com.stevesoltys.seedvault.backend.getAvailableBackups import com.stevesoltys.seedvault.coAssertThrows import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.MetadataReader import com.stevesoltys.seedvault.metadata.PackageMetadata -import com.stevesoltys.seedvault.plugins.EncryptedMetadata -import com.stevesoltys.seedvault.plugins.StoragePluginManager -import com.stevesoltys.seedvault.plugins.getAvailableBackups -import com.stevesoltys.seedvault.plugins.saf.SafStorage import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.Runs @@ -34,6 +33,7 @@ import io.mockk.mockkStatic import io.mockk.verify import kotlinx.coroutines.runBlocking import org.calyxos.seedvault.core.backends.Backend +import org.calyxos.seedvault.core.backends.saf.SafProperties import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertThrows @@ -46,7 +46,7 @@ import kotlin.random.Random internal class RestoreCoordinatorTest : TransportTest() { private val notificationManager: BackupNotificationManager = mockk() - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val backend = mockk() private val kv = mockk() private val full = mockk() @@ -58,14 +58,14 @@ internal class RestoreCoordinatorTest : TransportTest() { settingsManager = settingsManager, metadataManager = metadataManager, notificationManager = notificationManager, - pluginManager = storagePluginManager, + backendManager = backendManager, kv = kv, full = full, metadataReader = metadataReader, ) private val inputStream = mockk() - private val safStorage: SafStorage = mockk() + private val safStorage: SafProperties = mockk() private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" } private val packageInfoArray = arrayOf(packageInfo) private val packageInfoArray2 = arrayOf(packageInfo, packageInfo2) @@ -80,8 +80,8 @@ internal class RestoreCoordinatorTest : TransportTest() { metadata.packageMetadataMap[packageInfo2.packageName] = PackageMetadata(backupType = BackupType.FULL) - mockkStatic("com.stevesoltys.seedvault.plugins.BackendExtKt") - every { storagePluginManager.backend } returns backend + mockkStatic("com.stevesoltys.seedvault.backend.BackendExtKt") + every { backendManager.backend } returns backend } @Test @@ -175,7 +175,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() optimized auto-restore with removed storage shows notification`() = runBlocking { - every { storagePluginManager.storageProperties } returns safStorage + every { backendManager.backendProperties } returns safStorage every { safStorage.isUnavailableUsb(context) } returns true every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L) every { safStorage.name } returns storageName @@ -199,7 +199,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() optimized auto-restore with available storage shows no notification`() = runBlocking { - every { storagePluginManager.storageProperties } returns safStorage + every { backendManager.backendProperties } returns safStorage every { safStorage.isUnavailableUsb(context) } returns false restore.beforeStartRestore(metadata) @@ -215,7 +215,7 @@ internal class RestoreCoordinatorTest : TransportTest() { @Test fun `startRestore() with removed storage shows no notification`() = runBlocking { - every { storagePluginManager.storageProperties } returns safStorage + every { backendManager.backendProperties } returns safStorage every { safStorage.isUnavailableUsb(context) } returns true every { metadataManager.getPackageMetadata(packageName) } returns null diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt index ab3afad9b..9bdab3a63 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/restore/RestoreV0IntegrationTest.kt @@ -18,9 +18,8 @@ import com.stevesoltys.seedvault.crypto.KeyManagerTestImpl import com.stevesoltys.seedvault.encodeBase64 import com.stevesoltys.seedvault.header.HeaderReaderImpl import com.stevesoltys.seedvault.metadata.MetadataReaderImpl -import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePlugin -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.LegacyStoragePlugin +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.toByteArrayFromHex import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.backup.KvDbManager @@ -55,13 +54,13 @@ internal class RestoreV0IntegrationTest : TransportTest() { private val dbManager = mockk() private val metadataReader = MetadataReaderImpl(cryptoImpl) private val notificationManager = mockk() - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() @Suppress("Deprecation") private val legacyPlugin = mockk() private val backend = mockk() private val kvRestore = KVRestore( - pluginManager = storagePluginManager, + backendManager = backendManager, legacyPlugin = legacyPlugin, outputFactory = outputFactory, headerReader = headerReader, @@ -69,14 +68,14 @@ internal class RestoreV0IntegrationTest : TransportTest() { dbManager = dbManager, ) private val fullRestore = - FullRestore(storagePluginManager, legacyPlugin, outputFactory, headerReader, cryptoImpl) + FullRestore(backendManager, legacyPlugin, outputFactory, headerReader, cryptoImpl) private val restore = RestoreCoordinator( context = context, crypto = crypto, settingsManager = settingsManager, metadataManager = metadataManager, notificationManager = notificationManager, - pluginManager = storagePluginManager, + backendManager = backendManager, kv = kvRestore, full = fullRestore, metadataReader = metadataReader, @@ -124,7 +123,7 @@ internal class RestoreV0IntegrationTest : TransportTest() { private val key264 = key2.encodeBase64() init { - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt index 3ff11eb8b..246ae82de 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/worker/ApkBackupManagerTest.kt @@ -14,7 +14,7 @@ import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED -import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager @@ -40,7 +40,7 @@ internal class ApkBackupManagerTest : TransportTest() { private val packageService: PackageService = mockk() private val apkBackup: ApkBackup = mockk() private val iconManager: IconManager = mockk() - private val storagePluginManager: StoragePluginManager = mockk() + private val backendManager: BackendManager = mockk() private val backend: Backend = mockk() private val nm: BackupNotificationManager = mockk() @@ -51,7 +51,7 @@ internal class ApkBackupManagerTest : TransportTest() { packageService = packageService, apkBackup = apkBackup, iconManager = iconManager, - pluginManager = storagePluginManager, + backendManager = backendManager, nm = nm, ) @@ -59,7 +59,7 @@ internal class ApkBackupManagerTest : TransportTest() { private val packageMetadata: PackageMetadata = mockk() init { - every { storagePluginManager.backend } returns backend + every { backendManager.backend } returns backend } @Test diff --git a/core/src/main/java/org/calyxos/seedvault/core/ByteArrayUtils.kt b/core/src/main/java/org/calyxos/seedvault/core/ByteArrayUtils.kt index bc8d65c63..4ce2c4e79 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/ByteArrayUtils.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/ByteArrayUtils.kt @@ -5,6 +5,7 @@ package org.calyxos.seedvault.core -internal fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } +public fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) } -internal fun String.toByteArrayFromHex() = chunked(2).map { it.toInt(16).toByte() }.toByteArray() +public fun String.toByteArrayFromHex(): ByteArray = + chunked(2).map { it.toInt(16).toByte() }.toByteArray() diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/BackendFactory.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/BackendFactory.kt new file mode 100644 index 000000000..953e0c56c --- /dev/null +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/BackendFactory.kt @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.calyxos.seedvault.core.backends + +import android.content.Context +import org.calyxos.seedvault.core.backends.saf.SafBackend +import org.calyxos.seedvault.core.backends.saf.SafProperties +import org.calyxos.seedvault.core.backends.webdav.WebDavBackend +import org.calyxos.seedvault.core.backends.webdav.WebDavConfig + +public class BackendFactory( + private val context: Context, +) { + public fun createSafBackend(config: SafProperties): Backend = SafBackend(context, config) + public fun createWebDavBackend(config: WebDavConfig): Backend = WebDavBackend(config) +} diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/FileHandle.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/FileHandle.kt index 0b242b574..6c2a1240d 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/backends/FileHandle.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/FileHandle.kt @@ -20,6 +20,12 @@ public sealed class FileHandle { public data class TopLevelFolder(override val name: String) : FileHandle() { override val relativePath: String = name + + public companion object { + public fun fromAndroidId(androidId: String): TopLevelFolder { + return TopLevelFolder("$androidId.sv") + } + } } public sealed class LegacyAppBackupFile : FileHandle() { diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt index 43221412a..9bb5aedaf 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafBackend.kt @@ -39,7 +39,7 @@ internal const val ROOT_ID_DEVICE = "primary" public class SafBackend( private val appContext: Context, - private val safConfig: SafConfig, + private val safProperties: SafProperties, root: String = DIRECTORY_ROOT, ) : Backend { @@ -48,16 +48,16 @@ public class SafBackend( /** * Attention: This context might be from a different user. Use with care. */ - private val context: Context get() = appContext.getBackendContext { safConfig.isUsb } - private val cache = DocumentFileCache(context, safConfig.getDocumentFile(context), root) + private val context: Context get() = appContext.getBackendContext { safProperties.isUsb } + private val cache = DocumentFileCache(context, safProperties.getDocumentFile(context), root) override suspend fun test(): Boolean { return cache.getRootFile().isDirectory } override suspend fun getFreeSpace(): Long? { - val rootId = safConfig.rootId ?: return null - val authority = safConfig.uri.authority + val rootId = safProperties.rootId ?: return null + val authority = safProperties.uri.authority // using DocumentsContract#buildRootUri(String, String) with rootId directly doesn't work val rootUri = DocumentsContract.buildRootsUri(authority) val projection = arrayOf(COLUMN_AVAILABLE_BYTES) @@ -74,8 +74,8 @@ public class SafBackend( return if (bytesAvailable == null && authority == AUTHORITY_STORAGE) { if (rootId == ROOT_ID_DEVICE) { StatFs(Environment.getDataDirectory().absolutePath).availableBytes - } else if (safConfig.isUsb) { - val documentId = safConfig.uri.lastPathSegment ?: return null + } else if (safProperties.isUsb) { + val documentId = safProperties.uri.lastPathSegment ?: return null StatFs("/mnt/media_rw/${documentId.trimEnd(':')}").availableBytes } else null } else bytesAvailable @@ -188,7 +188,7 @@ public class SafBackend( } override val providerPackageName: String? by lazy { - val authority = safConfig.uri.authority ?: return@lazy null + val authority = safProperties.uri.authority ?: return@lazy null val providerInfo = context.packageManager.resolveContentProvider(authority, 0) ?: return@lazy null providerInfo.packageName diff --git a/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafConfig.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafProperties.kt similarity index 94% rename from core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafConfig.kt rename to core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafProperties.kt index 8d0d924a8..2da86c37d 100644 --- a/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafConfig.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/saf/SafProperties.kt @@ -12,7 +12,7 @@ import androidx.annotation.WorkerThread import androidx.documentfile.provider.DocumentFile import org.calyxos.seedvault.core.backends.BackendProperties -public data class SafConfig( +public data class SafProperties( override val config: Uri, override val name: String, override val isUsb: Boolean, @@ -24,7 +24,7 @@ public data class SafConfig( val rootId: String?, ) : BackendProperties() { - internal val uri: Uri = config + public val uri: Uri = config public fun getDocumentFile(context: Context): DocumentFile = DocumentFile.fromTreeUri(context, config) diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavProperties.kt b/core/src/main/java/org/calyxos/seedvault/core/backends/webdav/WebDavProperties.kt similarity index 60% rename from app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavProperties.kt rename to core/src/main/java/org/calyxos/seedvault/core/backends/webdav/WebDavProperties.kt index 7f06cf7cd..4d47fb373 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/webdav/WebDavProperties.kt +++ b/core/src/main/java/org/calyxos/seedvault/core/backends/webdav/WebDavProperties.kt @@ -3,16 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -package com.stevesoltys.seedvault.plugins.webdav +package org.calyxos.seedvault.core.backends.webdav import android.content.Context -import com.stevesoltys.seedvault.plugins.StorageProperties -import org.calyxos.seedvault.core.backends.webdav.WebDavConfig +import org.calyxos.seedvault.core.backends.BackendProperties -data class WebDavProperties( +public data class WebDavProperties( override val config: WebDavConfig, override val name: String, -) : StorageProperties() { +) : BackendProperties() { override val isUsb: Boolean = false override val requiresNetwork: Boolean = true override fun isUnavailableUsb(context: Context): Boolean = false diff --git a/storage/demo/src/main/java/de/grobox/storagebackuptester/plugin/TestSafBackend.kt b/storage/demo/src/main/java/de/grobox/storagebackuptester/plugin/TestSafBackend.kt index d3053689f..514c58db8 100644 --- a/storage/demo/src/main/java/de/grobox/storagebackuptester/plugin/TestSafBackend.kt +++ b/storage/demo/src/main/java/de/grobox/storagebackuptester/plugin/TestSafBackend.kt @@ -12,7 +12,7 @@ import org.calyxos.seedvault.core.backends.FileHandle import org.calyxos.seedvault.core.backends.FileInfo import org.calyxos.seedvault.core.backends.TopLevelFolder import org.calyxos.seedvault.core.backends.saf.SafBackend -import org.calyxos.seedvault.core.backends.saf.SafConfig +import org.calyxos.seedvault.core.backends.saf.SafProperties import java.io.InputStream import java.io.OutputStream import kotlin.reflect.KClass @@ -22,15 +22,15 @@ class TestSafBackend( private val getLocationUri: () -> Uri?, ) : Backend { - private val safConfig - get() = SafConfig( + private val safProperties + get() = SafProperties( config = getLocationUri() ?: error("no uri"), name = "foo", isUsb = false, requiresNetwork = false, rootId = "bar", ) - private val delegate: SafBackend get() = SafBackend(appContext, safConfig) + private val delegate: SafBackend get() = SafBackend(appContext, safProperties) private val nullStream = object : OutputStream() { override fun write(b: Int) { diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/ByteArrayUtils.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/ByteArrayUtils.kt deleted file mode 100644 index 2cabdd65e..000000000 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/ByteArrayUtils.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The Calyx Institute - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.calyxos.backup.storage - -internal fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) } - -internal fun String.toByteArrayFromHex() = chunked(2).map { it.toInt(16).toByte() }.toByteArray() diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/UriUtils.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/UriUtils.kt index 2162a0101..773c7a168 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/UriUtils.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/UriUtils.kt @@ -5,50 +5,14 @@ package org.calyxos.backup.storage -import android.content.ContentResolver import android.net.Uri -import android.provider.MediaStore import org.calyxos.backup.storage.api.MediaType import org.calyxos.backup.storage.api.mediaItems import org.calyxos.backup.storage.backup.BackupMediaFile import org.calyxos.backup.storage.db.StoredUri -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream internal fun Uri.toStoredUri(): StoredUri = StoredUri(this) -internal fun Uri.getDocumentPath(): String? { - return lastPathSegment?.split(':')?.getOrNull(1) -} - -internal fun Uri.getVolume(): String? { - val volume = lastPathSegment?.split(':')?.getOrNull(0) - return if (volume == "primary") MediaStore.VOLUME_EXTERNAL_PRIMARY else volume -} - -@Throws(IOException::class) -public fun Uri.openInputStream(contentResolver: ContentResolver): InputStream { - return try { - contentResolver.openInputStream(this) - } catch (e: IllegalArgumentException) { - // This is necessary, because contrary to the documentation, files that have been deleted - // after we retrieved their Uri, will throw an IllegalArgumentException - throw IOException(e) - } ?: throw IOException("Stream for $this returned null") -} - -@Throws(IOException::class) -public fun Uri.openOutputStream(contentResolver: ContentResolver): OutputStream { - return try { - contentResolver.openOutputStream(this, "wt") - } catch (e: IllegalArgumentException) { - // This is necessary, because contrary to the documentation, files that have been deleted - // after we retrieved their Uri, will throw an IllegalArgumentException - throw IOException(e) - } ?: throw IOException("Stream for $this returned null") -} - internal fun Uri.getMediaType(): MediaType? { val str = toString() for (item in mediaItems) { diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/api/BackupContentType.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/api/BackupContentType.kt index 04d1cb38b..21df9889d 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/api/BackupContentType.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/api/BackupContentType.kt @@ -11,7 +11,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import org.calyxos.backup.storage.R import org.calyxos.backup.storage.backup.BackupMediaFile -import org.calyxos.backup.storage.getDocumentPath +import org.calyxos.seedvault.core.backends.saf.getDocumentPath // hidden in DocumentsContract public const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY: String = @@ -38,7 +38,7 @@ public sealed class BackupContentType( public object Custom : BackupContentType(R.drawable.ic_folder) { public fun getName(uri: Uri): String { val path = uri.getDocumentPath()!! - return if (path.isBlank()) "/" else path + return path.ifBlank { "/" } } } } diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt index 9b87c1fbf..bedd5dcf3 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt @@ -25,7 +25,6 @@ import org.calyxos.backup.storage.backup.BackupSnapshot import org.calyxos.backup.storage.backup.ChunksCacheRepopulater import org.calyxos.backup.storage.db.Db import org.calyxos.backup.storage.getCurrentBackupSnapshots -import org.calyxos.backup.storage.getDocumentPath import org.calyxos.backup.storage.getMediaType import org.calyxos.backup.storage.prune.Pruner import org.calyxos.backup.storage.prune.RetentionManager @@ -37,6 +36,7 @@ import org.calyxos.backup.storage.scanner.MediaScanner import org.calyxos.backup.storage.toStoredUri import org.calyxos.seedvault.core.backends.Backend import org.calyxos.seedvault.core.backends.FileBackupFileType +import org.calyxos.seedvault.core.backends.saf.getDocumentPath import org.calyxos.seedvault.core.crypto.KeyManager import java.io.IOException import java.util.concurrent.atomic.AtomicBoolean diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Chunker.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Chunker.kt index 4a31343ac..4a52f0b97 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Chunker.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/Chunker.kt @@ -6,7 +6,7 @@ package org.calyxos.backup.storage.backup import org.calyxos.backup.storage.db.CachedChunk -import org.calyxos.backup.storage.toHexString +import org.calyxos.seedvault.core.toHexString import java.io.IOException import java.io.InputStream import javax.crypto.Mac diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/FileBackup.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/FileBackup.kt index f9f516e7d..6845e4129 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/FileBackup.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/FileBackup.kt @@ -13,7 +13,7 @@ import org.calyxos.backup.storage.content.DocFile import org.calyxos.backup.storage.content.MediaFile import org.calyxos.backup.storage.db.CachedFile import org.calyxos.backup.storage.db.FilesCache -import org.calyxos.backup.storage.openInputStream +import org.calyxos.seedvault.core.backends.saf.openInputStream import java.io.IOException import java.security.GeneralSecurityException diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/SmallFileBackup.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/SmallFileBackup.kt index 3d6d2cc39..a1b7b4930 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/SmallFileBackup.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/SmallFileBackup.kt @@ -13,11 +13,10 @@ import org.calyxos.backup.storage.content.DocFile import org.calyxos.backup.storage.content.MediaFile import org.calyxos.backup.storage.db.CachedFile import org.calyxos.backup.storage.db.FilesCache -import org.calyxos.backup.storage.openInputStream +import org.calyxos.seedvault.core.backends.saf.openInputStream import java.io.IOException import java.security.GeneralSecurityException -@Suppress("BlockingMethodInNonBlockingContext") internal class SmallFileBackup( private val contentResolver: ContentResolver, private val filesCache: FilesCache, diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ZipChunker.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ZipChunker.kt index 61532dd59..295ac82e2 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ZipChunker.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/ZipChunker.kt @@ -7,7 +7,7 @@ package org.calyxos.backup.storage.backup import org.calyxos.backup.storage.content.ContentFile import org.calyxos.backup.storage.db.CachedChunk -import org.calyxos.backup.storage.toHexString +import org.calyxos.seedvault.core.toHexString import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/crypto/StreamCrypto.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/crypto/StreamCrypto.kt index ff4891cd6..ad9f350ca 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/crypto/StreamCrypto.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/crypto/StreamCrypto.kt @@ -9,7 +9,7 @@ import com.google.crypto.tink.subtle.AesGcmHkdfStreaming import org.calyxos.backup.storage.backup.Backup.Companion.VERSION import org.calyxos.backup.storage.crypto.Hkdf.ALGORITHM_HMAC import org.calyxos.backup.storage.crypto.Hkdf.KEY_SIZE_BYTES -import org.calyxos.backup.storage.toByteArrayFromHex +import org.calyxos.seedvault.core.toByteArrayFromHex import java.io.IOException import java.io.InputStream import java.io.OutputStream diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt index b9519778a..dedd3d808 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/restore/FileRestore.kt @@ -15,8 +15,8 @@ import android.util.Log import org.calyxos.backup.storage.api.MediaType import org.calyxos.backup.storage.api.RestoreObserver import org.calyxos.backup.storage.backup.BackupMediaFile -import org.calyxos.backup.storage.openOutputStream import org.calyxos.backup.storage.scanner.MediaScanner +import org.calyxos.seedvault.core.backends.saf.openOutputStream import java.io.File import java.io.IOException import java.io.OutputStream @@ -24,7 +24,6 @@ import kotlin.random.Random private const val TAG = "FileRestore" -@Suppress("BlockingMethodInNonBlockingContext") internal class FileRestore( private val context: Context, private val mediaScanner: MediaScanner, @@ -63,7 +62,6 @@ internal class FileRestore( streamWriter: suspend (outputStream: OutputStream) -> Long, ): Long { // ensure directory exists - @Suppress("DEPRECATION") val dir = File("${getExternalStorageDirectory()}/${docFile.dir}") if (!dir.mkdirs() && !dir.isDirectory) { throw IOException("Could not create ${dir.absolutePath}") @@ -143,7 +141,6 @@ internal class FileRestore( } private fun setLastModifiedOnMediaFile(mediaFile: BackupMediaFile, uri: Uri) { - @Suppress("DEPRECATION") val extDir = getExternalStorageDirectory() // re-set lastModified as we can't use the MediaStore for this (read-only property) diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/DocumentScanner.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/DocumentScanner.kt index 1177e8e38..8d1dcd806 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/DocumentScanner.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/scanner/DocumentScanner.kt @@ -14,8 +14,8 @@ import androidx.core.database.getLongOrNull import androidx.core.database.getStringOrNull import org.calyxos.backup.storage.api.BackupFile import org.calyxos.backup.storage.content.DocFile -import org.calyxos.backup.storage.getDocumentPath -import org.calyxos.backup.storage.getVolume +import org.calyxos.seedvault.core.backends.saf.getDocumentPath +import org.calyxos.seedvault.core.backends.saf.getVolume public class DocumentScanner(context: Context) {