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.
This commit is contained in:
Torsten Grote 2024-05-09 14:28:03 -03:00 committed by Chirayu Desai
parent 066b147315
commit bb562a4cb2
7 changed files with 34 additions and 5 deletions

View file

@ -32,7 +32,7 @@ class KoinInstrumentationTestApp : App() {
single { spyk(BackupNotificationManager(context)) } single { spyk(BackupNotificationManager(context)) }
single { spyk(FullBackup(get(), get(), get(), get(), get())) } 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(InputFactory()) }
single { spyk(FullRestore(get(), get(), get(), get(), get())) } single { spyk(FullRestore(get(), get(), get(), get(), get())) }

View file

@ -9,6 +9,7 @@ import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import at.bitfire.dav4jvm.exception.HttpException
import java.io.IOException import java.io.IOException
abstract class StorageProperties<T> { abstract class StorageProperties<T> {
@ -37,5 +38,12 @@ abstract class StorageProperties<T> {
} }
fun Exception.isOutOfSpace(): Boolean { 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
}
} }

View file

@ -382,6 +382,7 @@ internal class BackupCoordinator(
onPackageBackedUp(packageInfo, BackupType.FULL, size) onPackageBackedUp(packageInfo, BackupType.FULL, size)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error calling onPackageBackedUp for $packageName", e) Log.e(TAG, "Error calling onPackageBackedUp for $packageName", e)
if (e.isOutOfSpace()) nm.onInsufficientSpaceError()
result = TRANSPORT_PACKAGE_REJECTED result = TRANSPORT_PACKAGE_REJECTED
} }
result result

View file

@ -19,6 +19,7 @@ val backupModule = module {
KVBackup( KVBackup(
pluginManager = get(), pluginManager = get(),
settingsManager = get(), settingsManager = get(),
nm = get(),
inputFactory = get(), inputFactory = get(),
crypto = get(), crypto = get(),
dbManager = get(), dbManager = get(),

View file

@ -14,7 +14,9 @@ import com.stevesoltys.seedvault.crypto.Crypto
import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.header.getADForKV import com.stevesoltys.seedvault.header.getADForKV
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.isOutOfSpace
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import java.io.IOException import java.io.IOException
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
@ -34,6 +36,7 @@ private val TAG = KVBackup::class.java.simpleName
internal class KVBackup( internal class KVBackup(
private val pluginManager: StoragePluginManager, private val pluginManager: StoragePluginManager,
private val settingsManager: SettingsManager, private val settingsManager: SettingsManager,
private val nm: BackupNotificationManager,
private val inputFactory: InputFactory, private val inputFactory: InputFactory,
private val crypto: Crypto, private val crypto: Crypto,
private val dbManager: KvDbManager, private val dbManager: KvDbManager,
@ -214,6 +217,7 @@ internal class KVBackup(
TRANSPORT_OK TRANSPORT_OK
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error uploading DB", e) Log.e(TAG, "Error uploading DB", e)
if (e.isOutOfSpace()) nm.onInsufficientSpaceError()
TRANSPORT_ERROR TRANSPORT_ERROR
} finally { } finally {
this.state = null this.state = null

View file

@ -63,8 +63,14 @@ internal class CoordinatorIntegrationTest : TransportTest() {
@Suppress("Deprecation") @Suppress("Deprecation")
private val legacyPlugin = mockk<LegacyStoragePlugin>() private val legacyPlugin = mockk<LegacyStoragePlugin>()
private val backupPlugin = mockk<StoragePlugin<*>>() private val backupPlugin = mockk<StoragePlugin<*>>()
private val kvBackup = private val kvBackup = KVBackup(
KVBackup(storagePluginManager, settingsManager, inputFactory, cryptoImpl, dbManager) pluginManager = storagePluginManager,
settingsManager = settingsManager,
nm = notificationManager,
inputFactory = inputFactory,
crypto = cryptoImpl,
dbManager = dbManager,
)
private val fullBackup = FullBackup( private val fullBackup = FullBackup(
pluginManager = storagePluginManager, pluginManager = storagePluginManager,
settingsManager = settingsManager, settingsManager = settingsManager,

View file

@ -14,6 +14,7 @@ import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.header.getADForKV import com.stevesoltys.seedvault.header.getADForKV
import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import io.mockk.CapturingSlot import io.mockk.CapturingSlot
import io.mockk.Runs import io.mockk.Runs
import io.mockk.coEvery import io.mockk.coEvery
@ -34,10 +35,18 @@ import kotlin.random.Random
internal class KVBackupTest : BackupTest() { internal class KVBackupTest : BackupTest() {
private val pluginManager = mockk<StoragePluginManager>() private val pluginManager = mockk<StoragePluginManager>()
private val notificationManager = mockk<BackupNotificationManager>()
private val dataInput = mockk<BackupDataInput>() private val dataInput = mockk<BackupDataInput>()
private val dbManager = mockk<KvDbManager>() 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 db = mockk<KVDb>()
private val plugin = mockk<StoragePlugin<*>>() private val plugin = mockk<StoragePlugin<*>>()