package de.srsoftware.widerhall.mail; import com.sun.mail.imap.IMAPFolder; import de.srsoftware.widerhall.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.mail.*; import java.time.Duration; import java.util.Date; import java.util.HashSet; import java.util.Properties; public class ImapClient { private static final Logger LOG = LoggerFactory.getLogger(ImapClient.class); private final int port; private final String host, username, password, folderName; private final ProblemListener problemListener; private IMAPFolder inbox; private ListeningThread listeningThread; private Heartbeat heartbeat; private class ListeningThread extends Thread { private static final Logger LOG = LoggerFactory.getLogger(ListeningThread.class); private HashSet listeners = new HashSet<>(); private boolean stopped = false; private Session session; public ListeningThread addListener(MessageHandler messageHandler) { listeners.add(messageHandler); return this; } public ListeningThread doStop() { stopped = true; return this; } public ListeningThread dropListeners() { listeners.clear(); return this; } private void handle(Message message) throws MessagingException { LOG.debug("Handling {}",message.getSubject()); for (MessageHandler listener : listeners) listener.onMessageReceived(message); } private void handleMessages() throws MessagingException { LOG.debug("Reading email of {}:",username); if (!inbox.isOpen()) inbox.open(IMAPFolder.READ_WRITE); for (Message message : inbox.getMessages()){ if (message.isSet(Flags.Flag.SEEN)) continue; handle(message); Folder folder = message.getFolder(); if (!folder.isOpen()) folder.open(Folder.READ_WRITE); message.setFlag(Flags.Flag.SEEN,true); } } private Properties imapProps() { Properties props = new Properties(); props.put(Constants.PROTOCOL,Constants.IMAPS); return props; } private void openInbox() throws MessagingException { LOG.debug("Connecting and logging in…"); Properties props = imapProps(); session = Session.getInstance(props); Store store = session.getStore(Constants.IMAPS); store.connect(host,username,password); LOG.debug("Connected, opening {}:",folderName); inbox = (IMAPFolder)store.getFolder(folderName); inbox.open(IMAPFolder.READ_WRITE); while (!stopped){ handleMessages(); problemListener.clearProblems(); LOG.debug("Idling."); inbox.idle(true); } } @Override public void run() { while (!stopped) { try { sleep(5000); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); } try { openInbox(); } catch (MessagingException e){ LOG.warn("Connection problem:",e); problemListener.onImapException(e); } } } } private class Heartbeat extends Thread{ private static final Logger LOG = LoggerFactory.getLogger(Heartbeat.class); private boolean stopped = false; public Heartbeat doStop() { stopped = true; return this; } @Override public void run() { while (!stopped){ try { sleep(300034); if (inbox != null) continue; LOG.debug("sending NOOP"); inbox.doCommand(protocol -> { protocol.simpleCommand("NOOP",null); return null; }); } catch (InterruptedException | MessagingException e) { e.printStackTrace(); } } } } public ImapClient(String host, int port, String username, String password, String folderName,ProblemListener listener) { this.host = host; this.port = port; this.username = username; this.password = password; this.folderName = folderName; this.problemListener = listener; } public ImapClient addListener(MessageHandler messageHandler) { if (listeningThread == null) listeningThread = new ListeningThread(); listeningThread.addListener(messageHandler); return this; } public void dropMailsOlderThan(Integer holdTime) throws MessagingException { var now = new Date(); if (holdTime == null) return; LOG.debug("Removing mails older than {} days:",holdTime); if (!inbox.isOpen()) inbox.open(IMAPFolder.READ_WRITE); for (Message message : inbox.getMessages()){ Date receivedDate = message.getReceivedDate(); Duration duration = Duration.between(receivedDate.toInstant(),now.toInstant()); var days = duration.toDays(); LOG.info("Message {} is {} days old!",message.getSubject(),days); if (days > holdTime){ LOG.info("…removing"); Folder folder = message.getFolder(); if (!folder.isOpen()) folder.open(Folder.READ_WRITE); message.setFlag(Flags.Flag.DELETED, true); } } inbox.expunge(); } public String folderName(){ return folderName; } public String host(){ return host; } public ImapClient move(Message message, String destinationFolder) throws MessagingException { if (listeningThread == null || listeningThread.stopped) throw new IllegalStateException("IMAP client not connected!"); var source = message.getFolder(); if (!source.isOpen()) source.open(Folder.READ_WRITE); var messages = new Message[]{message}; var store = source.getStore(); var dest = store.getFolder(new URLName(destinationFolder)); if (!dest.exists()) dest.create(Folder.HOLDS_MESSAGES); source.copyMessages(messages,dest); source.setFlags(messages, new Flags(Flags.Flag.DELETED), true); source.close(true); return this; } public String password(){ return password; } public int port(){ return port; } public ImapClient start() { stop(); LOG.info("Creating ListeningThread for {}…",username); (listeningThread = new ListeningThread()).start(); LOG.info("Creating Heartbeat for {}…",username); (heartbeat = new Heartbeat()).start(); return this; } public ImapClient stop(){ if (listeningThread != null) { LOG.info("Stopping old ListeningThread for {}…",username); listeningThread.dropListeners().doStop(); listeningThread = null; } if (heartbeat != null){ heartbeat.doStop(); heartbeat = null; } return this; } public String username(){ return username; } }