HttpClient: remove unnecessary close() (#1792)

* Remove unnecessary AutoCloseable implementations and client.close() calls

- Remove AutoCloseable from NextcloudLoginFlow and DavResourceFinder
- Remove client.close() calls in various classes and tests
- Update HttpClient to remove close() method

* Fix test

* Fix annotations / KDoc
This commit is contained in:
Ricki Hirner
2025-11-04 16:38:22 +01:00
committed by GitHub
parent 85548163ca
commit bd13d27e38
29 changed files with 261 additions and 317 deletions

View File

@@ -14,7 +14,6 @@ import dagger.hilt.android.testing.HiltAndroidTest
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
@@ -45,11 +44,6 @@ class CollectionTest {
Assume.assumeTrue(NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted)
}
@After
fun teardown() {
httpClient.close()
}
@Test
@SmallTest

View File

@@ -45,7 +45,6 @@ class HttpClientTest {
@After
fun tearDown() {
server.shutdown()
httpClient.close()
}

View File

@@ -31,16 +31,15 @@ class OkhttpClientTest {
@Test
@SdkSuppress(maxSdkVersion = 34)
fun testIcloudWithSettings() {
httpClientBuilder.build().use { client ->
client.okHttpClient
.newCall(
Request.Builder()
.get()
.url("https://icloud.com")
.build()
)
.execute()
}
val client = httpClientBuilder.build()
client.okHttpClient
.newCall(
Request.Builder()
.get()
.url("https://icloud.com")
.build()
)
.execute()
}
}

View File

@@ -80,7 +80,6 @@ class CollectionsWithoutHomeSetRefresherTest {
@After
fun tearDown() {
client.close()
mockServer.shutdown()
}

View File

@@ -83,7 +83,6 @@ class DavResourceFinderTest {
@After
fun tearDown() {
client.close()
server.shutdown()
}

View File

@@ -86,7 +86,6 @@ class HomeSetRefresherTest {
@After
fun tearDown() {
client.close()
mockServer.shutdown()
}

View File

@@ -81,7 +81,6 @@ class PrincipalsRefresherTest {
@After
fun tearDown() {
client.close()
mockServer.shutdown()
}

View File

@@ -68,7 +68,6 @@ class ServiceRefresherTest {
@After
fun tearDown() {
client.close()
mockServer.shutdown()
}

View File

@@ -84,7 +84,6 @@ class QueryChildDocumentsOperationTest {
@After
fun tearDown() {
client.close()
server.shutdown()
runBlocking {

View File

@@ -42,12 +42,7 @@ import javax.net.ssl.SSLContext
class HttpClient(
val okHttpClient: OkHttpClient
): AutoCloseable {
override fun close() {
// nothing to do, can be removed
}
) {
// builder

View File

@@ -33,7 +33,7 @@ import javax.inject.Inject
*/
class NextcloudLoginFlow @Inject constructor(
httpClientBuilder: HttpClient.Builder
): AutoCloseable {
) {
companion object {
const val FLOW_V1_PATH = "index.php/login/flow"
@@ -46,10 +46,6 @@ class NextcloudLoginFlow @Inject constructor(
val httpClient = httpClientBuilder
.build()
override fun close() {
httpClient.close()
}
// Login flow state
var loginUrl: HttpUrl? = null

View File

@@ -180,25 +180,23 @@ class PushRegistrationManager @Inject constructor(
return
val account = accountRepository.get().fromName(service.accountName)
httpClientBuilder.get()
val httpClient = httpClientBuilder.get()
.fromAccountAsync(account)
.build()
.use { httpClient ->
for (collection in subscribeTo)
try {
val expires = collection.pushSubscriptionExpires
// calculate next run time, but use the duplicate interval for safety (times are not exact)
val nextRun = Instant.now() + Duration.ofDays(2 * WORKER_INTERVAL_DAYS)
if (expires != null && expires >= nextRun.epochSecond)
logger.fine("Push subscription for ${collection.url} is still valid until ${collection.pushSubscriptionExpires}")
else {
// no existing subscription or expiring soon
logger.fine("Registering push subscription for ${collection.url}")
subscribe(httpClient, collection, endpoint)
}
} catch (e: Exception) {
logger.log(Level.WARNING, "Couldn't register subscription at CalDAV/CardDAV server", e)
}
for (collection in subscribeTo)
try {
val expires = collection.pushSubscriptionExpires
// calculate next run time, but use the duplicate interval for safety (times are not exact)
val nextRun = Instant.now() + Duration.ofDays(2 * WORKER_INTERVAL_DAYS)
if (expires != null && expires >= nextRun.epochSecond)
logger.fine("Push subscription for ${collection.url} is still valid until ${collection.pushSubscriptionExpires}")
else {
// no existing subscription or expiring soon
logger.fine("Registering push subscription for ${collection.url}")
subscribe(httpClient, collection, endpoint)
}
} catch (e: Exception) {
logger.log(Level.WARNING, "Couldn't register subscription at CalDAV/CardDAV server", e)
}
}
@@ -294,15 +292,13 @@ class PushRegistrationManager @Inject constructor(
return
val account = accountRepository.get().fromName(service.accountName)
httpClientBuilder.get()
val httpClient = httpClientBuilder.get()
.fromAccountAsync(account)
.build()
.use { httpClient ->
for (collection in from)
collection.pushSubscription?.toHttpUrlOrNull()?.let { url ->
logger.info("Unsubscribing Push from ${collection.url}")
unsubscribe(httpClient, collection, url)
}
for (collection in from)
collection.pushSubscription?.toHttpUrlOrNull()?.let { url ->
logger.info("Unsubscribing Push from ${collection.url}")
unsubscribe(httpClient, collection, url)
}
}

View File

@@ -173,21 +173,20 @@ class DavCollectionRepository @Inject constructor(
val service = serviceRepository.getBlocking(collection.serviceId) ?: throw IllegalArgumentException("Service not found")
val account = Account(service.accountName, context.getString(R.string.account_type))
httpClientBuilder.get().fromAccount(account).build().use { httpClient ->
runInterruptible(ioDispatcher) {
try {
DavResource(httpClient.okHttpClient, collection.url).delete {
// success, otherwise an exception would have been thrown → delete locally, too
delete(collection)
}
} catch (e: HttpException) {
if (e is NotFoundException || e is GoneException) {
// HTTP 404 Not Found or 410 Gone (collection is not there anymore) -> delete locally, too
logger.info("Collection ${collection.url} not found on server, deleting locally")
delete(collection)
} else
throw e
val httpClient = httpClientBuilder.get().fromAccount(account).build()
runInterruptible(ioDispatcher) {
try {
DavResource(httpClient.okHttpClient, collection.url).delete {
// success, otherwise an exception would have been thrown → delete locally, too
delete(collection)
}
} catch (e: HttpException) {
if (e is NotFoundException || e is GoneException) {
// HTTP 404 Not Found or 410 Gone (collection is not there anymore) -> delete locally, too
logger.info("Collection ${collection.url} not found on server, deleting locally")
delete(collection)
} else
throw e
}
}
}
@@ -290,19 +289,17 @@ class DavCollectionRepository @Inject constructor(
// helpers
private suspend fun createOnServer(account: Account, url: HttpUrl, method: String, xmlBody: String) {
httpClientBuilder.get()
val httpClient = httpClientBuilder.get()
.fromAccount(account)
.build()
.use { httpClient ->
runInterruptible(ioDispatcher) {
DavResource(httpClient.okHttpClient, url).mkCol(
xmlBody = xmlBody,
method = method
) {
// success, otherwise an exception would have been thrown
}
}
runInterruptible(ioDispatcher) {
DavResource(httpClient.okHttpClient, url).mkCol(
xmlBody = xmlBody,
method = method
) {
// success, otherwise an exception would have been thrown
}
}
}
private fun generateMkColXml(

View File

@@ -63,7 +63,7 @@ class DavResourceFinder @AssistedInject constructor(
@ApplicationContext val context: Context,
private val dnsRecordResolver: DnsRecordResolver,
httpClientBuilder: HttpClient.Builder
): AutoCloseable {
) {
@AssistedFactory
interface Factory {
@@ -93,10 +93,6 @@ class DavResourceFinder @AssistedInject constructor(
}
.build()
override fun close() {
httpClient.close()
}
private fun initLogging(): StringHandler {
// don't use more than 1/4 of the available memory for a log string
val activityManager = context.getSystemService<ActivityManager>()!!

View File

@@ -153,34 +153,32 @@ class RefreshCollectionsWorker @AssistedInject constructor(
.cancel(serviceId.toString(), NotificationRegistry.NOTIFY_REFRESH_COLLECTIONS)
// create authenticating OkHttpClient (credentials taken from account settings)
httpClientBuilder
val httpClient = httpClientBuilder
.fromAccount(account)
.build()
.use { httpClient ->
runInterruptible {
val httpClient = httpClient.okHttpClient
val refresher = collectionsWithoutHomeSetRefresherFactory.create(service, httpClient)
runInterruptible {
val httpClient = httpClient.okHttpClient
val refresher = collectionsWithoutHomeSetRefresherFactory.create(service, httpClient)
// refresh home set list (from principal url)
service.principal?.let { principalUrl ->
logger.fine("Querying principal $principalUrl for home sets")
val serviceRefresher = serviceRefresherFactory.create(service, httpClient)
serviceRefresher.discoverHomesets(principalUrl)
}
// refresh home sets and their member collections
homeSetRefresherFactory.create(service, httpClient)
.refreshHomesetsAndTheirCollections()
// also refresh collections without a home set
refresher.refreshCollectionsWithoutHomeSet()
// Lastly, refresh the principals (collection owners)
val principalsRefresher = principalsRefresherFactory.create(service, httpClient)
principalsRefresher.refreshPrincipals()
}
// refresh home set list (from principal url)
service.principal?.let { principalUrl ->
logger.fine("Querying principal $principalUrl for home sets")
val serviceRefresher = serviceRefresherFactory.create(service, httpClient)
serviceRefresher.discoverHomesets(principalUrl)
}
// refresh home sets and their member collections
homeSetRefresherFactory.create(service, httpClient)
.refreshHomesetsAndTheirCollections()
// also refresh collections without a home set
refresher.refreshCollectionsWithoutHomeSet()
// Lastly, refresh the principals (collection owners)
val principalsRefresher = principalsRefresherFactory.create(service, httpClient)
principalsRefresher.refreshPrincipals()
}
} catch(e: InvalidAccountException) {
logger.log(Level.SEVERE, "Invalid account", e)
return Result.failure()

View File

@@ -485,25 +485,23 @@ class ContactsSyncManager @AssistedInject constructor(
}
// authenticate only against a certain host, and only upon request
httpClientBuilder
val hostHttpClient = httpClientBuilder
.fromAccount(account, onlyHost = baseUrl.host)
.followRedirects(true) // allow redirects
.build()
.use { httpClient ->
try {
val response = httpClient.okHttpClient.newCall(Request.Builder()
.get()
.url(httpUrl)
.build()).execute()
try {
val response = hostHttpClient.okHttpClient.newCall(Request.Builder()
.get()
.url(httpUrl)
.build()).execute()
if (response.isSuccessful)
return response.body.bytes()
else
logger.warning("Couldn't download external resource")
} catch(e: IOException) {
logger.log(Level.SEVERE, "Couldn't download external resource", e)
}
}
if (response.isSuccessful)
return response.body.bytes()
else
logger.warning("Couldn't download external resource")
} catch(e: IOException) {
logger.log(Level.SEVERE, "Couldn't download external resource", e)
}
return null
}

View File

@@ -273,8 +273,6 @@ abstract class Syncer<StoreType: LocalDataStore<CollectionType>, CollectionType:
syncResult.numUnclassifiedErrors++ // Hard sync error
} finally {
if (httpClient.isInitialized())
httpClient.value.close()
logger.info("${dataStore.authority} sync of $account finished")
}
}

View File

@@ -195,9 +195,8 @@ class LoginScreenModel @AssistedInject constructor(
detectResourcesJob = viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
runInterruptible {
resourceFinderFactory.create(loginInfo.baseUri!!, loginInfo.credentials).use { finder ->
finder.findInitialConfiguration()
}
val finder = resourceFinderFactory.create(loginInfo.baseUri!!, loginInfo.credentials)
finder.findInitialConfiguration()
}
}

View File

@@ -102,10 +102,6 @@ class NextcloudLoginModel @AssistedInject constructor(
state[STATE_TOKEN] = value
}*/
override fun onCleared() {
loginFlow.close()
}
/**
* Starts the Login Flow.

View File

@@ -43,7 +43,7 @@ import javax.annotation.WillClose
@RequiresApi(26)
class RandomAccessCallback @AssistedInject constructor(
@Assisted @WillClose private val httpClient: HttpClient,
@Assisted private val httpClient: HttpClient,
@Assisted private val url: HttpUrl,
@Assisted private val mimeType: MediaType?,
@Assisted headResponse: HeadResponse,
@@ -127,7 +127,6 @@ class RandomAccessCallback @AssistedInject constructor(
// free resources
ioThread.quitSafely()
httpClient.close()
}

View File

@@ -27,10 +27,10 @@ import java.util.logging.Logger
import javax.annotation.WillClose
/**
* @param client HTTP client ([StreamingFileDescriptor] is responsible to close it)
* @param client HTTP client to use
*/
class StreamingFileDescriptor @AssistedInject constructor(
@Assisted @WillClose private val client: HttpClient,
@Assisted private val client: HttpClient,
@Assisted private val url: HttpUrl,
@Assisted private val mimeType: MediaType?,
@Assisted private val externalScope: CoroutineScope,
@@ -75,7 +75,6 @@ class StreamingFileDescriptor @AssistedInject constructor(
writeFd.close()
} catch (_: IOException) {}
client.close()
finishedCallback.onFinished(transferred, success)
}
}

View File

@@ -127,21 +127,19 @@ class WebDavMountRepository @Inject constructor(
val validVersions = arrayOf("1", "2", "3")
val builder = httpClientBuilder.get()
if (credentials != null)
builder.authenticate(
host = null,
getCredentials = { credentials }
)
val httpClient = builder.build()
var webdavUrl: HttpUrl? = null
builder.build().use { httpClient ->
val dav = DavResource(httpClient.okHttpClient, url)
runInterruptible {
dav.options(followRedirects = true) { davCapabilities, response ->
if (davCapabilities.any { it in validVersions })
webdavUrl = dav.location
}
val dav = DavResource(httpClient.okHttpClient, url)
runInterruptible {
dav.options(followRedirects = true) { davCapabilities, response ->
if (davCapabilities.any { it in validVersions })
webdavUrl = dav.location
}
}

View File

@@ -40,38 +40,37 @@ class CopyDocumentOperation @Inject constructor(
if (srcDoc.mountId != dstFolder.mountId)
throw UnsupportedOperationException("Can't COPY between WebDAV servers")
httpClientBuilder.build(srcDoc.mountId).use { client ->
val dav = DavResource(client.okHttpClient, srcDoc.toHttpUrl(db))
val dstUrl = dstFolder.toHttpUrl(db).newBuilder()
.addPathSegment(name)
.build()
val client = httpClientBuilder.build(srcDoc.mountId)
val dav = DavResource(client.okHttpClient, srcDoc.toHttpUrl(db))
val dstUrl = dstFolder.toHttpUrl(db).newBuilder()
.addPathSegment(name)
.build()
try {
runInterruptible(ioDispatcher) {
dav.copy(dstUrl, false) {
// successfully copied
}
try {
runInterruptible(ioDispatcher) {
dav.copy(dstUrl, false) {
// successfully copied
}
} catch (e: HttpException) {
e.throwForDocumentProvider(context)
}
val dstDocId = documentDao.insertOrReplace(
WebDavDocument(
mountId = dstFolder.mountId,
parentId = dstFolder.id,
name = name,
isDirectory = srcDoc.isDirectory,
displayName = srcDoc.displayName,
mimeType = srcDoc.mimeType,
size = srcDoc.size
)
).toString()
DocumentProviderUtils.notifyFolderChanged(context, targetParentDocumentId)
/* return */ dstDocId
} catch (e: HttpException) {
e.throwForDocumentProvider(context)
}
val dstDocId = documentDao.insertOrReplace(
WebDavDocument(
mountId = dstFolder.mountId,
parentId = dstFolder.id,
name = name,
isDirectory = srcDoc.isDirectory,
displayName = srcDoc.displayName,
mimeType = srcDoc.mimeType,
size = srcDoc.size
)
).toString()
DocumentProviderUtils.notifyFolderChanged(context, targetParentDocumentId)
/* return */ dstDocId
}
}

View File

@@ -41,45 +41,44 @@ class CreateDocumentOperation @Inject constructor(
val createDirectory = mimeType == Document.MIME_TYPE_DIR
var docId: Long?
httpClientBuilder.build(parent.mountId).use { client ->
for (attempt in 0..DocumentProviderUtils.MAX_DISPLAYNAME_TO_MEMBERNAME_ATTEMPTS) {
val newName = displayNameToMemberName(displayName, attempt)
val parentUrl = parent.toHttpUrl(db)
val newLocation = parentUrl.newBuilder()
.addPathSegment(newName)
.build()
val doc = DavResource(client.okHttpClient, newLocation)
try {
runInterruptible(ioDispatcher) {
if (createDirectory)
doc.mkCol(null) {
// directory successfully created
}
else
doc.put(RequestBody.EMPTY, ifNoneMatch = true) {
// document successfully created
}
}
docId = documentDao.insertOrReplace(
WebDavDocument(
mountId = parent.mountId,
parentId = parent.id,
name = newName,
isDirectory = createDirectory,
mimeType = mimeType.toMediaTypeOrNull(),
eTag = null,
lastModified = null,
size = if (createDirectory) null else 0
)
)
DocumentProviderUtils.notifyFolderChanged(context, parentDocumentId)
return@runBlocking docId.toString()
} catch (e: HttpException) {
e.throwForDocumentProvider(context, ignorePreconditionFailed = true)
val client = httpClientBuilder.build(parent.mountId)
for (attempt in 0..DocumentProviderUtils.MAX_DISPLAYNAME_TO_MEMBERNAME_ATTEMPTS) {
val newName = displayNameToMemberName(displayName, attempt)
val parentUrl = parent.toHttpUrl(db)
val newLocation = parentUrl.newBuilder()
.addPathSegment(newName)
.build()
val doc = DavResource(client.okHttpClient, newLocation)
try {
runInterruptible(ioDispatcher) {
if (createDirectory)
doc.mkCol(null) {
// directory successfully created
}
else
doc.put(RequestBody.EMPTY, ifNoneMatch = true) {
// document successfully created
}
}
docId = documentDao.insertOrReplace(
WebDavDocument(
mountId = parent.mountId,
parentId = parent.id,
name = newName,
isDirectory = createDirectory,
mimeType = mimeType.toMediaTypeOrNull(),
eTag = null,
lastModified = null,
size = if (createDirectory) null else 0
)
)
DocumentProviderUtils.notifyFolderChanged(context, parentDocumentId)
return@runBlocking docId.toString()
} catch (e: HttpException) {
e.throwForDocumentProvider(context, ignorePreconditionFailed = true)
}
}

View File

@@ -34,21 +34,20 @@ class DeleteDocumentOperation @Inject constructor(
logger.fine("WebDAV removeDocument $documentId")
val doc = documentDao.get(documentId.toLong()) ?: throw FileNotFoundException()
httpClientBuilder.build(doc.mountId).use { client ->
val dav = DavResource(client.okHttpClient, doc.toHttpUrl(db))
try {
runInterruptible(ioDispatcher) {
dav.delete {
// successfully deleted
}
val client = httpClientBuilder.build(doc.mountId)
val dav = DavResource(client.okHttpClient, doc.toHttpUrl(db))
try {
runInterruptible(ioDispatcher) {
dav.delete {
// successfully deleted
}
logger.fine("Successfully removed")
documentDao.delete(doc)
DocumentProviderUtils.notifyFolderChanged(context, doc.parentId)
} catch (e: HttpException) {
e.throwForDocumentProvider(context)
}
logger.fine("Successfully removed")
documentDao.delete(doc)
DocumentProviderUtils.notifyFolderChanged(context, doc.parentId)
} catch (e: HttpException) {
e.throwForDocumentProvider(context)
}
}

View File

@@ -42,22 +42,21 @@ class MoveDocumentOperation @Inject constructor(
.addPathSegment(doc.name)
.build()
httpClientBuilder.build(doc.mountId).use { client ->
val dav = DavResource(client.okHttpClient, doc.toHttpUrl(db))
try {
runInterruptible(ioDispatcher) {
dav.move(newLocation, false) {
// successfully moved
}
val client = httpClientBuilder.build(doc.mountId)
val dav = DavResource(client.okHttpClient, doc.toHttpUrl(db))
try {
runInterruptible(ioDispatcher) {
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()

View File

@@ -76,24 +76,23 @@ class OpenDocumentThumbnailOperation @Inject constructor(
// create thumbnail
val job = accessScope.async {
withTimeout(THUMBNAIL_TIMEOUT_MS) {
httpClientBuilder.build(doc.mountId, logBody = false).use { client ->
val url = doc.toHttpUrl(db)
val dav = DavResource(client.okHttpClient, url)
var result: ByteArray? = null
runInterruptible(ioDispatcher) {
dav.get("image/*", null) { response ->
response.body.byteStream().use { data ->
BitmapFactory.decodeStream(data)?.let { bitmap ->
val thumb = ThumbnailUtils.extractThumbnail(bitmap, sizeHint.x, sizeHint.y)
val baos = ByteArrayOutputStream()
thumb.compress(Bitmap.CompressFormat.JPEG, 95, baos)
result = baos.toByteArray()
}
val client = httpClientBuilder.build(doc.mountId, logBody = false)
val url = doc.toHttpUrl(db)
val dav = DavResource(client.okHttpClient, url)
var result: ByteArray? = null
runInterruptible(ioDispatcher) {
dav.get("image/*", null) { response ->
response.body.byteStream().use { data ->
BitmapFactory.decodeStream(data)?.let { bitmap ->
val thumb = ThumbnailUtils.extractThumbnail(bitmap, sizeHint.x, sizeHint.y)
val baos = ByteArrayOutputStream()
thumb.compress(Bitmap.CompressFormat.JPEG, 95, baos)
result = baos.toByteArray()
}
}
}
result
}
result
}
}

View File

@@ -128,58 +128,57 @@ class QueryChildDocumentsOperation @Inject constructor(
val newChildrenList = hashMapOf<String, WebDavDocument>()
val parentUrl = parent.toHttpUrl(db)
httpClientBuilder.build(parent.mountId).use { client ->
val folder = DavCollection(client.okHttpClient, parentUrl)
val client = httpClientBuilder.build(parent.mountId)
val folder = DavCollection(client.okHttpClient, parentUrl)
try {
runInterruptible(ioDispatcher) {
folder.propfind(1, *DAV_FILE_FIELDS) { response, relation ->
logger.fine("$relation $response")
try {
runInterruptible(ioDispatcher) {
folder.propfind(1, *DAV_FILE_FIELDS) { response, relation ->
logger.fine("$relation $response")
val resource: WebDavDocument =
when (relation) {
Response.HrefRelation.SELF -> // it's about the parent
parent
val resource: WebDavDocument =
when (relation) {
Response.HrefRelation.SELF -> // it's about the parent
parent
Response.HrefRelation.MEMBER -> // it's about a member
WebDavDocument(mountId = parent.mountId, parentId = parent.id, name = response.hrefName())
Response.HrefRelation.MEMBER -> // it's about a member
WebDavDocument(mountId = parent.mountId, parentId = parent.id, name = response.hrefName())
else -> {
// we didn't request this; log a warning and ignore it
logger.warning("Ignoring unexpected $response $relation in $parentUrl")
return@propfind
}
else -> {
// we didn't request this; log a warning and ignore it
logger.warning("Ignoring unexpected $response $relation in $parentUrl")
return@propfind
}
val updatedResource = resource.copy(
isDirectory = response[ResourceType::class.java]?.types?.contains(ResourceType.COLLECTION)
?: resource.isDirectory,
displayName = response[DisplayName::class.java]?.displayName,
mimeType = response[GetContentType::class.java]?.type,
eTag = response[GetETag::class.java]?.takeIf { !it.weak }?.eTag,
lastModified = response[GetLastModified::class.java]?.lastModified?.toEpochMilli(),
size = response[GetContentLength::class.java]?.contentLength,
mayBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind,
mayUnbind = response[CurrentUserPrivilegeSet::class.java]?.mayUnbind,
mayWriteContent = response[CurrentUserPrivilegeSet::class.java]?.mayWriteContent,
quotaAvailable = response[QuotaAvailableBytes::class.java]?.quotaAvailableBytes,
quotaUsed = response[QuotaUsedBytes::class.java]?.quotaUsedBytes,
)
if (resource == parent)
documentDao.update(updatedResource)
else {
documentDao.insertOrUpdate(updatedResource)
newChildrenList[resource.name] = updatedResource
}
// remove resource from known child nodes, because not found on server
oldChildren.remove(resource.name)
val updatedResource = resource.copy(
isDirectory = response[ResourceType::class.java]?.types?.contains(ResourceType.COLLECTION)
?: resource.isDirectory,
displayName = response[DisplayName::class.java]?.displayName,
mimeType = response[GetContentType::class.java]?.type,
eTag = response[GetETag::class.java]?.takeIf { !it.weak }?.eTag,
lastModified = response[GetLastModified::class.java]?.lastModified?.toEpochMilli(),
size = response[GetContentLength::class.java]?.contentLength,
mayBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind,
mayUnbind = response[CurrentUserPrivilegeSet::class.java]?.mayUnbind,
mayWriteContent = response[CurrentUserPrivilegeSet::class.java]?.mayWriteContent,
quotaAvailable = response[QuotaAvailableBytes::class.java]?.quotaAvailableBytes,
quotaUsed = response[QuotaUsedBytes::class.java]?.quotaUsedBytes,
)
if (resource == parent)
documentDao.update(updatedResource)
else {
documentDao.insertOrUpdate(updatedResource)
newChildrenList[resource.name] = updatedResource
}
// remove resource from known child nodes, because not found on server
oldChildren.remove(resource.name)
}
} catch (e: Exception) {
logger.log(Level.WARNING, "Couldn't query children", e)
}
} catch (e: Exception) {
logger.log(Level.WARNING, "Couldn't query children", e)
}
// Delete child nodes which were not rediscovered (deleted serverside)

View File

@@ -35,29 +35,28 @@ class RenameDocumentOperation @Inject constructor(
logger.fine("WebDAV renameDocument $documentId $displayName")
val doc = documentDao.get(documentId.toLong()) ?: throw FileNotFoundException()
httpClientBuilder.build(doc.mountId).use { client ->
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.okHttpClient, oldUrl)
runInterruptible(ioDispatcher) {
dav.move(newLocation, false) {
// successfully renamed
}
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.okHttpClient, oldUrl)
runInterruptible(ioDispatcher) {
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)
}
}