Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 2c7b36ec authored by Ricki Hirner's avatar Ricki Hirner Committed by GitHub
Browse files

Use Ktor for Push registration (#1930)

* Replace OkHttp with Ktor for push notifications

* Use Ktor HttpHeaders for Location and Expires
parent cf80b118
Loading
Loading
Loading
Loading
+59 −58
Original line number Diff line number Diff line
@@ -12,11 +12,13 @@ import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import at.bitfire.dav4jvm.HttpUtils
import at.bitfire.dav4jvm.HttpUtils.toKtorUrl
import at.bitfire.dav4jvm.XmlUtils
import at.bitfire.dav4jvm.XmlUtils.insertTag
import at.bitfire.dav4jvm.okhttp.DavCollection
import at.bitfire.dav4jvm.okhttp.DavResource
import at.bitfire.dav4jvm.okhttp.exception.DavException
import at.bitfire.dav4jvm.ktor.DavCollection
import at.bitfire.dav4jvm.ktor.DavResource
import at.bitfire.dav4jvm.ktor.exception.DavException
import at.bitfire.dav4jvm.ktor.toUrlOrNull
import at.bitfire.dav4jvm.property.push.WebDAVPush
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.Service
@@ -29,15 +31,14 @@ import at.bitfire.davdroid.repository.DavServiceRepository
import at.bitfire.davdroid.sync.account.InvalidAccountException
import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
import io.ktor.client.HttpClient
import io.ktor.http.HttpHeaders
import io.ktor.http.Url
import io.ktor.http.isSuccess
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import org.unifiedpush.android.connector.UnifiedPush
import org.unifiedpush.android.connector.data.PushEndpoint
import java.io.StringWriter
@@ -176,9 +177,10 @@ class PushRegistrationManager @Inject constructor(
            return

        val account = accountRepository.get().fromName(service.accountName)
        val httpClient = httpClientBuilder.get()
        httpClientBuilder.get()
            .fromAccountAsync(account)
            .build()
            .buildKtor()
            .use { httpClient ->
            for (collection in subscribeTo)
                try {
                    val expires = collection.pushSubscriptionExpires
@@ -195,6 +197,7 @@ class PushRegistrationManager @Inject constructor(
                    logger.log(Level.WARNING, "Couldn't register subscription at CalDAV/CardDAV server", e)
                }
        }
    }

    /**
     * Called when no subscription is available (anymore) for the given service.
@@ -224,7 +227,7 @@ class PushRegistrationManager @Inject constructor(
     * @param collection    collection to subscribe to
     * @param endpoint      subscription to register
     */
    private suspend fun subscribe(httpClient: OkHttpClient, collection: Collection, endpoint: PushEndpoint) {
    private suspend fun subscribe(httpClient: HttpClient, collection: Collection, endpoint: PushEndpoint) {
        // requested expiration time: 3 days
        val requestedExpiration = Instant.now() + Duration.ofDays(3)

@@ -257,28 +260,26 @@ class PushRegistrationManager @Inject constructor(
        }
        serializer.endDocument()

        runInterruptible(ioDispatcher) {
            val xml = writer.toString().toRequestBody(DavResource.MIME_XML)
            DavCollection(httpClient, collection.url).post(xml) { response ->
                if (response.isSuccessful) {
        DavCollection(httpClient, collection.url.toKtorUrl()).post(
            { ByteReadChannel(writer.toString()) },
            DavResource.MIME_XML_UTF8
        ) { response ->
            if (response.status.isSuccess()) {
                // update subscription URL and expiration in DB
                    val subscriptionUrl = response.header("Location")
                    val expires = response.header("Expires")?.let { expiresDate ->
                val subscriptionUrl = response.headers[HttpHeaders.Location]
                val expires = response.headers[HttpHeaders.Expires]?.let { expiresDate ->
                    HttpUtils.parseDate(expiresDate)
                } ?: requestedExpiration

                    runBlocking {
                collectionRepository.updatePushSubscription(
                    id = collection.id,
                    subscriptionUrl = subscriptionUrl,
                    expires = expires?.epochSecond
                )
                    }
            } else
                logger.warning("Couldn't register push for ${collection.url}: $response")
        }
    }
    }

    /**
     * Unsubscribe from the given collections.
@@ -288,23 +289,23 @@ class PushRegistrationManager @Inject constructor(
            return

        val account = accountRepository.get().fromName(service.accountName)
        val httpClient = httpClientBuilder.get()
        httpClientBuilder.get()
            .fromAccountAsync(account)
            .build()
            .buildKtor()
            .use { httpClient ->
            for (collection in from)
            collection.pushSubscription?.toHttpUrlOrNull()?.let { url ->
                collection.pushSubscription?.toUrlOrNull()?.let { url ->
                    logger.info("Unsubscribing Push from ${collection.url}")
                    unsubscribe(httpClient, collection, url)
                }
        }
    }

    private suspend fun unsubscribe(httpClient: OkHttpClient, collection: Collection, url: HttpUrl) {
    private suspend fun unsubscribe(httpClient: HttpClient, collection: Collection, url: Url) {
        try {
            runInterruptible(ioDispatcher) {
            DavResource(httpClient, url).delete {
                // deleted
            }
            }
        } catch (e: DavException) {
            logger.log(Level.WARNING, "Couldn't unregister push for ${collection.url}", e)
        }