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.MailStoreMonitor; 034 035/** 036 * Signals the retrieval of mails (update) by a {@link MailStoreMonitor}. 037 * Must be fired on a {@link MailChannel}. 038 */ 039@SuppressWarnings("PMD.DataflowAnomalyAnalysis") 040public class FoldersUpdated extends Event<Void> { 041 042 @SuppressWarnings("PMD.FieldNamingConventions") 043 private static final Logger logger 044 = Logger.getLogger(FoldersUpdated.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 allMessages the messages 053 */ 054 public FoldersUpdated(List<Folder> folders, List<Message> newMessages) { 055 this.folders = folders; 056 this.newMessages = newMessages; 057 } 058 059 /** 060 * Returns the folders. 061 * 062 * @return the list 063 */ 064 public List<Folder> folders() { 065 return folders; 066 } 067 068 /** 069 * Return the new messages. New messages have not been reported 070 * before by an event. 071 * 072 * @return the list 073 */ 074 public List<Message> newMessages() { 075 return newMessages; 076 } 077 078 /** 079 * Execute the action with the given folder. The method ensures that 080 * the folder is open. 081 * 082 * @param folder the folder 083 * @param action the action 084 * @throws MessagingException the messaging exception 085 */ 086 @SuppressWarnings("PMD.GuardLogStatement") 087 public static <R> R withFolder(Folder folder, Function<Folder, R> action) 088 throws MessagingException { 089 synchronized (folder) { 090 if (!folder.isOpen()) { 091 logger.fine("Found folder \"" + folder.getFullName() 092 + "\" to be unexpectedly closed."); 093 folder.open(Folder.READ_WRITE); 094 } 095 return action.apply(folder); 096 } 097 } 098 099 /** 100 * Return all messages (which are not deleted) from the folder. 101 * 102 * @param folder the folder 103 * @return the message[] 104 * @throws MessagingException the messaging exception 105 */ 106 public static List<Message> messages(Folder folder) 107 throws MessagingException { 108 return messages(folder, Integer.MAX_VALUE); 109 } 110 111 /** 112 * Return all (or max) messages (which are not deleted) from the folder, 113 * starting with the newest message. 114 * 115 * @param folder the folder 116 * @param max the limit 117 * @return the message[] 118 * @throws MessagingException the messaging exception 119 */ 120 @SuppressWarnings("PMD.CognitiveComplexity") 121 public static List<Message> messages(Folder folder, int max) 122 throws MessagingException { 123 MessagingException[] exception = { null }; 124 @SuppressWarnings({ "PMD.PrematureDeclaration", 125 "PMD.GuardLogStatement" }) 126 var result = withFolder(folder, f -> { 127 List<Message> msgs = new LinkedList<>(); 128 try { 129 int available = folder.getMessageCount(); 130 int retrieve = Math.min(available, max); 131 int start = available - retrieve + 1; 132 if (start > available) { 133 return msgs; 134 } 135 // Loops from older to newer 136 for (var msg : f.getMessages(start, available)) { 137 if (canBeAdded(msg)) { 138 // prepend newer 139 msgs.add(0, msg); 140 } 141 } 142 // Adds older messages to fill until max 143 while (start > 1 && msgs.size() < max) { 144 Message msg = f.getMessage(--start); 145 if (canBeAdded(msg)) { 146 msgs.add(msg); 147 } 148 } 149 } catch (MessagingException e) { 150 logger.log(Level.FINE, "Problem getting messages: " 151 + e.getMessage(), e); 152 exception[0] = e; 153 } 154 return msgs; 155 }); 156 if (exception[0] != null) { 157 throw exception[0]; 158 } 159 return result; 160 } 161 162 private static boolean canBeAdded(Message msg) 163 throws MessagingException { 164 try { 165 if (msg.getFlags().contains(Flag.DELETED)) { 166 return false; 167 } 168 } catch (MessageRemovedException e) { 169 return false; 170 } 171 return true; 172 } 173}