001/* 002 * JGrapes Event driven Framework 003 * Copyright (C) 2022 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.mail.events; 020 021import jakarta.mail.Flags.Flag; 022import jakarta.mail.Folder; 023import jakarta.mail.Message; 024import jakarta.mail.MessageRemovedException; 025import jakarta.mail.MessagingException; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.function.Function; 029import java.util.logging.Level; 030import java.util.logging.Logger; 031import org.jgrapes.core.Event; 032import org.jgrapes.mail.MailChannel; 033import org.jgrapes.mail.MailMonitor; 034 035/** 036 * Signals the retrieval of mails (update) by a {@link MailMonitor}. 037 * Must be fired on a {@link MailChannel}. 038 */ 039@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 040public class MailFoldersUpdated extends Event<Void> { 041 042 @SuppressWarnings("PMD.FieldNamingConventions") 043 private static final Logger logger 044 = Logger.getLogger(MailFoldersUpdated.class.getName()); 045 046 private final List<Folder> folders; 047 private final List<Message> newMessages; 048 049 /** 050 * Instantiates a new event. 051 * 052 * @param folders the folders 053 * @param newMessages the new messages 054 */ 055 public MailFoldersUpdated(List<Folder> folders, List<Message> newMessages) { 056 this.folders = folders; 057 this.newMessages = newMessages; 058 } 059 060 /** 061 * Returns the folders. 062 * 063 * @return the list 064 */ 065 public List<Folder> folders() { 066 return folders; 067 } 068 069 /** 070 * Return the new messages. New messages have not been reported 071 * before by an event. 072 * 073 * @return the list 074 */ 075 public List<Message> newMessages() { 076 return newMessages; 077 } 078 079 /** 080 * Execute the action with the given folder. The method ensures that 081 * the folder is open. 082 * 083 * @param folder the folder 084 * @param action the action 085 * @throws MessagingException the messaging exception 086 */ 087 @SuppressWarnings("PMD.GuardLogStatement") 088 public static <R> R withFolder(Folder folder, Function<Folder, R> action) 089 throws MessagingException { 090 synchronized (folder) { 091 if (!folder.isOpen()) { 092 logger.fine("Found folder \"" + folder.getFullName() 093 + "\" to be unexpectedly closed."); 094 folder.open(Folder.READ_WRITE); 095 } 096 return action.apply(folder); 097 } 098 } 099 100 /** 101 * Return all messages (which are not deleted) from the folder. 102 * 103 * @param folder the folder 104 * @return the message[] 105 * @throws MessagingException the messaging exception 106 */ 107 public static List<Message> messages(Folder folder) 108 throws MessagingException { 109 return messages(folder, Integer.MAX_VALUE); 110 } 111 112 /** 113 * Return all (or max) messages (which are not deleted) from the folder, 114 * starting with the newest message. 115 * 116 * @param folder the folder 117 * @param max the limit 118 * @return the message[] 119 * @throws MessagingException the messaging exception 120 */ 121 @SuppressWarnings("PMD.CognitiveComplexity") 122 public static List<Message> messages(Folder folder, int max) 123 throws MessagingException { 124 MessagingException[] exception = { null }; 125 @SuppressWarnings({ "PMD.PrematureDeclaration", 126 "PMD.GuardLogStatement" }) 127 var result = withFolder(folder, f -> { 128 List<Message> msgs = new LinkedList<>(); 129 try { 130 int available = folder.getMessageCount(); 131 int retrieve = Math.min(available, max); 132 int start = available - retrieve + 1; 133 if (start > available) { 134 return msgs; 135 } 136 // Loops from older to newer 137 for (var msg : f.getMessages(start, available)) { 138 if (canBeAdded(msg)) { 139 // prepend newer 140 msgs.add(0, msg); 141 } 142 } 143 // Adds older messages to fill until max 144 while (start > 1 && msgs.size() < max) { 145 Message msg = f.getMessage(--start); 146 if (canBeAdded(msg)) { 147 msgs.add(msg); 148 } 149 } 150 } catch (MessagingException e) { 151 logger.log(Level.FINE, "Problem getting messages: " 152 + e.getMessage(), e); 153 exception[0] = e; 154 } 155 return msgs; 156 }); 157 if (exception[0] != null) { 158 throw exception[0]; 159 } 160 return result; 161 } 162 163 private static boolean canBeAdded(Message msg) 164 throws MessagingException { 165 try { 166 if (msg.getFlags().contains(Flag.DELETED)) { 167 return false; 168 } 169 } catch (MessageRemovedException e) { 170 return false; 171 } 172 return true; 173 } 174}