WebDAV: allow other MIME types for (Ranged) GET, use coroutines for streaming (#503)

* StreamingFileDescriptor: use coroutines instead of threading

* WebDAV GET: accept any MIME type, but prefer known one
This commit is contained in:
Ricki Hirner 2023-12-12 14:39:43 +01:00 committed by GitHub
parent 769147b193
commit c56461ea9e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 10 deletions

View file

@ -13,9 +13,16 @@ import at.bitfire.davdroid.network.Android10Resolver
import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import org.xbill.DNS.*
import org.xbill.DNS.ExtendedResolver
import org.xbill.DNS.Lookup
import org.xbill.DNS.Record
import org.xbill.DNS.SRVRecord
import org.xbill.DNS.SimpleResolver
import org.xbill.DNS.TXTRecord
import java.net.InetAddress
import java.util.*
import java.util.LinkedList
import java.util.Locale
import java.util.TreeMap
/**
* Some WebDAV and HTTP network utility methods.
@ -30,6 +37,19 @@ object DavUtils {
val MEDIA_TYPE_OCTET_STREAM = "application/octet-stream".toMediaType()
val MEDIA_TYPE_VCARD = "text/vcard".toMediaType()
/**
* Builds an HTTP `Accept` header that accepts anything (*/*), but optionally
* specifies a preference.
*
* @param preferred preferred MIME type (optional)
*
* @return `media-range` for `Accept` header that accepts anything, but prefers [preferred] (if it was specified)
*/
fun acceptAnything(preferred: MediaType?): String =
if (preferred != null)
"$preferred, $MIME_TYPE_ACCEPT_ALL;q=0.8"
else
MIME_TYPE_ACCEPT_ALL
@Suppress("FunctionName")
fun ARGBtoCalDAVColor(colorWithAlpha: Int): String {

View file

@ -167,7 +167,7 @@ class RandomAccessCallback private constructor(
var result: ByteArray? = null
dav.getRange(
mimeType?.toString() ?: DavUtils.MIME_TYPE_ACCEPT_ALL,
DavUtils.acceptAnything(preferred = mimeType),
key.segment * PAGE_SIZE.toLong(),
PAGE_SIZE,
ifMatch

View file

@ -12,12 +12,16 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import at.bitfire.dav4jvm.DavResource
import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.network.HttpClient
import at.bitfire.davdroid.ui.NotificationUtils
import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible
import at.bitfire.davdroid.util.DavUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.runInterruptible
import okhttp3.HttpUrl
import okhttp3.MediaType
import okhttp3.RequestBody
@ -26,7 +30,6 @@ import okio.BufferedSink
import org.apache.commons.io.FileUtils
import java.io.IOException
import java.util.logging.Level
import kotlin.concurrent.thread
/**
* @param client HTTP client [StreamingFileDescriptor] is responsible to close it
@ -64,7 +67,7 @@ class StreamingFileDescriptor(
private fun doStreaming(upload: Boolean): ParcelFileDescriptor {
val (readFd, writeFd) = ParcelFileDescriptor.createReliablePipe()
val worker = thread {
val result = CoroutineScope(Dispatchers.IO).async {
try {
if (upload)
uploadNow(readFd)
@ -92,7 +95,7 @@ class StreamingFileDescriptor(
cancellationSignal?.setOnCancelListener {
Logger.log.fine("Cancelling transfer of $url")
worker.interrupt()
result.cancel()
}
return if (upload)
@ -102,8 +105,8 @@ class StreamingFileDescriptor(
}
@WorkerThread
private fun downloadNow(writeFd: ParcelFileDescriptor) {
dav.get(mimeType?.toString() ?: DavUtils.MIME_TYPE_ACCEPT_ALL, null) { response ->
private suspend fun downloadNow(writeFd: ParcelFileDescriptor) = runInterruptible {
dav.get(DavUtils.acceptAnything(preferred = mimeType), null) { response ->
response.body?.use { body ->
if (response.isSuccessful) {
val length = response.headersContentLength()
@ -156,7 +159,7 @@ class StreamingFileDescriptor(
}
@WorkerThread
private fun uploadNow(readFd: ParcelFileDescriptor) {
private suspend fun uploadNow(readFd: ParcelFileDescriptor) = runInterruptible {
val body = object: RequestBody() {
override fun contentType(): MediaType? = mimeType
override fun isOneShot() = true

View file

@ -7,6 +7,7 @@ package at.bitfire.davdroid
import at.bitfire.davdroid.util.DavUtils
import at.bitfire.davdroid.util.DavUtils.parent
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@ -19,6 +20,13 @@ class DavUtilsTest {
val exampleURL = "http://example.com/"
@Test
fun testAcceptAnything() {
assertEquals("*/*", DavUtils.acceptAnything(null))
assertEquals("some/thing;v=2.1, */*;q=0.8", DavUtils.acceptAnything("some/thing;v=2.1".toMediaType()))
}
@Test
fun testARGBtoCalDAVColor() {
assertEquals("#00000000", DavUtils.ARGBtoCalDAVColor(0))