Erweiterungen

This commit is contained in:
2026-01-23 12:47:21 +01:00
parent 53f6dbb8b5
commit 860ca1a09e
2 changed files with 159 additions and 6 deletions

View File

@@ -1,10 +1,16 @@
package de.assecutor.aimailassistant.mail.service; package de.assecutor.aimailassistant.mail.service;
import org.eclipse.angus.mail.imap.IMAPFolder;
import org.eclipse.angus.mail.imap.IMAPStore;
import de.assecutor.aimailassistant.mail.domain.OrderEmail; import de.assecutor.aimailassistant.mail.domain.OrderEmail;
import de.assecutor.aimailassistant.mail.domain.OrderEmailRepository; import de.assecutor.aimailassistant.mail.domain.OrderEmailRepository;
import de.assecutor.aimailassistant.mail.domain.OrderSummary; import de.assecutor.aimailassistant.mail.domain.OrderSummary;
import de.assecutor.aimailassistant.mail.event.EmailBroadcaster; import de.assecutor.aimailassistant.mail.event.EmailBroadcaster;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.mail.*; import jakarta.mail.*;
import jakarta.mail.event.MessageCountAdapter;
import jakarta.mail.event.MessageCountEvent;
import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMultipart; import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.search.FlagTerm; import jakarta.mail.search.FlagTerm;
@@ -35,6 +41,13 @@ public class ImapEmailService {
private final boolean ssl; private final boolean ssl;
private final String folderName; private final String folderName;
// IDLE support fields
private volatile boolean idleSupported = false;
private volatile boolean idleRunning = false;
private Thread idleThread;
private IMAPStore idleStore;
private IMAPFolder idleFolder;
public ImapEmailService( public ImapEmailService(
OrderEmailRepository orderEmailRepository, OrderEmailRepository orderEmailRepository,
LlmService llmService, LlmService llmService,
@@ -54,8 +67,148 @@ public class ImapEmailService {
this.folderName = folderName; this.folderName = folderName;
} }
// ==================== IDLE Support ====================
@PostConstruct
public void initializeIdleListener() {
log.info("Initializing IMAP IDLE listener...");
startIdleListener();
}
@PreDestroy
public void shutdown() {
log.info("Shutting down IMAP IDLE listener...");
if (idleThread != null) {
idleThread.interrupt();
}
closeIdleConnection();
}
private void startIdleListener() {
idleThread = new Thread(() -> {
while (!Thread.interrupted()) {
try {
connectAndRunIdle();
} catch (InterruptedException e) {
log.info("IDLE thread interrupted, shutting down");
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
log.warn("IDLE connection lost: {}. Reconnecting in 10s...", e.getMessage());
idleRunning = false;
try {
Thread.sleep(10000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}, "IMAP-IDLE-Thread");
idleThread.setDaemon(true);
idleThread.start();
}
private void connectAndRunIdle() throws Exception {
// Properties erstellen
Properties props = new Properties();
props.put("mail.store.protocol", "imaps");
props.put("mail.imaps.host", host);
props.put("mail.imaps.port", String.valueOf(port));
props.put("mail.imaps.ssl.enable", String.valueOf(ssl));
props.put("mail.imaps.ssl.trust", "*");
Session session = Session.getInstance(props);
idleStore = (IMAPStore) session.getStore("imaps");
idleStore.connect(host, port, username, password);
// IDLE-Unterstützung prüfen
idleSupported = checkIdleSupport(idleStore);
if (!idleSupported) {
log.info("IMAP server does not support IDLE, using polling only");
idleStore.close();
idleStore = null;
// Warte lange bevor erneut versucht wird (Polling übernimmt)
Thread.sleep(Long.MAX_VALUE);
return;
}
// Folder öffnen
idleFolder = (IMAPFolder) idleStore.getFolder(folderName);
idleFolder.open(Folder.READ_WRITE);
// Listener für neue Nachrichten
idleFolder.addMessageCountListener(new MessageCountAdapter() {
@Override
public void messagesAdded(MessageCountEvent event) {
log.info("IDLE: {} new message(s) received", event.getMessages().length);
processNewMessages(event.getMessages());
}
});
log.info("IMAP IDLE started successfully on folder: {}", folderName);
idleRunning = true;
// IDLE-Loop - erneuert automatisch nach Server-Timeout
while (idleFolder.isOpen() && !Thread.interrupted()) {
// idle() blockiert bis neue Nachricht oder Timeout (~29 min)
idleFolder.idle();
}
// Wenn wir hier ankommen, wurde die Verbindung getrennt
idleRunning = false;
closeIdleConnection();
}
private boolean checkIdleSupport(IMAPStore store) {
try {
return store.hasCapability("IDLE");
} catch (MessagingException e) {
log.warn("Could not check IDLE capability: {}", e.getMessage());
return false;
}
}
private void processNewMessages(Message[] messages) {
for (Message message : messages) {
try {
processMessage(message);
message.setFlag(Flags.Flag.SEEN, true);
} catch (Exception e) {
log.error("Error processing message via IDLE", e);
}
}
}
private void closeIdleConnection() {
try {
if (idleFolder != null && idleFolder.isOpen()) {
idleFolder.close(false);
}
} catch (Exception e) {
log.debug("Error closing IDLE folder: {}", e.getMessage());
}
try {
if (idleStore != null && idleStore.isConnected()) {
idleStore.close();
}
} catch (Exception e) {
log.debug("Error closing IDLE store: {}", e.getMessage());
}
idleFolder = null;
idleStore = null;
}
// ==================== Polling (Fallback) ====================
@Scheduled(fixedDelayString = "${mail.imap.poll-interval-seconds:60}000") @Scheduled(fixedDelayString = "${mail.imap.poll-interval-seconds:60}000")
public void pollEmails() { public void pollEmails() {
// Nur pollen wenn IDLE nicht aktiv
if (idleRunning && idleSupported) {
log.debug("IDLE active, skipping poll");
return;
}
log.info("Polling IMAP mailbox for new emails..."); log.info("Polling IMAP mailbox for new emails...");
fetchAndProcessEmails(); fetchAndProcessEmails();
} }

View File

@@ -17,19 +17,19 @@ spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true spring.h2.console.enabled=true
# IMAP Configuration # IMAP Configuration
mail.imap.host=mail.appcreation.de mail.imap.host=mailhub.assecutor.org
mail.imap.port=993 mail.imap.port=993
mail.imap.username=sb@appcreation.de mail.imap.username=sb-anfrage@assecutor.org
mail.imap.password=SV1705CA!sb mail.imap.password=?,mpIpto,88
mail.imap.ssl=true mail.imap.ssl=true
mail.imap.folder=INBOX mail.imap.folder=INBOX
mail.imap.poll-interval-seconds=60 mail.imap.poll-interval-seconds=60
# SMTP Configuration # SMTP Configuration
spring.mail.host=mail.appcreation.de spring.mail.host=mailhub.assecutor.org
spring.mail.port=465 spring.mail.port=465
spring.mail.username=sb@appcreation.de spring.mail.username=sb-anfrage@assecutor.org
spring.mail.password=SV1705CA!sb spring.mail.password=?,mpIpto,88
spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.ssl.enable=true
spring.mail.properties.mail.smtp.ssl.required=true spring.mail.properties.mail.smtp.ssl.required=true