JMAP: Add support for uploading messages

This commit is contained in:
cketti 2020-02-21 01:51:10 +01:00
parent c5cf5da746
commit 2a8d094343
6 changed files with 118 additions and 4 deletions

View file

@ -23,7 +23,7 @@ dependencies {
implementation "androidx.fragment:fragment:${versions.androidxFragment}"
implementation "androidx.localbroadcastmanager:localbroadcastmanager:${versions.androidxLocalBroadcastManager}"
implementation "org.jsoup:jsoup:1.11.2"
implementation "com.squareup.moshi:moshi:1.9.2"
implementation "com.squareup.moshi:moshi:${versions.moshi}"
implementation "com.jakewharton.timber:timber:${versions.timber}"
implementation "org.apache.james:apache-mime4j-core:${versions.mime4j}"

View file

@ -1,6 +1,7 @@
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'org.jlleitschuh.gradle.ktlint'
apply plugin: 'kotlin-kapt'
if (rootProject.testCoverage) {
apply plugin: 'jacoco'
@ -12,8 +13,10 @@ dependencies {
api project(":backend:api")
api "com.squareup.okhttp3:okhttp:${versions.okhttp}"
implementation "rs.ltt.jmap:jmap-client:0.3.0"
implementation "rs.ltt.jmap:jmap-client:0.3.1"
implementation "com.jakewharton.timber:timber:${versions.timber}"
implementation "com.squareup.moshi:moshi:${versions.moshi}"
kapt "com.squareup.moshi:moshi-kotlin-codegen:${versions.moshi}"
testImplementation project(":mail:testing")
testImplementation "org.mockito:mockito-core:${versions.mockito}"

View file

@ -0,0 +1,98 @@
package com.fsck.k9.backend.jmap
import com.fsck.k9.mail.Message
import com.fsck.k9.mail.MessagingException
import com.squareup.moshi.Moshi
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okio.BufferedSink
import rs.ltt.jmap.client.JmapClient
import rs.ltt.jmap.client.http.HttpAuthentication
import rs.ltt.jmap.common.entity.EmailImport
import rs.ltt.jmap.common.method.call.email.ImportEmailMethodCall
import rs.ltt.jmap.common.method.response.email.ImportEmailMethodResponse
import timber.log.Timber
class CommandUpload(
private val jmapClient: JmapClient,
private val okHttpClient: OkHttpClient,
private val httpAuthentication: HttpAuthentication,
private val accountId: String
) {
private val moshi = Moshi.Builder().build()
fun uploadMessage(folderServerId: String, message: Message): String? {
Timber.d("Uploading message to $folderServerId")
val uploadResponse = uploadMessageAsBlob(message)
return importEmailBlob(uploadResponse, folderServerId)
}
private fun uploadMessageAsBlob(message: Message): JmapUploadResponse {
val session = jmapClient.session.get()
val uploadUrl = session.getUploadUrl(accountId)
val request = Request.Builder()
.url(uploadUrl)
.post(MessageRequestBody(message))
.apply {
httpAuthentication.authenticate(this)
}
.build()
return okHttpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw MessagingException("Uploading message as blob failed")
}
response.body!!.source().use { source ->
val adapter = moshi.adapter(JmapUploadResponse::class.java)
val uploadResponse = adapter.fromJson(source)
uploadResponse ?: throw MessagingException("Error reading upload response")
}
}
}
private fun importEmailBlob(uploadResponse: JmapUploadResponse, folderServerId: String): String? {
val importEmailRequest = ImportEmailMethodCall.builder()
.accountId(accountId)
.email(
LOCAL_EMAIL_ID,
EmailImport.builder()
.blobId(uploadResponse.blobId)
.keywords(mapOf("\$seen" to true))
.mailboxIds(mapOf(folderServerId to true))
.build()
)
.build()
val importEmailCall = jmapClient.call(importEmailRequest)
val importEmailResponse = importEmailCall.getMainResponseBlocking<ImportEmailMethodResponse>()
return importEmailResponse.serverEmailId
}
private val ImportEmailMethodResponse.serverEmailId
get() = created?.get(LOCAL_EMAIL_ID)?.id
companion object {
private const val LOCAL_EMAIL_ID = "t1"
}
}
private class MessageRequestBody(private val message: Message) : RequestBody() {
override fun contentType(): MediaType? {
return "message/rfc822".toMediaType()
}
override fun contentLength(): Long {
return message.calculateSize()
}
override fun writeTo(sink: BufferedSink) {
message.writeTo(sink.outputStream())
}
}

View file

@ -31,6 +31,7 @@ class JmapBackend(
private val commandSetFlag = CommandSetFlag(jmapClient, accountId)
private val commandDelete = CommandDelete(jmapClient, accountId)
private val commandMove = CommandMove(jmapClient, accountId)
private val commandUpload = CommandUpload(jmapClient, okHttpClient, httpAuthentication, accountId)
override val supportsSeenFlag = true
override val supportsExpunge = false
override val supportsMove = true
@ -113,11 +114,11 @@ class JmapBackend(
}
override fun findByMessageId(folderServerId: String, messageId: String): String? {
throw UnsupportedOperationException("not implemented")
return null
}
override fun uploadMessage(folderServerId: String, message: Message): String? {
throw UnsupportedOperationException("not implemented")
return commandUpload.uploadMessage(folderServerId, message)
}
override fun createPusher(receiver: PushReceiver): Pusher {

View file

@ -0,0 +1,11 @@
package com.fsck.k9.backend.jmap
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class JmapUploadResponse(
val accountId: String,
val blobId: String,
val type: String,
val size: Long
)

View file

@ -23,6 +23,7 @@ buildscript {
'materialComponents': '1.1.0',
'preferencesFix': '1.1.0',
'okio': '2.4.3',
'moshi': '1.9.2',
'timber': '4.5.1',
'koin': '2.0.1',
'commonsIo': '2.6',