diff --git a/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java b/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java index 9f43dce..ff8638a 100644 --- a/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java +++ b/src/main/java/de/assecutor/aimailassistant/mail/service/ImapEmailService.java @@ -1,10 +1,16 @@ 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.OrderEmailRepository; import de.assecutor.aimailassistant.mail.domain.OrderSummary; import de.assecutor.aimailassistant.mail.event.EmailBroadcaster; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import jakarta.mail.*; +import jakarta.mail.event.MessageCountAdapter; +import jakarta.mail.event.MessageCountEvent; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMultipart; import jakarta.mail.search.FlagTerm; @@ -35,6 +41,13 @@ public class ImapEmailService { private final boolean ssl; 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( OrderEmailRepository orderEmailRepository, LlmService llmService, @@ -54,8 +67,148 @@ public class ImapEmailService { 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") 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..."); fetchAndProcessEmails(); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5ecfe5a..fd5f7ed 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -17,19 +17,19 @@ spring.jpa.hibernate.ddl-auto=update spring.h2.console.enabled=true # IMAP Configuration -mail.imap.host=mail.appcreation.de +mail.imap.host=mailhub.assecutor.org mail.imap.port=993 -mail.imap.username=sb@appcreation.de -mail.imap.password=SV1705CA!sb +mail.imap.username=sb-anfrage@assecutor.org +mail.imap.password=?,mpIpto,88 mail.imap.ssl=true mail.imap.folder=INBOX mail.imap.poll-interval-seconds=60 # SMTP Configuration -spring.mail.host=mail.appcreation.de +spring.mail.host=mailhub.assecutor.org spring.mail.port=465 -spring.mail.username=sb@appcreation.de -spring.mail.password=SV1705CA!sb +spring.mail.username=sb-anfrage@assecutor.org +spring.mail.password=?,mpIpto,88 spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.ssl.enable=true spring.mail.properties.mail.smtp.ssl.required=true