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}