Make individual autoconfig steps suspendable and cancellable
This commit is contained in:
parent
2d1d54cd6d
commit
67d082f01f
5 changed files with 87 additions and 40 deletions
|
@ -8,14 +8,12 @@ import app.k9mail.core.common.net.toDomain
|
|||
import com.fsck.k9.helper.EmailHelper
|
||||
import com.fsck.k9.logging.Timber
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
class AutoconfigDiscovery(
|
||||
private val urlProvider: AutoconfigUrlProvider,
|
||||
private val fetcher: AutoconfigFetcher,
|
||||
private val parser: AutoconfigParser,
|
||||
private val parser: SuspendableAutoconfigParser,
|
||||
) : AutoDiscovery {
|
||||
|
||||
override fun initDiscovery(email: EmailAddress): List<AutoDiscoveryRunnable> {
|
||||
|
@ -27,18 +25,12 @@ class AutoconfigDiscovery(
|
|||
|
||||
return autoconfigUrls.map { autoconfigUrl ->
|
||||
AutoDiscoveryRunnable {
|
||||
getConfigInBackground(email, autoconfigUrl)
|
||||
getAutoconfig(email, autoconfigUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getConfigInBackground(email: EmailAddress, autoconfigUrl: HttpUrl): AutoDiscoveryResult? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
getAutoconfig(email, autoconfigUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAutoconfig(email: EmailAddress, autoconfigUrl: HttpUrl): AutoDiscoveryResult? {
|
||||
private suspend fun getAutoconfig(email: EmailAddress, autoconfigUrl: HttpUrl): AutoDiscoveryResult? {
|
||||
return try {
|
||||
fetcher.fetchAutoconfigFile(autoconfigUrl)?.use { inputStream ->
|
||||
parser.parseSettings(inputStream, email)
|
||||
|
|
|
@ -3,26 +3,56 @@ package app.k9mail.autodiscovery.autoconfig
|
|||
import com.fsck.k9.logging.Timber
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
||||
class AutoconfigFetcher(private val okHttpClient: OkHttpClient) {
|
||||
|
||||
fun fetchAutoconfigFile(url: HttpUrl): InputStream? {
|
||||
suspend fun fetchAutoconfigFile(url: HttpUrl): InputStream? {
|
||||
return try {
|
||||
val request = Request.Builder().url(url).build()
|
||||
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body?.byteStream()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
performHttpRequest(url)
|
||||
} catch (e: IOException) {
|
||||
Timber.d(e, "Error fetching URL: %s", url)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun performHttpRequest(url: HttpUrl): InputStream? {
|
||||
return suspendCancellableCoroutine { cancellableContinuation ->
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.build()
|
||||
|
||||
val call = okHttpClient.newCall(request)
|
||||
|
||||
cancellableContinuation.invokeOnCancellation {
|
||||
call.cancel()
|
||||
}
|
||||
|
||||
val responseCallback = object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
cancellableContinuation.cancel(e)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (response.isSuccessful) {
|
||||
val inputStream = response.body?.byteStream()
|
||||
cancellableContinuation.resume(inputStream)
|
||||
} else {
|
||||
// We don't care about the body of error responses.
|
||||
response.close()
|
||||
cancellableContinuation.resume(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
call.enqueue(responseCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,35 +9,27 @@ import app.k9mail.core.common.net.toDomain
|
|||
import com.fsck.k9.helper.EmailHelper
|
||||
import com.fsck.k9.logging.Timber
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
class MxLookupAutoconfigDiscovery(
|
||||
private val mxResolver: MxResolver,
|
||||
private val mxResolver: SuspendableMxResolver,
|
||||
private val baseDomainExtractor: BaseDomainExtractor,
|
||||
private val subDomainExtractor: SubDomainExtractor,
|
||||
private val urlProvider: AutoconfigUrlProvider,
|
||||
private val fetcher: AutoconfigFetcher,
|
||||
private val parser: AutoconfigParser,
|
||||
private val parser: SuspendableAutoconfigParser,
|
||||
) : AutoDiscovery {
|
||||
|
||||
override fun initDiscovery(email: EmailAddress): List<AutoDiscoveryRunnable> {
|
||||
return listOf(
|
||||
AutoDiscoveryRunnable {
|
||||
mxLookupInBackground(email)
|
||||
mxLookupAutoconfig(email)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun mxLookupInBackground(email: EmailAddress): AutoDiscoveryResult? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
mxLookupAutoconfig(email)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount")
|
||||
private fun mxLookupAutoconfig(email: EmailAddress): AutoDiscoveryResult? {
|
||||
private suspend fun mxLookupAutoconfig(email: EmailAddress): AutoDiscoveryResult? {
|
||||
val domain = requireNotNull(EmailHelper.getDomainFromEmailAddress(email.address)?.toDomain()) {
|
||||
"Couldn't extract domain from email address: ${email.address}"
|
||||
}
|
||||
|
@ -54,14 +46,19 @@ class MxLookupAutoconfigDiscovery(
|
|||
// between Outlook.com/Hotmail and Office365 business domains.
|
||||
val mxSubDomain = getNextSubDomain(mxHostName)?.takeIf { it != mxBaseDomain }
|
||||
|
||||
return listOfNotNull(mxSubDomain, mxBaseDomain)
|
||||
.asSequence()
|
||||
.flatMap { domainToCheck -> urlProvider.getAutoconfigUrls(domainToCheck) }
|
||||
.mapNotNull { autoconfigUrl -> getAutoconfig(email, autoconfigUrl) }
|
||||
.firstOrNull()
|
||||
for (domainToCheck in listOfNotNull(mxSubDomain, mxBaseDomain)) {
|
||||
for (autoconfigUrl in urlProvider.getAutoconfigUrls(domainToCheck)) {
|
||||
val discoveryResult = getAutoconfig(email, autoconfigUrl)
|
||||
if (discoveryResult != null) {
|
||||
return discoveryResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun mxLookup(domain: Domain): Domain? {
|
||||
private suspend fun mxLookup(domain: Domain): Domain? {
|
||||
// Only return the most preferred entry to match Thunderbird's behavior.
|
||||
return try {
|
||||
mxResolver.lookup(domain).firstOrNull()
|
||||
|
@ -79,7 +76,7 @@ class MxLookupAutoconfigDiscovery(
|
|||
return subDomainExtractor.extractSubDomain(domain)
|
||||
}
|
||||
|
||||
private fun getAutoconfig(email: EmailAddress, autoconfigUrl: HttpUrl): AutoDiscoveryResult? {
|
||||
private suspend fun getAutoconfig(email: EmailAddress, autoconfigUrl: HttpUrl): AutoDiscoveryResult? {
|
||||
return try {
|
||||
fetcher.fetchAutoconfigFile(autoconfigUrl)?.use { inputStream ->
|
||||
parser.parseSettings(inputStream, email)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package app.k9mail.autodiscovery.autoconfig
|
||||
|
||||
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import java.io.InputStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
|
||||
class SuspendableAutoconfigParser(private val autoconfigParser: AutoconfigParser) {
|
||||
suspend fun parseSettings(inputStream: InputStream, email: EmailAddress): AutoDiscoveryResult? {
|
||||
return runInterruptible(Dispatchers.IO) {
|
||||
autoconfigParser.parseSettings(inputStream, email)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package app.k9mail.autodiscovery.autoconfig
|
||||
|
||||
import app.k9mail.core.common.net.Domain
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
|
||||
class SuspendableMxResolver(private val mxResolver: MxResolver) {
|
||||
suspend fun lookup(domain: Domain): List<Domain> {
|
||||
return runInterruptible(Dispatchers.IO) {
|
||||
mxResolver.lookup(domain)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue