From ba2891826af0ee35d667c08cbd6807a3e497e785 Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Thu, 9 May 2024 14:28:03 -0300 Subject: [PATCH] Catch out 507 HTTP error when using WebDAV Nextcloud has a bug that lets us write chunked transfers over quota: https://github.com/nextcloud/server/issues/7993 However, when we upload small files, we can get the proper 507 response and thus detect out of space situations and warn the user about them. --- .../seedvault/KoinInstrumentationTestApp.kt | 2 +- .../seedvault/plugins/StorageProperties.kt | 10 +++++++++- .../seedvault/transport/backup/BackupCoordinator.kt | 1 + .../seedvault/transport/backup/BackupModule.kt | 1 + .../seedvault/transport/backup/KVBackup.kt | 4 ++++ .../seedvault/transport/CoordinatorIntegrationTest.kt | 10 ++++++++-- .../seedvault/transport/backup/KVBackupTest.kt | 11 ++++++++++- 7 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt index d5f98989..d5b78ecc 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt @@ -32,7 +32,7 @@ class KoinInstrumentationTestApp : App() { single { spyk(BackupNotificationManager(context)) } single { spyk(FullBackup(get(), get(), get(), get(), get())) } - single { spyk(KVBackup(get(), get(), get(), get(), get())) } + single { spyk(KVBackup(get(), get(), get(), get(), get(), get())) } single { spyk(InputFactory()) } single { spyk(FullRestore(get(), get(), get(), get(), get())) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/plugins/StorageProperties.kt b/app/src/main/java/com/stevesoltys/seedvault/plugins/StorageProperties.kt index 8187d312..5fa3cfd4 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/plugins/StorageProperties.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/plugins/StorageProperties.kt @@ -9,6 +9,7 @@ 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<T> { @@ -37,5 +38,12 @@ abstract class StorageProperties<T> { } fun Exception.isOutOfSpace(): Boolean { - return this is IOException && message?.contains("No space left on device") == true + 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/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index bacd39a6..82022934 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 @@ -382,6 +382,7 @@ internal class BackupCoordinator( onPackageBackedUp(packageInfo, BackupType.FULL, size) } catch (e: Exception) { Log.e(TAG, "Error calling onPackageBackedUp for $packageName", e) + if (e.isOutOfSpace()) nm.onInsufficientSpaceError() result = TRANSPORT_PACKAGE_REJECTED } result 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 f600c8b0..e120c689 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 @@ -19,6 +19,7 @@ val backupModule = module { KVBackup( pluginManager = get(), settingsManager = get(), + nm = get(), inputFactory = get(), crypto = get(), dbManager = get(), 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 c867144f..a4732388 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 @@ -14,7 +14,9 @@ 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.settings.SettingsManager +import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import java.io.IOException import java.util.zip.GZIPOutputStream @@ -34,6 +36,7 @@ private val TAG = KVBackup::class.java.simpleName internal class KVBackup( private val pluginManager: StoragePluginManager, private val settingsManager: SettingsManager, + private val nm: BackupNotificationManager, private val inputFactory: InputFactory, private val crypto: Crypto, private val dbManager: KvDbManager, @@ -214,6 +217,7 @@ internal class KVBackup( TRANSPORT_OK } catch (e: IOException) { Log.e(TAG, "Error uploading DB", e) + if (e.isOutOfSpace()) nm.onInsufficientSpaceError() TRANSPORT_ERROR } finally { this.state = null 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 347641f1..4e0ffd43 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt @@ -63,8 +63,14 @@ internal class CoordinatorIntegrationTest : TransportTest() { @Suppress("Deprecation") private val legacyPlugin = mockk<LegacyStoragePlugin>() private val backupPlugin = mockk<StoragePlugin<*>>() - private val kvBackup = - KVBackup(storagePluginManager, settingsManager, inputFactory, cryptoImpl, dbManager) + private val kvBackup = KVBackup( + pluginManager = storagePluginManager, + settingsManager = settingsManager, + nm = notificationManager, + inputFactory = inputFactory, + crypto = cryptoImpl, + dbManager = dbManager, + ) private val fullBackup = FullBackup( pluginManager = storagePluginManager, settingsManager = settingsManager, 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 f9bbe264..a60c9a11 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 @@ -14,6 +14,7 @@ import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.getADForKV import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePluginManager +import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import io.mockk.CapturingSlot import io.mockk.Runs import io.mockk.coEvery @@ -34,10 +35,18 @@ import kotlin.random.Random internal class KVBackupTest : BackupTest() { private val pluginManager = mockk<StoragePluginManager>() + private val notificationManager = mockk<BackupNotificationManager>() private val dataInput = mockk<BackupDataInput>() private val dbManager = mockk<KvDbManager>() - private val backup = KVBackup(pluginManager, settingsManager, inputFactory, crypto, dbManager) + private val backup = KVBackup( + pluginManager = pluginManager, + settingsManager = settingsManager, + nm = notificationManager, + inputFactory = inputFactory, + crypto = crypto, + dbManager = dbManager + ) private val db = mockk<KVDb>() private val plugin = mockk<StoragePlugin<*>>()