Added errors to result of listAllMessagesWithEInvoice()
This commit is contained in:
parent
6aaecd1656
commit
0c1d48736c
|
@ -34,16 +34,19 @@ open class MailReader(
|
||||||
protected val log by logger()
|
protected val log by logger()
|
||||||
|
|
||||||
|
|
||||||
open fun listenForNewReceivedEInvoices(account: MailAccount, downloadMessageBody: Boolean = false, emailFolderName: String = "INBOX", eInvoiceReceived: (MailWithInvoice) -> Unit) = runBlocking {
|
open fun listenForNewReceivedEInvoices(account: MailAccount, downloadMessageBody: Boolean = false, emailFolderName: String = "INBOX",
|
||||||
|
error: ((ReadMailError) -> Unit)? = null, eInvoiceReceived: (MailWithInvoice) -> Unit) = runBlocking {
|
||||||
try {
|
try {
|
||||||
connect(account) { store ->
|
connect(account) { store ->
|
||||||
val folder = store.getFolder(emailFolderName)
|
val folder = store.getFolder(emailFolderName)
|
||||||
folder.open(Folder.READ_ONLY)
|
folder.open(Folder.READ_ONLY)
|
||||||
|
|
||||||
|
val status = ReadMailsStatus(ReadMailsOptions(downloadMessageBody))
|
||||||
|
|
||||||
folder.addMessageCountListener(object : MessageCountAdapter() {
|
folder.addMessageCountListener(object : MessageCountAdapter() {
|
||||||
override fun messagesAdded(event: MessageCountEvent) {
|
override fun messagesAdded(event: MessageCountEvent) {
|
||||||
event.messages.forEach { message ->
|
event.messages.forEach { message ->
|
||||||
findEInvoice(message, downloadMessageBody)?.let {
|
findEInvoice(message, status)?.let {
|
||||||
eInvoiceReceived(it)
|
eInvoiceReceived(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +59,7 @@ open class MailReader(
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Listening to new received eInvoices of '${account.username}' failed" }
|
log.error(e) { "Listening to new received eInvoices of '${account.username}' failed" }
|
||||||
|
error?.invoke(ReadMailError(ReadMailsErrorType.ListenForNewEmails, null, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info { "Stopped listening to new received eInvoices of '${account.username}'" }
|
log.info { "Stopped listening to new received eInvoices of '${account.username}'" }
|
||||||
|
@ -78,24 +82,28 @@ open class MailReader(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun listAllMessagesWithEInvoice(account: MailAccount, downloadMessageBody: Boolean = false, emailFolderName: String = "INBOX"): List<MailWithInvoice> {
|
open fun listAllMessagesWithEInvoice(account: MailAccount, downloadMessageBody: Boolean = false, emailFolderName: String = "INBOX"): ReadMailsResult {
|
||||||
try {
|
try {
|
||||||
return connect(account) { store ->
|
return connect(account) { store ->
|
||||||
val inbox = store.getFolder(emailFolderName)
|
val inbox = store.getFolder(emailFolderName)
|
||||||
inbox.open(Folder.READ_ONLY)
|
inbox.open(Folder.READ_ONLY)
|
||||||
|
|
||||||
listAllMessagesWithEInvoiceInFolder(inbox, downloadMessageBody).also {
|
val status = ReadMailsStatus(ReadMailsOptions(downloadMessageBody))
|
||||||
|
|
||||||
|
val mails = listAllMessagesWithEInvoiceInFolder(inbox, status).also {
|
||||||
inbox.close(false)
|
inbox.close(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReadMailsResult(mails, null, status.mailSpecificErrors)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not read mails of account $account" }
|
log.error(e) { "Could not read mails of account $account" }
|
||||||
}
|
|
||||||
|
|
||||||
return emptyList()
|
return ReadMailsResult(emptyList(), e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun listAllMessagesWithEInvoiceInFolder(folder: Folder, downloadMessageBody: Boolean): List<MailWithInvoice> = runBlocking {
|
protected open fun listAllMessagesWithEInvoiceInFolder(folder: Folder, status: ReadMailsStatus): List<MailWithInvoice> = runBlocking {
|
||||||
val messageCount = folder.messageCount
|
val messageCount = folder.messageCount
|
||||||
if (messageCount <= 0) {
|
if (messageCount <= 0) {
|
||||||
return@runBlocking emptyList()
|
return@runBlocking emptyList()
|
||||||
|
@ -104,9 +112,10 @@ open class MailReader(
|
||||||
IntRange(1, messageCount).mapNotNull { messageNumber -> // message numbers start at 1
|
IntRange(1, messageCount).mapNotNull { messageNumber -> // message numbers start at 1
|
||||||
async(mailDispatcher) {
|
async(mailDispatcher) {
|
||||||
try {
|
try {
|
||||||
findEInvoice(folder.getMessage(messageNumber), downloadMessageBody)
|
findEInvoice(folder.getMessage(messageNumber), status)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not get message with messageNumber $messageNumber" }
|
log.error(e) { "Could not get message with messageNumber $messageNumber" }
|
||||||
|
status.addError(ReadMailsErrorType.GetEmail, messageNumber, e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,34 +124,30 @@ open class MailReader(
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun findEInvoice(message: Message, downloadMessageBody: Boolean): MailWithInvoice? {
|
protected open fun findEInvoice(message: Message, status: ReadMailsStatus): MailWithInvoice? {
|
||||||
try {
|
val parts = getAllMessageParts(message)
|
||||||
val parts = getAllMessageParts(message)
|
|
||||||
|
|
||||||
val attachmentsWithEInvoice = parts.mapNotNull { part ->
|
val attachmentsWithEInvoice = parts.mapNotNull { part ->
|
||||||
findEInvoice(part)
|
findEInvoice(part, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachmentsWithEInvoice.isNotEmpty()) {
|
if (attachmentsWithEInvoice.isNotEmpty()) {
|
||||||
return MailWithInvoice(
|
return MailWithInvoice(
|
||||||
message.from?.joinToString(), message.subject ?: "",
|
message.from?.joinToString(), message.subject ?: "",
|
||||||
message.sentDate?.let { map(it) }, map(message.receivedDate), message.messageNumber,
|
message.sentDate?.let { map(it) }, map(message.receivedDate), message.messageNumber,
|
||||||
parts.any { it.mediaType == "application/pgp-encrypted" },
|
parts.any { it.mediaType == "application/pgp-encrypted" },
|
||||||
if (downloadMessageBody) getPlainTextBody(parts) else null, if (downloadMessageBody) getHtmlBody(parts) else null,
|
getPlainTextBody(parts, status), getHtmlBody(parts, status),
|
||||||
attachmentsWithEInvoice
|
attachmentsWithEInvoice
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
log.error(e) { "Could not read message $message" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun findEInvoice(messagePart: MessagePart): MailAttachmentWithEInvoice? {
|
protected open fun findEInvoice(messagePart: MessagePart, status: ReadMailsStatus): MailAttachmentWithEInvoice? {
|
||||||
try {
|
try {
|
||||||
val part = messagePart.part
|
val part = messagePart.part
|
||||||
val invoice = tryToReadEInvoice(part, messagePart.mediaType)
|
val invoice = tryToReadEInvoice(part, messagePart.mediaType, status)
|
||||||
|
|
||||||
if (invoice != null) {
|
if (invoice != null) {
|
||||||
val filename = File(part.fileName)
|
val filename = File(part.fileName)
|
||||||
|
@ -155,12 +160,13 @@ open class MailReader(
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not check attachment '${messagePart.part.fileName}' (${messagePart.mediaType}) for eInvoice" }
|
log.error(e) { "Could not check attachment '${messagePart.part.fileName}' (${messagePart.mediaType}) for eInvoice" }
|
||||||
|
status.addError(ReadMailsErrorType.GetAttachment, messagePart.part, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun tryToReadEInvoice(part: Part, mediaType: String?): Invoice? = try {
|
protected open fun tryToReadEInvoice(part: Part, mediaType: String?, status: ReadMailsStatus): Invoice? = try {
|
||||||
val filename = part.fileName?.lowercase() ?: ""
|
val filename = part.fileName?.lowercase() ?: ""
|
||||||
|
|
||||||
if (filename.endsWith(".pdf") || mediaType == "application/pdf" || mediaType == "application/octet-stream") {
|
if (filename.endsWith(".pdf") || mediaType == "application/pdf" || mediaType == "application/octet-stream") {
|
||||||
|
@ -172,6 +178,7 @@ open class MailReader(
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.debug(e) { "Could not extract invoices from ${part.fileName}" }
|
log.debug(e) { "Could not extract invoices from ${part.fileName}" }
|
||||||
|
status.addError(ReadMailsErrorType.ExtractInvoice, part, e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,11 +219,13 @@ open class MailReader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getPlainTextBody(parts: Collection<MessagePart>) = getBodyWithMediaType(parts, "text/plain")
|
protected open fun getPlainTextBody(parts: Collection<MessagePart>, status: ReadMailsStatus) =
|
||||||
|
if (status.options.downloadMessageBody) getBodyWithMediaType(parts, "text/plain", status) else null
|
||||||
|
|
||||||
protected open fun getHtmlBody(parts: Collection<MessagePart>) = getBodyWithMediaType(parts, "text/html")
|
protected open fun getHtmlBody(parts: Collection<MessagePart>, status: ReadMailsStatus) =
|
||||||
|
if (status.options.downloadMessageBody) getBodyWithMediaType(parts, "text/html", status) else null
|
||||||
|
|
||||||
protected open fun getBodyWithMediaType(parts: Collection<MessagePart>, mediaType: String): String? = try {
|
protected open fun getBodyWithMediaType(parts: Collection<MessagePart>, mediaType: String, status: ReadMailsStatus): String? = try {
|
||||||
val partsForMediaType = parts.filter { it.mediaType == mediaType }
|
val partsForMediaType = parts.filter { it.mediaType == mediaType }
|
||||||
|
|
||||||
if (partsForMediaType.size == 1) {
|
if (partsForMediaType.size == 1) {
|
||||||
|
@ -236,6 +245,7 @@ open class MailReader(
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not get message body for media type '$mediaType'" }
|
log.error(e) { "Could not get message body for media type '$mediaType'" }
|
||||||
|
status.addError(ReadMailsErrorType.GetMesssageBody, parts.map { it.part }, e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package net.codinux.invoicing.mail
|
||||||
|
|
||||||
|
data class ReadMailError(
|
||||||
|
val type: ReadMailsErrorType,
|
||||||
|
val messageNumber: Int?,
|
||||||
|
val error: Throwable
|
||||||
|
)
|
|
@ -0,0 +1,13 @@
|
||||||
|
package net.codinux.invoicing.mail
|
||||||
|
|
||||||
|
enum class ReadMailsErrorType {
|
||||||
|
GetEmail,
|
||||||
|
|
||||||
|
GetMesssageBody,
|
||||||
|
|
||||||
|
GetAttachment,
|
||||||
|
|
||||||
|
ExtractInvoice,
|
||||||
|
|
||||||
|
ListenForNewEmails
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package net.codinux.invoicing.mail
|
||||||
|
|
||||||
|
data class ReadMailsOptions(
|
||||||
|
val downloadMessageBody: Boolean = false
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
package net.codinux.invoicing.mail
|
||||||
|
|
||||||
|
data class ReadMailsResult(
|
||||||
|
val emails: List<MailWithInvoice>,
|
||||||
|
val overallError: Throwable?,
|
||||||
|
val messageSpecificErrors: List<ReadMailError> = emptyList()
|
||||||
|
)
|
|
@ -0,0 +1,39 @@
|
||||||
|
package net.codinux.invoicing.mail
|
||||||
|
|
||||||
|
import jakarta.mail.BodyPart
|
||||||
|
import jakarta.mail.Message
|
||||||
|
import jakarta.mail.Part
|
||||||
|
|
||||||
|
data class ReadMailsStatus(
|
||||||
|
val options: ReadMailsOptions,
|
||||||
|
val mailSpecificErrors: MutableList<ReadMailError> = mutableListOf(),
|
||||||
|
val error: ((ReadMailError) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun addError(type: ReadMailsErrorType, parts: Collection<Part>, error: Throwable) =
|
||||||
|
addError(ReadMailError(type, parts.firstNotNullOfOrNull { getMessage(it) }?.messageNumber, error))
|
||||||
|
|
||||||
|
fun addError(type: ReadMailsErrorType, part: Part, error: Throwable) =
|
||||||
|
addError(ReadMailError(type, getMessage(part)?.messageNumber, error))
|
||||||
|
|
||||||
|
fun addError(type: ReadMailsErrorType, messageNumber: Int?, error: Throwable) =
|
||||||
|
addError(ReadMailError(type, messageNumber, error))
|
||||||
|
|
||||||
|
fun addError(mailError: ReadMailError) {
|
||||||
|
mailSpecificErrors.add(mailError)
|
||||||
|
|
||||||
|
error?.invoke(mailError)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMessage(part: Part): Message? {
|
||||||
|
if (part is Message) {
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
|
||||||
|
(part as? BodyPart)?.parent.let { parent ->
|
||||||
|
return getMessage(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,10 +27,10 @@ class MailReaderTest {
|
||||||
fun listAllMessagesWithEInvoice() {
|
fun listAllMessagesWithEInvoice() {
|
||||||
val result = underTest.listAllMessagesWithEInvoice(mailAccount, true)
|
val result = underTest.listAllMessagesWithEInvoice(mailAccount, true)
|
||||||
|
|
||||||
assertThat(result).isNotEmpty()
|
assertThat(result.emails).isNotEmpty()
|
||||||
|
|
||||||
val messagesWithoutBody = result.filter { it.plainTextOrHtmlBody == null }
|
val emailsWithoutBody = result.emails.filter { it.plainTextOrHtmlBody == null }
|
||||||
assertThat(messagesWithoutBody).isEmpty()
|
assertThat(emailsWithoutBody).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue