mirror of
https://github.com/bitfireAT/davx5-ose.git
synced 2026-01-23 22:28:49 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
722edc1ea7 | ||
|
|
7b68ec0832 | ||
|
|
530e62f1f4 | ||
|
|
f0a8694436 | ||
|
|
dd839f5b96 |
@@ -69,6 +69,7 @@ class HttpClientBuilder @Inject constructor(
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
init {
|
||||
// make sure Conscrypt is available when the HttpClientBuilder class is loaded the first time
|
||||
ConscryptIntegration().initialize()
|
||||
@@ -84,12 +85,18 @@ class HttpClientBuilder @Inject constructor(
|
||||
* The shared client is available for the lifetime of the application and must not be shut down or
|
||||
* closed (which is not necessary, according to its documentation).
|
||||
*/
|
||||
val sharedOkHttpClient = OkHttpClient.Builder()
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
.pingInterval(45, TimeUnit.SECONDS) // avoid cancellation because of missing traffic; only works for HTTP/2
|
||||
.build()
|
||||
val sharedOkHttpClient = OkHttpClient.Builder().apply {
|
||||
configureTimeouts(this)
|
||||
}.build()
|
||||
|
||||
private fun configureTimeouts(okBuilder: OkHttpClient.Builder) {
|
||||
okBuilder
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
.pingInterval(45, TimeUnit.SECONDS) // avoid cancellation because of missing traffic; only works for HTTP/2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -407,6 +414,11 @@ class HttpClientBuilder @Inject constructor(
|
||||
|
||||
config {
|
||||
// OkHttpClient.Builder configuration here
|
||||
|
||||
// we don't use the sharedOkHttpClient, so we have to apply timeouts again
|
||||
configureTimeouts(this)
|
||||
|
||||
// build most config on okhttp level
|
||||
configureOkHttp(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,19 @@ import android.provider.DocumentsContract.buildChildDocumentsUri
|
||||
import android.provider.DocumentsContract.buildRootsUri
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
import at.bitfire.dav4jvm.okhttp.exception.HttpException
|
||||
import at.bitfire.dav4jvm.ktor.exception.HttpException
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.ui.webdav.WebdavMountsActivity
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.logging.Logger
|
||||
|
||||
object DocumentProviderUtils {
|
||||
object DocumentProviderUtils {
|
||||
|
||||
const val MAX_DISPLAYNAME_TO_MEMBERNAME_ATTEMPTS = 5
|
||||
|
||||
private val logger
|
||||
get() = Logger.getLogger(javaClass.name)
|
||||
|
||||
internal fun displayNameToMemberName(displayName: String, appendNumber: Int = 0): String {
|
||||
val safeName = displayName.filterNot { it.isISOControl() }
|
||||
|
||||
@@ -37,24 +41,23 @@ object DocumentProviderUtils {
|
||||
}
|
||||
|
||||
internal fun notifyFolderChanged(context: Context, parentDocumentId: Long?) {
|
||||
if (parentDocumentId != null)
|
||||
context.contentResolver.notifyChange(
|
||||
buildChildDocumentsUri(
|
||||
context.getString(R.string.webdav_authority),
|
||||
parentDocumentId.toString()
|
||||
),
|
||||
null
|
||||
if (parentDocumentId != null) {
|
||||
val uri = buildChildDocumentsUri(
|
||||
context.getString(R.string.webdav_authority),
|
||||
parentDocumentId.toString()
|
||||
)
|
||||
logger.fine("Notifying observers of $uri")
|
||||
context.contentResolver.notifyChange(uri, null)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun notifyFolderChanged(context: Context, parentDocumentId: String) {
|
||||
context.contentResolver.notifyChange(
|
||||
buildChildDocumentsUri(
|
||||
context.getString(R.string.webdav_authority),
|
||||
parentDocumentId
|
||||
),
|
||||
null
|
||||
val uri = buildChildDocumentsUri(
|
||||
context.getString(R.string.webdav_authority),
|
||||
parentDocumentId
|
||||
)
|
||||
logger.fine("Notifying observers of $uri")
|
||||
context.contentResolver.notifyChange(uri, null)
|
||||
}
|
||||
|
||||
internal fun notifyMountsChanged(context: Context) {
|
||||
@@ -66,12 +69,20 @@ object DocumentProviderUtils {
|
||||
}
|
||||
|
||||
internal fun HttpException.throwForDocumentProvider(context: Context, ignorePreconditionFailed: Boolean = false) {
|
||||
throwForDocumentProvider(context, statusCode, this, ignorePreconditionFailed)
|
||||
}
|
||||
|
||||
internal fun at.bitfire.dav4jvm.okhttp.exception.HttpException.throwForDocumentProvider(context: Context, ignorePreconditionFailed: Boolean = false) {
|
||||
throwForDocumentProvider(context, statusCode, this, ignorePreconditionFailed)
|
||||
}
|
||||
|
||||
private fun throwForDocumentProvider(context: Context, statusCode: Int, ex: Exception, ignorePreconditionFailed: Boolean) {
|
||||
when (statusCode) {
|
||||
401 -> {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
val intent = Intent(context, WebdavMountsActivity::class.java)
|
||||
throw AuthenticationRequiredException(
|
||||
this,
|
||||
ex,
|
||||
TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(intent)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
@@ -86,5 +97,5 @@ internal fun HttpException.throwForDocumentProvider(context: Context, ignorePrec
|
||||
}
|
||||
|
||||
// re-throw
|
||||
throw this
|
||||
throw ex
|
||||
}
|
||||
@@ -5,8 +5,8 @@
|
||||
package at.bitfire.davdroid.webdav.operation
|
||||
|
||||
import android.content.Context
|
||||
import at.bitfire.dav4jvm.okhttp.DavResource
|
||||
import at.bitfire.dav4jvm.okhttp.exception.HttpException
|
||||
import at.bitfire.dav4jvm.ktor.DavResource
|
||||
import at.bitfire.dav4jvm.ktor.exception.HttpException
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.db.WebDavDocument
|
||||
import at.bitfire.davdroid.di.IoDispatcher
|
||||
@@ -14,9 +14,10 @@ import at.bitfire.davdroid.webdav.DavHttpClientBuilder
|
||||
import at.bitfire.davdroid.webdav.DocumentProviderUtils
|
||||
import at.bitfire.davdroid.webdav.throwForDocumentProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.ktor.http.URLBuilder
|
||||
import io.ktor.http.appendPathSegments
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.logging.Logger
|
||||
import javax.inject.Inject
|
||||
@@ -31,7 +32,7 @@ class CopyDocumentOperation @Inject constructor(
|
||||
|
||||
private val documentDao = db.webDavDocumentDao()
|
||||
|
||||
operator fun invoke(sourceDocumentId: String, targetParentDocumentId: String): String = runBlocking {
|
||||
operator fun invoke(sourceDocumentId: String, targetParentDocumentId: String): String = runBlocking(ioDispatcher) {
|
||||
logger.fine("WebDAV copyDocument $sourceDocumentId $targetParentDocumentId")
|
||||
val srcDoc = documentDao.get(sourceDocumentId.toLong()) ?: throw FileNotFoundException()
|
||||
val dstFolder = documentDao.get(targetParentDocumentId.toLong()) ?: throw FileNotFoundException()
|
||||
@@ -40,21 +41,22 @@ class CopyDocumentOperation @Inject constructor(
|
||||
if (srcDoc.mountId != dstFolder.mountId)
|
||||
throw UnsupportedOperationException("Can't COPY between WebDAV servers")
|
||||
|
||||
val client = httpClientBuilder.build(srcDoc.mountId)
|
||||
val dav = DavResource(client, srcDoc.toHttpUrl(db))
|
||||
val dstUrl = dstFolder.toHttpUrl(db).newBuilder()
|
||||
.addPathSegment(name)
|
||||
.build()
|
||||
httpClientBuilder
|
||||
.buildKtor(srcDoc.mountId)
|
||||
.use { httpClient ->
|
||||
val dav = DavResource(httpClient, srcDoc.toKtorUrl(db))
|
||||
val dstUrl = URLBuilder(dstFolder.toKtorUrl(db))
|
||||
.appendPathSegments(name)
|
||||
.build()
|
||||
|
||||
try {
|
||||
runInterruptible(ioDispatcher) {
|
||||
dav.copy(dstUrl, false) {
|
||||
// successfully copied
|
||||
try {
|
||||
dav.copy(dstUrl, false) {
|
||||
// successfully copied
|
||||
}
|
||||
} catch (e: HttpException) {
|
||||
e.throwForDocumentProvider(context)
|
||||
}
|
||||
}
|
||||
} catch (e: HttpException) {
|
||||
e.throwForDocumentProvider(context)
|
||||
}
|
||||
|
||||
val dstDocId = documentDao.insertOrReplace(
|
||||
WebDavDocument(
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
package at.bitfire.davdroid.webdav.operation
|
||||
|
||||
import android.content.Context
|
||||
import at.bitfire.dav4jvm.okhttp.DavResource
|
||||
import at.bitfire.dav4jvm.okhttp.exception.HttpException
|
||||
import at.bitfire.dav4jvm.ktor.DavResource
|
||||
import at.bitfire.dav4jvm.ktor.exception.HttpException
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.di.IoDispatcher
|
||||
import at.bitfire.davdroid.webdav.DavHttpClientBuilder
|
||||
import at.bitfire.davdroid.webdav.DocumentProviderUtils
|
||||
import at.bitfire.davdroid.webdav.throwForDocumentProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.ktor.http.URLBuilder
|
||||
import io.ktor.http.appendPathSegments
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.logging.Logger
|
||||
import javax.inject.Inject
|
||||
@@ -30,7 +31,7 @@ class MoveDocumentOperation @Inject constructor(
|
||||
|
||||
private val documentDao = db.webDavDocumentDao()
|
||||
|
||||
operator fun invoke(sourceDocumentId: String, sourceParentDocumentId: String, targetParentDocumentId: String): String = runBlocking {
|
||||
operator fun invoke(sourceDocumentId: String, sourceParentDocumentId: String, targetParentDocumentId: String): String = runBlocking(ioDispatcher) {
|
||||
logger.fine("WebDAV moveDocument $sourceDocumentId $sourceParentDocumentId $targetParentDocumentId")
|
||||
val doc = documentDao.get(sourceDocumentId.toLong()) ?: throw FileNotFoundException()
|
||||
val dstParent = documentDao.get(targetParentDocumentId.toLong()) ?: throw FileNotFoundException()
|
||||
@@ -38,27 +39,28 @@ class MoveDocumentOperation @Inject constructor(
|
||||
if (doc.mountId != dstParent.mountId)
|
||||
throw UnsupportedOperationException("Can't MOVE between WebDAV servers")
|
||||
|
||||
val newLocation = dstParent.toHttpUrl(db).newBuilder()
|
||||
.addPathSegment(doc.name)
|
||||
.build()
|
||||
httpClientBuilder
|
||||
.buildKtor(doc.mountId)
|
||||
.use { httpClient ->
|
||||
val newLocation = URLBuilder(dstParent.toKtorUrl(db))
|
||||
.appendPathSegments(doc.name)
|
||||
.build()
|
||||
|
||||
val client = httpClientBuilder.build(doc.mountId)
|
||||
val dav = DavResource(client, doc.toHttpUrl(db))
|
||||
try {
|
||||
runInterruptible(ioDispatcher) {
|
||||
dav.move(newLocation, false) {
|
||||
// successfully moved
|
||||
val dav = DavResource(httpClient, doc.toKtorUrl(db))
|
||||
try {
|
||||
dav.move(newLocation, false) {
|
||||
// successfully moved
|
||||
}
|
||||
|
||||
documentDao.update(doc.copy(parentId = dstParent.id))
|
||||
|
||||
DocumentProviderUtils.notifyFolderChanged(context, sourceParentDocumentId)
|
||||
DocumentProviderUtils.notifyFolderChanged(context, targetParentDocumentId)
|
||||
} catch (e: HttpException) {
|
||||
e.throwForDocumentProvider(context)
|
||||
}
|
||||
}
|
||||
|
||||
documentDao.update(doc.copy(parentId = dstParent.id))
|
||||
|
||||
DocumentProviderUtils.notifyFolderChanged(context, sourceParentDocumentId)
|
||||
DocumentProviderUtils.notifyFolderChanged(context, targetParentDocumentId)
|
||||
} catch (e: HttpException) {
|
||||
e.throwForDocumentProvider(context)
|
||||
}
|
||||
|
||||
doc.id.toString()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
package at.bitfire.davdroid.webdav.operation
|
||||
|
||||
import android.content.Context
|
||||
import at.bitfire.dav4jvm.okhttp.DavResource
|
||||
import at.bitfire.dav4jvm.okhttp.exception.HttpException
|
||||
import at.bitfire.dav4jvm.ktor.DavResource
|
||||
import at.bitfire.dav4jvm.ktor.exception.HttpException
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.di.IoDispatcher
|
||||
import at.bitfire.davdroid.webdav.DavHttpClientBuilder
|
||||
@@ -14,9 +14,9 @@ import at.bitfire.davdroid.webdav.DocumentProviderUtils
|
||||
import at.bitfire.davdroid.webdav.DocumentProviderUtils.displayNameToMemberName
|
||||
import at.bitfire.davdroid.webdav.throwForDocumentProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.ktor.http.URLBuilder
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.logging.Logger
|
||||
import javax.inject.Inject
|
||||
@@ -31,34 +31,36 @@ class RenameDocumentOperation @Inject constructor(
|
||||
|
||||
private val documentDao = db.webDavDocumentDao()
|
||||
|
||||
operator fun invoke(documentId: String, displayName: String): String? = runBlocking {
|
||||
operator fun invoke(documentId: String, displayName: String): String? = runBlocking(ioDispatcher) {
|
||||
logger.fine("WebDAV renameDocument $documentId $displayName")
|
||||
val doc = documentDao.get(documentId.toLong()) ?: throw FileNotFoundException()
|
||||
|
||||
val client = httpClientBuilder.build(doc.mountId)
|
||||
for (attempt in 0..DocumentProviderUtils.MAX_DISPLAYNAME_TO_MEMBERNAME_ATTEMPTS) {
|
||||
val newName = displayNameToMemberName(displayName, attempt)
|
||||
val oldUrl = doc.toHttpUrl(db)
|
||||
val newLocation = oldUrl.newBuilder()
|
||||
.removePathSegment(oldUrl.pathSegments.lastIndex)
|
||||
.addPathSegment(newName)
|
||||
.build()
|
||||
try {
|
||||
val dav = DavResource(client, oldUrl)
|
||||
runInterruptible(ioDispatcher) {
|
||||
dav.move(newLocation, false) {
|
||||
// successfully renamed
|
||||
httpClientBuilder
|
||||
.buildKtor(doc.mountId)
|
||||
.use { httpClient ->
|
||||
for (attempt in 0..DocumentProviderUtils.MAX_DISPLAYNAME_TO_MEMBERNAME_ATTEMPTS) {
|
||||
val newName = displayNameToMemberName(displayName, attempt)
|
||||
val oldUrl = doc.toKtorUrl(db)
|
||||
val newLocation = URLBuilder(oldUrl)
|
||||
.apply {
|
||||
// Remove the last path segment (current file name) and add the new name
|
||||
pathSegments = pathSegments.dropLast(1) + newName
|
||||
}.build()
|
||||
try {
|
||||
val dav = DavResource(httpClient, oldUrl)
|
||||
dav.move(newLocation, false) {
|
||||
// successfully renamed
|
||||
}
|
||||
documentDao.update(doc.copy(name = newName))
|
||||
|
||||
DocumentProviderUtils.notifyFolderChanged(context, doc.parentId)
|
||||
|
||||
return@runBlocking doc.id.toString()
|
||||
} catch (e: HttpException) {
|
||||
e.throwForDocumentProvider(context, true)
|
||||
}
|
||||
}
|
||||
documentDao.update(doc.copy(name = newName))
|
||||
|
||||
DocumentProviderUtils.notifyFolderChanged(context, doc.parentId)
|
||||
|
||||
return@runBlocking doc.id.toString()
|
||||
} catch (e: HttpException) {
|
||||
e.throwForDocumentProvider(context, true)
|
||||
}
|
||||
}
|
||||
|
||||
null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user