···5959 public void folderExpunged(FolderExpungedEvent e);
60606161 /**
6262- * Invoked when the message index-to-UID map for a folder has been loaded.
6363- * <p>
6464- * This listener method exists to support the <code>MailStoreServices</code>
6565- * layer, and should be cleaned up in a future refactoring.
6666- * </p>
6767- *
6868- * @param e Folder event data
6969- */
7070- public void folderMessageIndexMapAvailable(FolderMessageIndexMapEvent e);
7171-7272- /**
7362 * Invoked when the folder state has changed significantly enough that a
7463 * refresh operation is required to reliably maintain the folder selection.
7564 * <p>
···11-/*-
22- * Copyright (c) 2010, Derek Konigsberg
33- * All rights reserved.
44- *
55- * Redistribution and use in source and binary forms, with or without
66- * modification, are permitted provided that the following conditions
77- * are met:
88- *
99- * 1. Redistributions of source code must retain the above copyright
1010- * notice, this list of conditions and the following disclaimer.
1111- * 2. Redistributions in binary form must reproduce the above copyright
1212- * notice, this list of conditions and the following disclaimer in the
1313- * documentation and/or other materials provided with the distribution.
1414- * 3. Neither the name of the project nor the names of its
1515- * contributors may be used to endorse or promote products derived
1616- * from this software without specific prior written permission.
1717- *
1818- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1919- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2020- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
2121- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
2222- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
2323- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
2424- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2525- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2626- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2727- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2828- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2929- * OF THE POSSIBILITY OF SUCH DAMAGE.
3030- */
3131-package org.logicprobe.LogicMail.mail;
3232-3333-import net.rim.device.api.util.ToIntHashtable;
3434-3535-public class FolderMessageIndexMapEvent extends FolderEvent {
3636- private final ToIntHashtable uidIndexMap;
3737-3838- public FolderMessageIndexMapEvent(Object source, FolderTreeItem folder, ToIntHashtable uidIndexMap) {
3939- super(source, folder);
4040- this.uidIndexMap = uidIndexMap;
4141- }
4242-4343- /**
4444- * Gets the message UID-to-index map that was retrieved.
4545- *
4646- * @return the UID-to-index map, with UIDs in <code>String</code> form.
4747- */
4848- public ToIntHashtable getUidIndexMap() {
4949- return uidIndexMap;
5050- }
5151-}
···11+/*-
22+ * Copyright (c) 2011, Derek Konigsberg
33+ * All rights reserved.
44+ *
55+ * Redistribution and use in source and binary forms, with or without
66+ * modification, are permitted provided that the following conditions
77+ * are met:
88+ *
99+ * 1. Redistributions of source code must retain the above copyright
1010+ * notice, this list of conditions and the following disclaimer.
1111+ * 2. Redistributions in binary form must reproduce the above copyright
1212+ * notice, this list of conditions and the following disclaimer in the
1313+ * documentation and/or other materials provided with the distribution.
1414+ * 3. Neither the name of the project nor the names of its
1515+ * contributors may be used to endorse or promote products derived
1616+ * from this software without specific prior written permission.
1717+ *
1818+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1919+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2020+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
2121+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
2222+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
2323+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
2424+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2525+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2626+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2727+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2828+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2929+ * OF THE POSSIBILITY OF SUCH DAMAGE.
3030+ */
3131+package org.logicprobe.LogicMail.mail;
3232+3333+import java.io.IOException;
3434+import java.util.Enumeration;
3535+import java.util.Hashtable;
3636+import java.util.Vector;
3737+3838+import net.rim.device.api.collection.util.BigVector;
3939+import net.rim.device.api.util.Comparator;
4040+4141+import org.logicprobe.LogicMail.LogicMailResource;
4242+import org.logicprobe.LogicMail.mail.imap.ImapClient;
4343+import org.logicprobe.LogicMail.message.FolderMessage;
4444+4545+class ImapFolderRefreshRequest extends NetworkMailStoreRequest implements MailStoreRequest {
4646+ private final String statusMessage;
4747+ private final FolderTreeItem folder;
4848+ private final Hashtable loadedMessageMap;
4949+ private int messageRetentionLimit;
5050+ private volatile boolean checkAllTokens;
5151+ private Vector secondaryMessageTokensToFetch;
5252+5353+ ImapFolderRefreshRequest(NetworkMailStore mailStore, FolderTreeItem folder, FolderMessage[] loadedMessages) {
5454+ super(mailStore);
5555+ this.statusMessage = resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES);
5656+ this.folder = folder;
5757+ this.loadedMessageMap = new Hashtable();
5858+ if(loadedMessages != null) {
5959+ for(int i=0; i<loadedMessages.length; i++) {
6060+ loadedMessageMap.put(
6161+ loadedMessages[i].getMessageToken().getMessageUid(),
6262+ loadedMessages[i]);
6363+ }
6464+ }
6565+ }
6666+6767+ private FolderListener folderListener = new FolderListener() {
6868+ public void folderMessagesAvailable(FolderMessagesEvent e) { }
6969+ public void folderStatusChanged(FolderEvent e) { }
7070+ public void folderExpunged(FolderExpungedEvent e) { }
7171+ public void folderRefreshRequired(FolderEvent e) {
7272+ if(e.getFolder().equals(folder)){
7373+ checkAllTokens = true;
7474+ }
7575+ }
7676+ };
7777+7878+ protected String getInitialStatus() {
7979+ return statusMessage + "...";
8080+ }
8181+8282+ public void execute(MailClient client) throws IOException, MailException {
8383+ ImapClient incomingClient = (ImapClient)client;
8484+8585+ mailStore.addFolderListener(folderListener);
8686+8787+ this.messageRetentionLimit = incomingClient.getAcctConfig().getMaximumFolderMessages();
8888+8989+ checkActiveFolder(incomingClient, folder);
9090+9191+ // Fetch new folder messages from the mail store
9292+ Vector folderMessages = new Vector();
9393+ incomingClient.getNewFolderMessages(
9494+ true,
9595+ new GetFolderMessageCallback(folderMessages, true),
9696+ getProgressHandler(statusMessage));
9797+9898+ initialFlagsRefreshComplete(incomingClient, folderMessages);
9999+ }
100100+101101+ protected void fireMailStoreRequestComplete() {
102102+ mailStore.removeFolderListener(folderListener);
103103+ super.fireMailStoreRequestComplete();
104104+ }
105105+106106+ protected void fireMailStoreRequestFailed(Throwable exception, boolean isFinal) {
107107+ mailStore.removeFolderListener(folderListener);
108108+ super.fireMailStoreRequestFailed(exception, isFinal);
109109+ }
110110+111111+ private void initialFlagsRefreshComplete(ImapClient incomingClient, Vector pendingFlagUpdates) throws IOException, MailException {
112112+ secondaryMessageTokensToFetch = new Vector();
113113+ MessageToken oldestFetchedToken = null;
114114+ Comparator tokenComparator = null;
115115+116116+ // Iterate through the pending flag updates, doing the following:
117117+ // - Remove messages from the orphan set that exist on the server
118118+ // - Update the cache for fetched messages
119119+ // - Build a collection of messages to provide update notifications for
120120+ // - Build a collection of messages that need to be fetched
121121+ // - Keep track of the oldest message in the update set
122122+ int size = pendingFlagUpdates.size();
123123+ for(int i=0; i<size; i++) {
124124+ FolderMessage message = (FolderMessage)pendingFlagUpdates.elementAt(i);
125125+ MessageToken token = message.getMessageToken();
126126+127127+ // Remove messages with received flag updates from the orphan set
128128+ if(loadedMessageMap.remove(token.getMessageUid()) == null) {
129129+ // Anything that wasn't in the set needs to be fetched
130130+ secondaryMessageTokensToFetch.addElement(token);
131131+ }
132132+133133+ if(oldestFetchedToken == null) {
134134+ oldestFetchedToken = token;
135135+ tokenComparator = token.getComparator();
136136+ }
137137+ else {
138138+ if(tokenComparator.compare(token, oldestFetchedToken) < 0) {
139139+ oldestFetchedToken = token;
140140+ }
141141+ }
142142+ }
143143+ messageRetentionLimit -= size;
144144+ pendingFlagUpdates.removeAllElements();
145145+146146+ // Build a collection of messages in the cache that still need to be verified
147147+ Vector cachedTokensToCheck = new Vector();
148148+ if(checkAllTokens) {
149149+ for(Enumeration e = loadedMessageMap.elements(); e.hasMoreElements() ;) {
150150+ MessageToken token = ((FolderMessage)e.nextElement()).getMessageToken();
151151+ cachedTokensToCheck.addElement(token);
152152+ }
153153+ }
154154+ else if(oldestFetchedToken != null) {
155155+ for(Enumeration e = loadedMessageMap.elements(); e.hasMoreElements() ;) {
156156+ MessageToken token = ((FolderMessage)e.nextElement()).getMessageToken();
157157+ if(tokenComparator.compare(token, oldestFetchedToken) < 0) {
158158+ cachedTokensToCheck.addElement(token);
159159+ }
160160+ }
161161+ }
162162+ checkAllTokens = false;
163163+164164+ if(cachedTokensToCheck.size() > 0 && messageRetentionLimit > 0) {
165165+ // Perform a second flags fetch
166166+ MessageToken[] tokens = new MessageToken[cachedTokensToCheck.size()];
167167+ cachedTokensToCheck.copyInto(tokens);
168168+169169+ Vector folderMessages = new Vector();
170170+ incomingClient.getFolderMessages(
171171+ tokens,
172172+ true,
173173+ new GetFolderMessageCallback(folderMessages, true),
174174+ getProgressHandler(statusMessage));
175175+176176+ secondaryFlagsRefreshComplete(incomingClient, folderMessages);
177177+ }
178178+ else {
179179+ removeOrphanedMessages();
180180+ finalFolderMessageFetch(incomingClient);
181181+ }
182182+ }
183183+184184+ private void secondaryFlagsRefreshComplete(ImapClient incomingClient, Vector pendingFlagUpdates) throws IOException, MailException {
185185+ int size = pendingFlagUpdates.size();
186186+ BigVector messagesUpdated = new BigVector(size);
187187+ Comparator folderMessageComparator = FolderMessage.getComparator();
188188+ for(int i=0; i<size; i++) {
189189+ FolderMessage message = (FolderMessage)pendingFlagUpdates.elementAt(i);
190190+ MessageToken token = message.getMessageToken();
191191+192192+ // Remove messages with received flag updates from the orphan set
193193+ if(loadedMessageMap.remove(token.getMessageUid()) != null) {
194194+ // If it was removed from the set, then we assume it exists
195195+ // in the cache and will have been updated accordingly.
196196+ messagesUpdated.insertElement(folderMessageComparator, message);
197197+ }
198198+ }
199199+ pendingFlagUpdates.removeAllElements();
200200+201201+ // Determine the how many messages from this secondary set we can keep
202202+ size = messagesUpdated.size();
203203+ if(size > messageRetentionLimit) {
204204+ // We have too many additional messages, so we need to prune the set
205205+ messagesUpdated.optimize();
206206+207207+ int splitIndex = messagesUpdated.size() - messageRetentionLimit;
208208+ for(int i=0; i<splitIndex; i++) {
209209+ FolderMessage message = (FolderMessage)messagesUpdated.elementAt(i);
210210+ loadedMessageMap.put(message.getMessageToken().getMessageUid(), message);
211211+ }
212212+ }
213213+214214+ removeOrphanedMessages();
215215+ finalFolderMessageFetch(incomingClient);
216216+ }
217217+218218+ private void finalFolderMessageFetch(ImapClient incomingClient) throws IOException, MailException {
219219+ // Queue a fetch for messages missing from the cache
220220+ if(!secondaryMessageTokensToFetch.isEmpty()) {
221221+ MessageToken[] fetchArray = new MessageToken[secondaryMessageTokensToFetch.size()];
222222+ secondaryMessageTokensToFetch.copyInto(fetchArray);
223223+ secondaryMessageTokensToFetch.removeAllElements();
224224+225225+ incomingClient.getFolderMessages(
226226+ fetchArray,
227227+ false,
228228+ new GetFolderMessageCallback(false),
229229+ getProgressHandler(statusMessage));
230230+ }
231231+ loadedMessageMap.clear();
232232+ fireMailStoreRequestComplete();
233233+ }
234234+235235+ private void removeOrphanedMessages() {
236236+ Enumeration e = loadedMessageMap.elements();
237237+ MessageToken[] orphanedTokens = new MessageToken[loadedMessageMap.size()];
238238+ int index = 0;
239239+ while(e.hasMoreElements()) {
240240+ FolderMessage message = (FolderMessage)e.nextElement();
241241+ orphanedTokens[index++] = message.getMessageToken();
242242+ }
243243+ loadedMessageMap.clear();
244244+ mailStore.fireFolderExpunged(folder, orphanedTokens, new MessageToken[0]);
245245+ }
246246+247247+ private class GetFolderMessageCallback implements FolderMessageCallback {
248248+ private final Vector folderMessages;
249249+ private final boolean flagsOnly;
250250+ public GetFolderMessageCallback(Vector folderMessages, boolean flagsOnly) {
251251+ this.folderMessages = folderMessages;
252252+ this.flagsOnly = flagsOnly;
253253+ }
254254+ public GetFolderMessageCallback(boolean flagsOnly) {
255255+ this(null, flagsOnly);
256256+ }
257257+258258+ public void folderMessageUpdate(FolderMessage folderMessage) {
259259+ if(folderMessage != null) {
260260+ if(folderMessages != null) {
261261+ folderMessages.addElement(folderMessage);
262262+ }
263263+ mailStore.fireFolderMessagesAvailable(folder, new FolderMessage[] { folderMessage }, flagsOnly);
264264+ }
265265+ else {
266266+ // This is the last update of the sequence
267267+ mailStore.fireFolderMessagesAvailable(folder, null, flagsOnly);
268268+ }
269269+ }
270270+ };
271271+}
···11-/*-
22- * Copyright (c) 2011, Derek Konigsberg
33- * All rights reserved.
44- *
55- * Redistribution and use in source and binary forms, with or without
66- * modification, are permitted provided that the following conditions
77- * are met:
88- *
99- * 1. Redistributions of source code must retain the above copyright
1010- * notice, this list of conditions and the following disclaimer.
1111- * 2. Redistributions in binary form must reproduce the above copyright
1212- * notice, this list of conditions and the following disclaimer in the
1313- * documentation and/or other materials provided with the distribution.
1414- * 3. Neither the name of the project nor the names of its
1515- * contributors may be used to endorse or promote products derived
1616- * from this software without specific prior written permission.
1717- *
1818- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1919- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2020- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
2121- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
2222- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
2323- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
2424- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2525- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2626- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2727- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2828- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2929- * OF THE POSSIBILITY OF SUCH DAMAGE.
3030- */
3131-3232-package org.logicprobe.LogicMail.mail;
3333-3434-import java.io.IOException;
3535-3636-import net.rim.device.api.util.ToIntHashtable;
3737-3838-import org.logicprobe.LogicMail.LogicMailResource;
3939-4040-public class NetworkFolderMessageIndexMapRequest extends NetworkMailStoreRequest implements MailStoreRequest {
4141- private final FolderTreeItem folder;
4242- private ToIntHashtable resultUidIndexMap;
4343-4444- NetworkFolderMessageIndexMapRequest(NetworkMailStore mailStore, FolderTreeItem folder) {
4545- super(mailStore);
4646- this.folder = folder;
4747- }
4848-4949- public FolderTreeItem getFolder() {
5050- return folder;
5151- }
5252-5353- protected String getInitialStatus() {
5454- return resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES) + "...";
5555- }
5656-5757- public void execute(MailClient client) throws IOException, MailException {
5858- IncomingMailClient incomingClient = (IncomingMailClient)client;
5959-6060- checkActiveFolder(incomingClient, folder);
6161-6262- String message = resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES);
6363- resultUidIndexMap = incomingClient.getFolderMessageIndexMap(getProgressHandler(message));
6464-6565- fireMailStoreRequestComplete();
6666- mailStore.fireFolderMessageIndexMapAvailable(folder, resultUidIndexMap);
6767- }
6868-6969- public ToIntHashtable getResultUidIndexMap() {
7070- return resultUidIndexMap;
7171- }
7272-}
···3636import net.rim.device.api.system.UnsupportedOperationException;
37373838import org.logicprobe.LogicMail.conf.AccountConfig;
3939+import org.logicprobe.LogicMail.mail.imap.ImapClient;
4040+import org.logicprobe.LogicMail.mail.pop.PopClient;
4141+import org.logicprobe.LogicMail.message.FolderMessage;
3942import org.logicprobe.LogicMail.message.MessageFlags;
4043import org.logicprobe.LogicMail.message.MimeMessagePart;
4144···212215 }
213216214217 /**
215215- * Requests the message UID-to-index map for a particular folder.
216216- * <p>
217217- * Successful completion is indicated by a call to
218218- * {@link FolderListener#folderMessageIndexMapAvailable(FolderMessageIndexMapEvent)}.
219219- * </p>
220220- *
221221- * @param folder The folder to request a message listing for.
218218+ * Creates a request to synchronize a locally cached folder with data from
219219+ * the mail server. This is a complex request with protocol-dependent
220220+ * implementations, and is intended to be called from within cache-handling
221221+ * code.
222222+ *
223223+ * @param folder The folder to refresh.
224224+ * @param loadedMessages Collection of {@link FolderMessage} objects
225225+ * for messages that have already been loaded for this folder prior to
226226+ * the start of the refresh operation.
227227+ * @return the request object
222228 */
223223- public NetworkFolderMessageIndexMapRequest requestFolderMessageIndexMap(FolderTreeItem folder) {
224224- NetworkFolderMessageIndexMapRequest request = new NetworkFolderMessageIndexMapRequest(this, folder);
225225- return request;
229229+ public MailStoreRequest createFolderRefreshRequest(FolderTreeItem folder, FolderMessage[] loadedMessages) {
230230+ if(client instanceof ImapClient) {
231231+ return new ImapFolderRefreshRequest(this, folder, loadedMessages);
232232+ }
233233+ else if(client instanceof PopClient) {
234234+ return new PopFolderRefreshRequest(this, folder, loadedMessages);
235235+ }
236236+ else {
237237+ throw new UnsupportedOperationException();
238238+ }
226239 }
227240228241 public MessageRequest createMessageRequest(MessageToken messageToken, boolean useLimits) {
···11+/*-
22+ * Copyright (c) 2011, Derek Konigsberg
33+ * All rights reserved.
44+ *
55+ * Redistribution and use in source and binary forms, with or without
66+ * modification, are permitted provided that the following conditions
77+ * are met:
88+ *
99+ * 1. Redistributions of source code must retain the above copyright
1010+ * notice, this list of conditions and the following disclaimer.
1111+ * 2. Redistributions in binary form must reproduce the above copyright
1212+ * notice, this list of conditions and the following disclaimer in the
1313+ * documentation and/or other materials provided with the distribution.
1414+ * 3. Neither the name of the project nor the names of its
1515+ * contributors may be used to endorse or promote products derived
1616+ * from this software without specific prior written permission.
1717+ *
1818+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1919+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2020+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
2121+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
2222+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
2323+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
2424+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2525+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2626+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2727+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2828+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
2929+ * OF THE POSSIBILITY OF SUCH DAMAGE.
3030+ */
3131+package org.logicprobe.LogicMail.mail;
3232+3333+import java.io.IOException;
3434+import java.util.Enumeration;
3535+import java.util.Hashtable;
3636+import java.util.Vector;
3737+3838+import net.rim.device.api.util.IntHashtable;
3939+import net.rim.device.api.util.IntVector;
4040+import net.rim.device.api.util.SimpleSortingIntVector;
4141+import net.rim.device.api.util.ToIntHashtable;
4242+4343+import org.logicprobe.LogicMail.LogicMailResource;
4444+import org.logicprobe.LogicMail.mail.pop.PopClient;
4545+import org.logicprobe.LogicMail.message.FolderMessage;
4646+4747+class PopFolderRefreshRequest extends NetworkMailStoreRequest implements MailStoreRequest {
4848+ private final String statusMessage;
4949+ private final FolderTreeItem folder;
5050+ private final Hashtable loadedMessageMap;
5151+5252+ PopFolderRefreshRequest(NetworkMailStore mailStore, FolderTreeItem folder, FolderMessage[] loadedMessages) {
5353+ super(mailStore);
5454+ this.statusMessage = resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES);
5555+ this.folder = folder;
5656+ this.loadedMessageMap = new Hashtable();
5757+ if(loadedMessages != null) {
5858+ for(int i=0; i<loadedMessages.length; i++) {
5959+ loadedMessageMap.put(
6060+ loadedMessages[i].getMessageToken().getMessageUid(),
6161+ loadedMessages[i]);
6262+ }
6363+ }
6464+ }
6565+6666+ protected String getInitialStatus() {
6767+ return statusMessage + "...";
6868+ }
6969+7070+ public void execute(MailClient client) throws IOException, MailException {
7171+ PopClient incomingClient = (PopClient)client;
7272+7373+ checkActiveFolder(incomingClient, folder);
7474+7575+ // Get the Index-to-UID map for the folder
7676+ ToIntHashtable uidIndexMap = incomingClient.getFolderMessageIndexMap(getProgressHandler(statusMessage));
7777+7878+ // Get configuration values that affect the rest of the process
7979+ int initialMessageLimit = incomingClient.getAcctConfig().getInitialFolderMessages();
8080+ int messageRetentionLimit = incomingClient.getAcctConfig().getMaximumFolderMessages();
8181+8282+ // Initialize collections used during processing
8383+ Vector messagesUpdated = new Vector();
8484+ SimpleSortingIntVector indexVector = new SimpleSortingIntVector();
8585+ IntHashtable cachedIndexToMessageMap = new IntHashtable();
8686+8787+ // Iterate through the UID-to-index map, and do the following:
8888+ // - Remove cache-loaded messages from the orphan set if they exist on the server.
8989+ // - Update index information for those messages that do still exist server-side.
9090+ // - Build a sortable vector of index values
9191+ Enumeration e = uidIndexMap.keys();
9292+ while(e.hasMoreElements()) {
9393+ String uid = (String)e.nextElement();
9494+ int index = uidIndexMap.get(uid);
9595+ indexVector.addElement(index);
9696+9797+ FolderMessage message = (FolderMessage)loadedMessageMap.remove(uid);
9898+ if(message != null) {
9999+ message.setIndex(index);
100100+ message.getMessageToken().updateMessageIndex(index);
101101+ messagesUpdated.addElement(message);
102102+ cachedIndexToMessageMap.put(index, message);
103103+ }
104104+ }
105105+ indexVector.reSort(SimpleSortingIntVector.SORT_TYPE_NUMERIC);
106106+107107+ notifyMessageFlagUpdates(messagesUpdated);
108108+ removeOrphanedMessages();
109109+110110+ // Determine the fetch range
111111+ int size = indexVector.size();
112112+ if(size == 0) { return; }
113113+ int fetchRangeStart = Math.max(0, size - initialMessageLimit);
114114+115115+ // Build a list of indices to fetch
116116+ IntVector messagesToFetch = new IntVector();
117117+ for(int i=indexVector.size() - 1; i >= fetchRangeStart; --i) {
118118+ int index = indexVector.elementAt(i);
119119+ if(!cachedIndexToMessageMap.containsKey(index)) {
120120+ messagesToFetch.addElement(index);
121121+ }
122122+ }
123123+124124+ int additionalMessageLimit = messageRetentionLimit - initialMessageLimit;
125125+ for(int i=fetchRangeStart - 1; i >= 0; --i) {
126126+ if(additionalMessageLimit > 0) {
127127+ additionalMessageLimit--;
128128+ }
129129+ else {
130130+ // Beyond the limit, add these back to the orphan set
131131+ FolderMessage message = (FolderMessage)cachedIndexToMessageMap.get(indexVector.elementAt(i));
132132+ if(message != null) {
133133+ loadedMessageMap.put(message.getMessageToken().getMessageUid(), message);
134134+ }
135135+ }
136136+ }
137137+ removeOrphanedMessages();
138138+139139+ // Do the final request for missing messages
140140+ if(messagesToFetch.size() > 0) {
141141+ fetchMessageSetByIndices(incomingClient, messagesToFetch.toArray());
142142+ }
143143+144144+ loadedMessageMap.clear();
145145+ fireMailStoreRequestComplete();
146146+ }
147147+148148+ private void notifyMessageFlagUpdates(Vector messagesUpdated) {
149149+ if(!messagesUpdated.isEmpty()) {
150150+ FolderMessage[] messages = new FolderMessage[messagesUpdated.size()];
151151+ messagesUpdated.copyInto(messages);
152152+ mailStore.fireFolderMessagesAvailable(folder, messages, true);
153153+ }
154154+ }
155155+156156+ private void removeOrphanedMessages() {
157157+ Enumeration e = loadedMessageMap.elements();
158158+ MessageToken[] orphanedTokens = new MessageToken[loadedMessageMap.size()];
159159+ int index = 0;
160160+ while(e.hasMoreElements()) {
161161+ FolderMessage message = (FolderMessage)e.nextElement();
162162+ orphanedTokens[index++] = message.getMessageToken();
163163+ }
164164+ loadedMessageMap.clear();
165165+ mailStore.fireFolderExpunged(folder, orphanedTokens, new MessageToken[0]);
166166+ }
167167+168168+ private void fetchMessageSetByIndices(PopClient incomingClient, int[] messageIndices) throws IOException, MailException {
169169+ GetFolderMessageCallback clientCallback = new GetFolderMessageCallback();
170170+ incomingClient.getFolderMessages(
171171+ messageIndices,
172172+ clientCallback,
173173+ getProgressHandler(statusMessage));
174174+ }
175175+176176+ private class GetFolderMessageCallback implements FolderMessageCallback {
177177+ public void folderMessageUpdate(FolderMessage folderMessage) {
178178+ if(folderMessage != null) {
179179+ mailStore.fireFolderMessagesAvailable(folder, new FolderMessage[] { folderMessage }, false);
180180+ }
181181+ else {
182182+ // This is the last update of the sequence
183183+ mailStore.fireFolderMessagesAvailable(folder, null, false);
184184+ }
185185+ }
186186+ };
187187+}
···3131package org.logicprobe.LogicMail.model;
32323333import java.util.Date;
3434-import java.util.Enumeration;
3535-import java.util.Hashtable;
3634import java.util.Vector;
37353836import org.logicprobe.LogicMail.conf.MailSettings;
···8179 private final Vector postRefreshTasks = new Vector();
82808381 /** Indicates that the initial refresh has completed. */
8484- protected volatile boolean initialRefreshComplete;
8282+ private volatile boolean initialRefreshComplete;
85838684 /** Indicates that cached messages have been loaded. */
8785 private boolean cacheLoaded;
88868987 /**
9090- * Set of messages that have been loaded from the cache, but no longer
9191- * exist on the server.
9292- */
9393- protected final Hashtable orphanedMessageSet = new Hashtable();
9494-9595- /**
9688 * Set if the mail store is disconnected, to indicate that local state
9789 * should be cleared prior to the next refresh request.
9890 */
9999- protected volatile boolean cleanPriorToUse;
9191+ private volatile boolean cleanPriorToUse;
1009210193 /**
10294 * Tasks that need to be executed after a folder refresh should subclass
···148140 if(cleanPriorToUse) {
149141 refreshInProgressDeliberate = true;
150142 initialRefreshComplete = false;
151151- orphanedMessageSet.clear();
152143 cleanPriorToUse = false;
153144 }
154145 }
···192183 * operation. Upon completion of the operation, regardless of outcome,
193184 * {@link #endFolderRefreshOperation()} must be called.
194185 */
195195- protected abstract void beginFolderRefreshOperation();
186186+ protected void beginFolderRefreshOperation() {
187187+ if(mailStore.hasLockedFolders() && initialRefreshComplete) {
188188+ // Subsequent refresh is pointless on locked-folder mail stores
189189+ endFolderRefreshOperation(true);
190190+ return;
191191+ }
192192+193193+ mailStoreServices.invokeLater(new Runnable() { public void run() {
194194+ FolderMessage[] cacheLoadedMessages;
195195+ if(!initialRefreshComplete) {
196196+ // Fetch messages stored in cache
197197+ cacheLoadedMessages = loadCachedFolderMessages();
198198+ }
199199+ else {
200200+ cacheLoadedMessages = null;
201201+ }
202202+203203+ MailStoreRequest request = mailStore.createFolderRefreshRequest(folderTreeItem, cacheLoadedMessages);
204204+ request.setRequestCallback(finalFetchCallback);
205205+ processMailStoreRequest(request);
206206+ }});
207207+ }
196208197209 /**
198210 * This method should be called upon the completion of a folder refresh.
···208220 // queue, to make sure that they happen after the completion of any
209221 // other refresh-related code.
210222 mailStoreServices.invokeLater(new Runnable() { public void run() {
211211- // Clear the set of loaded messages that have not yet been reconciled,
212212- // which should only be non-empty if this method was called due to an
213213- // error.
214214- orphanedMessageSet.clear();
215215-216223 // Commit the folder message cache
217224 folderMessageCache.commit();
218225···233240 * events are fired to notify listeners of the messages. The load order is
234241 * determined by the global message display order setting.
235242 */
236236- protected void loadCachedFolderMessages() {
243243+ protected FolderMessage[] loadCachedFolderMessages() {
237244 boolean dispOrder = MailSettings.getInstance().getGlobalConfig().getDispOrder();
238245 FolderMessage[] messages = folderMessageCache.getFolderMessages(folderTreeItem);
239246 if(messages.length > 0) {
···245252 // it cannot be considered recent. This is done to prevent
246253 // redundant new message notifications.
247254 messages[i].getFlags().setRecent(false);
248248-249249- orphanedMessageSet.put(messages[i].getMessageToken().getMessageUid(), messages[i]);
250255 }
251256252257 // If the cached messages have already been loaded, then we can
···276281 cacheLoaded = true;
277282 }
278283 }
284284+ return messages;
279285 }
280286281281- /**
282282- * Removes any messages stored in the <code>orphanedMessageSet</code> from
283283- * the cache, clears the set, and then fires the necessary events to have
284284- * them expunged by any listeners.
285285- */
286286- protected void removeOrphanedMessages() {
287287- Enumeration e = orphanedMessageSet.elements();
288288- MessageToken[] orphanedTokens = new MessageToken[orphanedMessageSet.size()];
289289- int index = 0;
290290- while(e.hasMoreElements()) {
291291- FolderMessage message = (FolderMessage)e.nextElement();
292292- folderMessageCache.removeFolderMessage(folderTreeItem, message);
293293- orphanedTokens[index++] = message.getMessageToken();
294294- }
295295- orphanedMessageSet.clear();
296296- mailStoreServices.fireFolderExpunged(folderTreeItem, orphanedTokens, new MessageToken[0]);
297297- }
298298-299287 public void requestMoreFolderMessages(MessageToken firstToken, int increment) {
300288 processMailStoreRequest(mailStore.createFolderMessagesRangeRequest(folderTreeItem, firstToken, increment)
301289 .setRequestCallback(new MailStoreRequestCallback() {
···593581 * refresh process. It will set the <code>initialRefreshComplete</code>
594582 * flag prior to cleanup.
595583 */
596596- protected MailStoreRequestCallback finalFetchCallback = new FolderRefreshRequestCallback() {
584584+ private MailStoreRequestCallback finalFetchCallback = new MailStoreRequestCallback() {
597585 public void mailStoreRequestComplete(MailStoreRequest request) {
598586 initialRefreshComplete = true;
599587 endFolderRefreshOperation(true);
600588 }
601601- };
602602-603603- /**
604604- * Standard callback used by all requests that are part of the folder
605605- * refresh process.
606606- */
607607- protected abstract class FolderRefreshRequestCallback implements MailStoreRequestCallback {
608589 public void mailStoreRequestFailed(MailStoreRequest request, Throwable exception, boolean isFinal) {
609590 // All folder refresh request failures are handled by cleanly
610591 // ending the refresh process.
611592 endFolderRefreshOperation(false);
612593 }
613613- }
594594+ };
614595}