Erweiterungen
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user