Returning now all mails and attachments, not only those with eInvoices

This commit is contained in:
dankito 2024-11-26 03:47:55 +01:00
parent c3cf0652b2
commit d70a748ad0
8 changed files with 34 additions and 38 deletions

View File

@ -32,7 +32,7 @@ val fetchResult = emailsFetcher.fetchAllEmails(EmailAccount(
)) ))
fetchResult.emails.forEach { email -> fetchResult.emails.forEach { email ->
println("${email.sender}: ${email.attachmentsWithEInvoice.firstNotNullOfOrNull { it.invoice }?.totalAmounts?.duePayableAmount}") println("${email.sender}: ${email.attachments.firstNotNullOfOrNull { it.invoice }?.totalAmounts?.duePayableAmount}")
} }
``` ```

View File

@ -10,8 +10,8 @@ import jakarta.mail.event.MessageCountAdapter
import jakarta.mail.event.MessageCountEvent import jakarta.mail.event.MessageCountEvent
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.codinux.invoicing.email.model.EmailAccount import net.codinux.invoicing.email.model.EmailAccount
import net.codinux.invoicing.email.model.EmailAttachmentWithEInvoice import net.codinux.invoicing.email.model.EmailAttachment
import net.codinux.invoicing.email.model.EmailWithInvoice import net.codinux.invoicing.email.model.Email
import net.codinux.invoicing.model.Invoice import net.codinux.invoicing.model.Invoice
import net.codinux.invoicing.reader.EInvoiceReader import net.codinux.invoicing.reader.EInvoiceReader
import net.codinux.log.logger import net.codinux.log.logger
@ -47,7 +47,7 @@ open class EmailsFetcher(
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, status) getEmail(message, status)
} }
} }
}) })
@ -102,7 +102,7 @@ open class EmailsFetcher(
} }
} }
protected open fun fetchAllEmailsInFolder(folder: Folder, status: FetchEmailsStatus): List<EmailWithInvoice> = runBlocking { protected open fun fetchAllEmailsInFolder(folder: Folder, status: FetchEmailsStatus): List<Email> = runBlocking {
val messageCount = folder.messageCount val messageCount = folder.messageCount
if (messageCount <= 0) { if (messageCount <= 0) {
return@runBlocking emptyList() return@runBlocking emptyList()
@ -111,7 +111,7 @@ open class EmailsFetcher(
IntRange(1, messageCount).mapNotNull { messageNumber -> // message numbers start at 1 IntRange(1, messageCount).mapNotNull { messageNumber -> // message numbers start at 1
async(coroutineDispatcher) { async(coroutineDispatcher) {
try { try {
findEInvoice(folder.getMessage(messageNumber), status) getEmail(folder.getMessage(messageNumber), status)
} catch (e: Throwable) { } catch (e: Throwable) {
log.error(e) { "Could not get email with messageNumber $messageNumber" } log.error(e) { "Could not get email with messageNumber $messageNumber" }
status.addError(FetchEmailsErrorType.GetEmail, messageNumber, e) status.addError(FetchEmailsErrorType.GetEmail, messageNumber, e)
@ -123,43 +123,39 @@ open class EmailsFetcher(
.filterNotNull() .filterNotNull()
} }
protected open fun findEInvoice(message: Message, status: FetchEmailsStatus): EmailWithInvoice? { protected open fun getEmail(message: Message, status: FetchEmailsStatus): Email? {
val parts = getAllMessageParts(message) val parts = getAllMessageParts(message)
val attachmentsWithEInvoice = parts.mapNotNull { part -> val attachments = parts.mapNotNull { part ->
findEInvoice(part, status) findAttachment(part, status)
} }
if (attachmentsWithEInvoice.isNotEmpty()) { val email = Email(
val email = EmailWithInvoice( 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" }, getPlainTextBody(parts, status), getHtmlBody(parts, status),
getPlainTextBody(parts, status), getHtmlBody(parts, status), attachments
attachmentsWithEInvoice )
)
status.options.emailReceived(email) status.options.emailReceived(email)
return email return email
}
return null
} }
protected open fun findEInvoice(messagePart: MessagePart, status: FetchEmailsStatus): EmailAttachmentWithEInvoice? { protected open fun findAttachment(messagePart: MessagePart, status: FetchEmailsStatus): EmailAttachment? {
try { try {
val part = messagePart.part val part = messagePart.part
val invoice = tryToReadEInvoice(part, messagePart.mediaType, status) val invoice = tryToReadEInvoice(part, messagePart.mediaType, status)
if (invoice != null) { if (invoice != null || Part.ATTACHMENT.equals(part.description, ignoreCase = true)) {
val filename = File(part.fileName) val filename = File(part.fileName)
val file = File.createTempFile(filename.nameWithoutExtension, "." + filename.extension).also { file -> val file = File.createTempFile(filename.nameWithoutExtension, "." + filename.extension).also { file ->
part.inputStream.use { it.copyTo(file.outputStream()) } part.inputStream.use { it.copyTo(file.outputStream()) }
file.deleteOnExit() file.deleteOnExit()
} }
return EmailAttachmentWithEInvoice(part.fileName, messagePart.mediaType, invoice, file) return EmailAttachment(part.fileName, messagePart.mediaType, invoice, file)
} }
} 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" }

View File

@ -1,15 +1,15 @@
package net.codinux.invoicing.email package net.codinux.invoicing.email
import net.codinux.invoicing.email.model.EmailWithInvoice import net.codinux.invoicing.email.model.Email
open class FetchEmailsOptions( open class FetchEmailsOptions(
val downloadMessageBody: Boolean = false, val downloadMessageBody: Boolean = false,
val emailFolderName: String = "INBOX", val emailFolderName: String = "INBOX",
val onError: ((FetchEmailsError) -> Unit)? = null, val onError: ((FetchEmailsError) -> Unit)? = null,
val onEmailReceived: ((EmailWithInvoice) -> Unit)? = null val onEmailReceived: ((Email) -> Unit)? = null
) { ) {
fun emailReceived(email: EmailWithInvoice) { fun emailReceived(email: Email) {
onEmailReceived?.invoke(email) onEmailReceived?.invoke(email)
} }
} }

View File

@ -1,9 +1,9 @@
package net.codinux.invoicing.email package net.codinux.invoicing.email
import net.codinux.invoicing.email.model.EmailWithInvoice import net.codinux.invoicing.email.model.Email
data class FetchEmailsResult( data class FetchEmailsResult(
val emails: List<EmailWithInvoice>, val emails: List<Email>,
val overallError: Throwable?, val overallError: Throwable?,
val messageSpecificErrors: List<FetchEmailsError> = emptyList() val messageSpecificErrors: List<FetchEmailsError> = emptyList()
) )

View File

@ -1,6 +1,6 @@
package net.codinux.invoicing.email package net.codinux.invoicing.email
import net.codinux.invoicing.email.model.EmailWithInvoice import net.codinux.invoicing.email.model.Email
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
open class ListenForNewMailsOptions( open class ListenForNewMailsOptions(
@ -10,5 +10,5 @@ open class ListenForNewMailsOptions(
emailFolderName: String = "INBOX", emailFolderName: String = "INBOX",
onError: ((FetchEmailsError) -> Unit)? = null, onError: ((FetchEmailsError) -> Unit)? = null,
onEmailReceived: (EmailWithInvoice) -> Unit onEmailReceived: (Email) -> Unit
) : FetchEmailsOptions(downloadMessageBody, emailFolderName, onError, onEmailReceived) ) : FetchEmailsOptions(downloadMessageBody, emailFolderName, onError, onEmailReceived)

View File

@ -3,7 +3,7 @@ package net.codinux.invoicing.email.model
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
class EmailWithInvoice( class Email(
val sender: String?, val sender: String?,
val subject: String, val subject: String,
val sent: Instant?, val sent: Instant?,
@ -19,9 +19,9 @@ class EmailWithInvoice(
val isEncrypted: Boolean = false, val isEncrypted: Boolean = false,
val plainTextBody: String?, val plainTextBody: String?,
val htmlBody: String?, val htmlBody: String?,
val attachmentsWithEInvoice: List<EmailAttachmentWithEInvoice> val attachments: List<EmailAttachment>
) { ) {
val plainTextOrHtmlBody: String? by lazy { plainTextBody ?: htmlBody } val plainTextOrHtmlBody: String? by lazy { plainTextBody ?: htmlBody }
override fun toString() = "${(sent ?: received).atZone(ZoneId.systemDefault()).toLocalDate()} $sender: $subject, ${attachmentsWithEInvoice.size} invoice(s)" override fun toString() = "${(sent ?: received).atZone(ZoneId.systemDefault()).toLocalDate()} $sender: $subject, ${attachments.size} attachment(s)"
} }

View File

@ -3,7 +3,7 @@ package net.codinux.invoicing.email.model
import net.codinux.invoicing.model.Invoice import net.codinux.invoicing.model.Invoice
import java.io.File import java.io.File
class EmailAttachmentWithEInvoice( class EmailAttachment(
val filename: String, val filename: String,
/** /**
* Attachment's media type like "application/xml", "application/pdf", ... * Attachment's media type like "application/xml", "application/pdf", ...
@ -11,7 +11,7 @@ class EmailAttachmentWithEInvoice(
* Should always be non-null, but can theoretically be null. * Should always be non-null, but can theoretically be null.
*/ */
val mediaType: String?, val mediaType: String?,
val invoice: Invoice, val invoice: Invoice?,
val file: File val file: File
) { ) {
override fun toString() = "$filename: $invoice" override fun toString() = "$filename: $invoice"

View File

@ -35,7 +35,7 @@ class Demonstration {
)) ))
fetchResult.emails.forEach { email -> fetchResult.emails.forEach { email ->
println("${email.sender}: ${email.attachmentsWithEInvoice.firstNotNullOfOrNull { it.invoice }?.totalAmounts?.duePayableAmount}") println("${email.sender}: ${email.attachments.firstNotNullOfOrNull { it.invoice }?.totalAmounts?.duePayableAmount}")
} }
} }