git clone of logicmail with some fixes/features added
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Refactored the folder refresh processes from awkward services-layer implementations into direct mail store request implementations.

git-svn-id: https://logicmail.svn.sourceforge.net/svnroot/logicmail/trunk@950 5c734088-3d25-0410-9155-b3c3832efda5

octorian 8b236e91 4123eb71

+513 -622
-20
LogicMail/src/org/logicprobe/LogicMail/mail/AbstractMailStore.java
··· 33 33 34 34 import java.util.Date; 35 35 36 - import net.rim.device.api.util.ToIntHashtable; 37 - 38 36 import org.logicprobe.LogicMail.message.FolderMessage; 39 37 import org.logicprobe.LogicMail.message.MimeMessageContent; 40 38 import org.logicprobe.LogicMail.message.MessageFlags; ··· 532 530 e = new FolderMessagesEvent(this, folder, messages, flagsOnly); 533 531 } 534 532 ((FolderListener)listeners[i]).folderMessagesAvailable(e); 535 - } 536 - } 537 - 538 - /** 539 - * Notifies all registered <tt>FolderListener</tt>s that 540 - * the UID-to-message index map for a folder has been loaded. 541 - * 542 - * @param folder The folder which has an updated map 543 - * @param uidIndexMap The UID-to-index map for the folder's messages 544 - */ 545 - protected void fireFolderMessageIndexMapAvailable(FolderTreeItem folder, ToIntHashtable uidIndexMap) { 546 - Object[] listeners = listenerList.getListeners(FolderListener.class); 547 - FolderMessageIndexMapEvent e = null; 548 - for(int i=0; i<listeners.length; i++) { 549 - if(e == null) { 550 - e = new FolderMessageIndexMapEvent(this, folder, uidIndexMap); 551 - } 552 - ((FolderListener)listeners[i]).folderMessageIndexMapAvailable(e); 553 533 } 554 534 } 555 535
-11
LogicMail/src/org/logicprobe/LogicMail/mail/FolderListener.java
··· 59 59 public void folderExpunged(FolderExpungedEvent e); 60 60 61 61 /** 62 - * Invoked when the message index-to-UID map for a folder has been loaded. 63 - * <p> 64 - * This listener method exists to support the <code>MailStoreServices</code> 65 - * layer, and should be cleaned up in a future refactoring. 66 - * </p> 67 - * 68 - * @param e Folder event data 69 - */ 70 - public void folderMessageIndexMapAvailable(FolderMessageIndexMapEvent e); 71 - 72 - /** 73 62 * Invoked when the folder state has changed significantly enough that a 74 63 * refresh operation is required to reliably maintain the folder selection. 75 64 * <p>
-51
LogicMail/src/org/logicprobe/LogicMail/mail/FolderMessageIndexMapEvent.java
··· 1 - /*- 2 - * Copyright (c) 2010, Derek Konigsberg 3 - * All rights reserved. 4 - * 5 - * Redistribution and use in source and binary forms, with or without 6 - * modification, are permitted provided that the following conditions 7 - * are met: 8 - * 9 - * 1. Redistributions of source code must retain the above copyright 10 - * notice, this list of conditions and the following disclaimer. 11 - * 2. Redistributions in binary form must reproduce the above copyright 12 - * notice, this list of conditions and the following disclaimer in the 13 - * documentation and/or other materials provided with the distribution. 14 - * 3. Neither the name of the project nor the names of its 15 - * contributors may be used to endorse or promote products derived 16 - * from this software without specific prior written permission. 17 - * 18 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 29 - * OF THE POSSIBILITY OF SUCH DAMAGE. 30 - */ 31 - package org.logicprobe.LogicMail.mail; 32 - 33 - import net.rim.device.api.util.ToIntHashtable; 34 - 35 - public class FolderMessageIndexMapEvent extends FolderEvent { 36 - private final ToIntHashtable uidIndexMap; 37 - 38 - public FolderMessageIndexMapEvent(Object source, FolderTreeItem folder, ToIntHashtable uidIndexMap) { 39 - super(source, folder); 40 - this.uidIndexMap = uidIndexMap; 41 - } 42 - 43 - /** 44 - * Gets the message UID-to-index map that was retrieved. 45 - * 46 - * @return the UID-to-index map, with UIDs in <code>String</code> form. 47 - */ 48 - public ToIntHashtable getUidIndexMap() { 49 - return uidIndexMap; 50 - } 51 - }
+271
LogicMail/src/org/logicprobe/LogicMail/mail/ImapFolderRefreshRequest.java
··· 1 + /*- 2 + * Copyright (c) 2011, Derek Konigsberg 3 + * All rights reserved. 4 + * 5 + * Redistribution and use in source and binary forms, with or without 6 + * modification, are permitted provided that the following conditions 7 + * are met: 8 + * 9 + * 1. Redistributions of source code must retain the above copyright 10 + * notice, this list of conditions and the following disclaimer. 11 + * 2. Redistributions in binary form must reproduce the above copyright 12 + * notice, this list of conditions and the following disclaimer in the 13 + * documentation and/or other materials provided with the distribution. 14 + * 3. Neither the name of the project nor the names of its 15 + * contributors may be used to endorse or promote products derived 16 + * from this software without specific prior written permission. 17 + * 18 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 29 + * OF THE POSSIBILITY OF SUCH DAMAGE. 30 + */ 31 + package org.logicprobe.LogicMail.mail; 32 + 33 + import java.io.IOException; 34 + import java.util.Enumeration; 35 + import java.util.Hashtable; 36 + import java.util.Vector; 37 + 38 + import net.rim.device.api.collection.util.BigVector; 39 + import net.rim.device.api.util.Comparator; 40 + 41 + import org.logicprobe.LogicMail.LogicMailResource; 42 + import org.logicprobe.LogicMail.mail.imap.ImapClient; 43 + import org.logicprobe.LogicMail.message.FolderMessage; 44 + 45 + class ImapFolderRefreshRequest extends NetworkMailStoreRequest implements MailStoreRequest { 46 + private final String statusMessage; 47 + private final FolderTreeItem folder; 48 + private final Hashtable loadedMessageMap; 49 + private int messageRetentionLimit; 50 + private volatile boolean checkAllTokens; 51 + private Vector secondaryMessageTokensToFetch; 52 + 53 + ImapFolderRefreshRequest(NetworkMailStore mailStore, FolderTreeItem folder, FolderMessage[] loadedMessages) { 54 + super(mailStore); 55 + this.statusMessage = resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES); 56 + this.folder = folder; 57 + this.loadedMessageMap = new Hashtable(); 58 + if(loadedMessages != null) { 59 + for(int i=0; i<loadedMessages.length; i++) { 60 + loadedMessageMap.put( 61 + loadedMessages[i].getMessageToken().getMessageUid(), 62 + loadedMessages[i]); 63 + } 64 + } 65 + } 66 + 67 + private FolderListener folderListener = new FolderListener() { 68 + public void folderMessagesAvailable(FolderMessagesEvent e) { } 69 + public void folderStatusChanged(FolderEvent e) { } 70 + public void folderExpunged(FolderExpungedEvent e) { } 71 + public void folderRefreshRequired(FolderEvent e) { 72 + if(e.getFolder().equals(folder)){ 73 + checkAllTokens = true; 74 + } 75 + } 76 + }; 77 + 78 + protected String getInitialStatus() { 79 + return statusMessage + "..."; 80 + } 81 + 82 + public void execute(MailClient client) throws IOException, MailException { 83 + ImapClient incomingClient = (ImapClient)client; 84 + 85 + mailStore.addFolderListener(folderListener); 86 + 87 + this.messageRetentionLimit = incomingClient.getAcctConfig().getMaximumFolderMessages(); 88 + 89 + checkActiveFolder(incomingClient, folder); 90 + 91 + // Fetch new folder messages from the mail store 92 + Vector folderMessages = new Vector(); 93 + incomingClient.getNewFolderMessages( 94 + true, 95 + new GetFolderMessageCallback(folderMessages, true), 96 + getProgressHandler(statusMessage)); 97 + 98 + initialFlagsRefreshComplete(incomingClient, folderMessages); 99 + } 100 + 101 + protected void fireMailStoreRequestComplete() { 102 + mailStore.removeFolderListener(folderListener); 103 + super.fireMailStoreRequestComplete(); 104 + } 105 + 106 + protected void fireMailStoreRequestFailed(Throwable exception, boolean isFinal) { 107 + mailStore.removeFolderListener(folderListener); 108 + super.fireMailStoreRequestFailed(exception, isFinal); 109 + } 110 + 111 + private void initialFlagsRefreshComplete(ImapClient incomingClient, Vector pendingFlagUpdates) throws IOException, MailException { 112 + secondaryMessageTokensToFetch = new Vector(); 113 + MessageToken oldestFetchedToken = null; 114 + Comparator tokenComparator = null; 115 + 116 + // Iterate through the pending flag updates, doing the following: 117 + // - Remove messages from the orphan set that exist on the server 118 + // - Update the cache for fetched messages 119 + // - Build a collection of messages to provide update notifications for 120 + // - Build a collection of messages that need to be fetched 121 + // - Keep track of the oldest message in the update set 122 + int size = pendingFlagUpdates.size(); 123 + for(int i=0; i<size; i++) { 124 + FolderMessage message = (FolderMessage)pendingFlagUpdates.elementAt(i); 125 + MessageToken token = message.getMessageToken(); 126 + 127 + // Remove messages with received flag updates from the orphan set 128 + if(loadedMessageMap.remove(token.getMessageUid()) == null) { 129 + // Anything that wasn't in the set needs to be fetched 130 + secondaryMessageTokensToFetch.addElement(token); 131 + } 132 + 133 + if(oldestFetchedToken == null) { 134 + oldestFetchedToken = token; 135 + tokenComparator = token.getComparator(); 136 + } 137 + else { 138 + if(tokenComparator.compare(token, oldestFetchedToken) < 0) { 139 + oldestFetchedToken = token; 140 + } 141 + } 142 + } 143 + messageRetentionLimit -= size; 144 + pendingFlagUpdates.removeAllElements(); 145 + 146 + // Build a collection of messages in the cache that still need to be verified 147 + Vector cachedTokensToCheck = new Vector(); 148 + if(checkAllTokens) { 149 + for(Enumeration e = loadedMessageMap.elements(); e.hasMoreElements() ;) { 150 + MessageToken token = ((FolderMessage)e.nextElement()).getMessageToken(); 151 + cachedTokensToCheck.addElement(token); 152 + } 153 + } 154 + else if(oldestFetchedToken != null) { 155 + for(Enumeration e = loadedMessageMap.elements(); e.hasMoreElements() ;) { 156 + MessageToken token = ((FolderMessage)e.nextElement()).getMessageToken(); 157 + if(tokenComparator.compare(token, oldestFetchedToken) < 0) { 158 + cachedTokensToCheck.addElement(token); 159 + } 160 + } 161 + } 162 + checkAllTokens = false; 163 + 164 + if(cachedTokensToCheck.size() > 0 && messageRetentionLimit > 0) { 165 + // Perform a second flags fetch 166 + MessageToken[] tokens = new MessageToken[cachedTokensToCheck.size()]; 167 + cachedTokensToCheck.copyInto(tokens); 168 + 169 + Vector folderMessages = new Vector(); 170 + incomingClient.getFolderMessages( 171 + tokens, 172 + true, 173 + new GetFolderMessageCallback(folderMessages, true), 174 + getProgressHandler(statusMessage)); 175 + 176 + secondaryFlagsRefreshComplete(incomingClient, folderMessages); 177 + } 178 + else { 179 + removeOrphanedMessages(); 180 + finalFolderMessageFetch(incomingClient); 181 + } 182 + } 183 + 184 + private void secondaryFlagsRefreshComplete(ImapClient incomingClient, Vector pendingFlagUpdates) throws IOException, MailException { 185 + int size = pendingFlagUpdates.size(); 186 + BigVector messagesUpdated = new BigVector(size); 187 + Comparator folderMessageComparator = FolderMessage.getComparator(); 188 + for(int i=0; i<size; i++) { 189 + FolderMessage message = (FolderMessage)pendingFlagUpdates.elementAt(i); 190 + MessageToken token = message.getMessageToken(); 191 + 192 + // Remove messages with received flag updates from the orphan set 193 + if(loadedMessageMap.remove(token.getMessageUid()) != null) { 194 + // If it was removed from the set, then we assume it exists 195 + // in the cache and will have been updated accordingly. 196 + messagesUpdated.insertElement(folderMessageComparator, message); 197 + } 198 + } 199 + pendingFlagUpdates.removeAllElements(); 200 + 201 + // Determine the how many messages from this secondary set we can keep 202 + size = messagesUpdated.size(); 203 + if(size > messageRetentionLimit) { 204 + // We have too many additional messages, so we need to prune the set 205 + messagesUpdated.optimize(); 206 + 207 + int splitIndex = messagesUpdated.size() - messageRetentionLimit; 208 + for(int i=0; i<splitIndex; i++) { 209 + FolderMessage message = (FolderMessage)messagesUpdated.elementAt(i); 210 + loadedMessageMap.put(message.getMessageToken().getMessageUid(), message); 211 + } 212 + } 213 + 214 + removeOrphanedMessages(); 215 + finalFolderMessageFetch(incomingClient); 216 + } 217 + 218 + private void finalFolderMessageFetch(ImapClient incomingClient) throws IOException, MailException { 219 + // Queue a fetch for messages missing from the cache 220 + if(!secondaryMessageTokensToFetch.isEmpty()) { 221 + MessageToken[] fetchArray = new MessageToken[secondaryMessageTokensToFetch.size()]; 222 + secondaryMessageTokensToFetch.copyInto(fetchArray); 223 + secondaryMessageTokensToFetch.removeAllElements(); 224 + 225 + incomingClient.getFolderMessages( 226 + fetchArray, 227 + false, 228 + new GetFolderMessageCallback(false), 229 + getProgressHandler(statusMessage)); 230 + } 231 + loadedMessageMap.clear(); 232 + fireMailStoreRequestComplete(); 233 + } 234 + 235 + private void removeOrphanedMessages() { 236 + Enumeration e = loadedMessageMap.elements(); 237 + MessageToken[] orphanedTokens = new MessageToken[loadedMessageMap.size()]; 238 + int index = 0; 239 + while(e.hasMoreElements()) { 240 + FolderMessage message = (FolderMessage)e.nextElement(); 241 + orphanedTokens[index++] = message.getMessageToken(); 242 + } 243 + loadedMessageMap.clear(); 244 + mailStore.fireFolderExpunged(folder, orphanedTokens, new MessageToken[0]); 245 + } 246 + 247 + private class GetFolderMessageCallback implements FolderMessageCallback { 248 + private final Vector folderMessages; 249 + private final boolean flagsOnly; 250 + public GetFolderMessageCallback(Vector folderMessages, boolean flagsOnly) { 251 + this.folderMessages = folderMessages; 252 + this.flagsOnly = flagsOnly; 253 + } 254 + public GetFolderMessageCallback(boolean flagsOnly) { 255 + this(null, flagsOnly); 256 + } 257 + 258 + public void folderMessageUpdate(FolderMessage folderMessage) { 259 + if(folderMessage != null) { 260 + if(folderMessages != null) { 261 + folderMessages.addElement(folderMessage); 262 + } 263 + mailStore.fireFolderMessagesAvailable(folder, new FolderMessage[] { folderMessage }, flagsOnly); 264 + } 265 + else { 266 + // This is the last update of the sequence 267 + mailStore.fireFolderMessagesAvailable(folder, null, flagsOnly); 268 + } 269 + } 270 + }; 271 + }
-72
LogicMail/src/org/logicprobe/LogicMail/mail/NetworkFolderMessageIndexMapRequest.java
··· 1 - /*- 2 - * Copyright (c) 2011, Derek Konigsberg 3 - * All rights reserved. 4 - * 5 - * Redistribution and use in source and binary forms, with or without 6 - * modification, are permitted provided that the following conditions 7 - * are met: 8 - * 9 - * 1. Redistributions of source code must retain the above copyright 10 - * notice, this list of conditions and the following disclaimer. 11 - * 2. Redistributions in binary form must reproduce the above copyright 12 - * notice, this list of conditions and the following disclaimer in the 13 - * documentation and/or other materials provided with the distribution. 14 - * 3. Neither the name of the project nor the names of its 15 - * contributors may be used to endorse or promote products derived 16 - * from this software without specific prior written permission. 17 - * 18 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 29 - * OF THE POSSIBILITY OF SUCH DAMAGE. 30 - */ 31 - 32 - package org.logicprobe.LogicMail.mail; 33 - 34 - import java.io.IOException; 35 - 36 - import net.rim.device.api.util.ToIntHashtable; 37 - 38 - import org.logicprobe.LogicMail.LogicMailResource; 39 - 40 - public class NetworkFolderMessageIndexMapRequest extends NetworkMailStoreRequest implements MailStoreRequest { 41 - private final FolderTreeItem folder; 42 - private ToIntHashtable resultUidIndexMap; 43 - 44 - NetworkFolderMessageIndexMapRequest(NetworkMailStore mailStore, FolderTreeItem folder) { 45 - super(mailStore); 46 - this.folder = folder; 47 - } 48 - 49 - public FolderTreeItem getFolder() { 50 - return folder; 51 - } 52 - 53 - protected String getInitialStatus() { 54 - return resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES) + "..."; 55 - } 56 - 57 - public void execute(MailClient client) throws IOException, MailException { 58 - IncomingMailClient incomingClient = (IncomingMailClient)client; 59 - 60 - checkActiveFolder(incomingClient, folder); 61 - 62 - String message = resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES); 63 - resultUidIndexMap = incomingClient.getFolderMessageIndexMap(getProgressHandler(message)); 64 - 65 - fireMailStoreRequestComplete(); 66 - mailStore.fireFolderMessageIndexMapAvailable(folder, resultUidIndexMap); 67 - } 68 - 69 - public ToIntHashtable getResultUidIndexMap() { 70 - return resultUidIndexMap; 71 - } 72 - }
+23 -10
LogicMail/src/org/logicprobe/LogicMail/mail/NetworkMailStore.java
··· 36 36 import net.rim.device.api.system.UnsupportedOperationException; 37 37 38 38 import org.logicprobe.LogicMail.conf.AccountConfig; 39 + import org.logicprobe.LogicMail.mail.imap.ImapClient; 40 + import org.logicprobe.LogicMail.mail.pop.PopClient; 41 + import org.logicprobe.LogicMail.message.FolderMessage; 39 42 import org.logicprobe.LogicMail.message.MessageFlags; 40 43 import org.logicprobe.LogicMail.message.MimeMessagePart; 41 44 ··· 212 215 } 213 216 214 217 /** 215 - * Requests the message UID-to-index map for a particular folder. 216 - * <p> 217 - * Successful completion is indicated by a call to 218 - * {@link FolderListener#folderMessageIndexMapAvailable(FolderMessageIndexMapEvent)}. 219 - * </p> 220 - * 221 - * @param folder The folder to request a message listing for. 218 + * Creates a request to synchronize a locally cached folder with data from 219 + * the mail server. This is a complex request with protocol-dependent 220 + * implementations, and is intended to be called from within cache-handling 221 + * code. 222 + * 223 + * @param folder The folder to refresh. 224 + * @param loadedMessages Collection of {@link FolderMessage} objects 225 + * for messages that have already been loaded for this folder prior to 226 + * the start of the refresh operation. 227 + * @return the request object 222 228 */ 223 - public NetworkFolderMessageIndexMapRequest requestFolderMessageIndexMap(FolderTreeItem folder) { 224 - NetworkFolderMessageIndexMapRequest request = new NetworkFolderMessageIndexMapRequest(this, folder); 225 - return request; 229 + public MailStoreRequest createFolderRefreshRequest(FolderTreeItem folder, FolderMessage[] loadedMessages) { 230 + if(client instanceof ImapClient) { 231 + return new ImapFolderRefreshRequest(this, folder, loadedMessages); 232 + } 233 + else if(client instanceof PopClient) { 234 + return new PopFolderRefreshRequest(this, folder, loadedMessages); 235 + } 236 + else { 237 + throw new UnsupportedOperationException(); 238 + } 226 239 } 227 240 228 241 public MessageRequest createMessageRequest(MessageToken messageToken, boolean useLimits) {
+187
LogicMail/src/org/logicprobe/LogicMail/mail/PopFolderRefreshRequest.java
··· 1 + /*- 2 + * Copyright (c) 2011, Derek Konigsberg 3 + * All rights reserved. 4 + * 5 + * Redistribution and use in source and binary forms, with or without 6 + * modification, are permitted provided that the following conditions 7 + * are met: 8 + * 9 + * 1. Redistributions of source code must retain the above copyright 10 + * notice, this list of conditions and the following disclaimer. 11 + * 2. Redistributions in binary form must reproduce the above copyright 12 + * notice, this list of conditions and the following disclaimer in the 13 + * documentation and/or other materials provided with the distribution. 14 + * 3. Neither the name of the project nor the names of its 15 + * contributors may be used to endorse or promote products derived 16 + * from this software without specific prior written permission. 17 + * 18 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 27 + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 29 + * OF THE POSSIBILITY OF SUCH DAMAGE. 30 + */ 31 + package org.logicprobe.LogicMail.mail; 32 + 33 + import java.io.IOException; 34 + import java.util.Enumeration; 35 + import java.util.Hashtable; 36 + import java.util.Vector; 37 + 38 + import net.rim.device.api.util.IntHashtable; 39 + import net.rim.device.api.util.IntVector; 40 + import net.rim.device.api.util.SimpleSortingIntVector; 41 + import net.rim.device.api.util.ToIntHashtable; 42 + 43 + import org.logicprobe.LogicMail.LogicMailResource; 44 + import org.logicprobe.LogicMail.mail.pop.PopClient; 45 + import org.logicprobe.LogicMail.message.FolderMessage; 46 + 47 + class PopFolderRefreshRequest extends NetworkMailStoreRequest implements MailStoreRequest { 48 + private final String statusMessage; 49 + private final FolderTreeItem folder; 50 + private final Hashtable loadedMessageMap; 51 + 52 + PopFolderRefreshRequest(NetworkMailStore mailStore, FolderTreeItem folder, FolderMessage[] loadedMessages) { 53 + super(mailStore); 54 + this.statusMessage = resources.getString(LogicMailResource.MAILCONNECTION_REQUEST_FOLDER_MESSAGES); 55 + this.folder = folder; 56 + this.loadedMessageMap = new Hashtable(); 57 + if(loadedMessages != null) { 58 + for(int i=0; i<loadedMessages.length; i++) { 59 + loadedMessageMap.put( 60 + loadedMessages[i].getMessageToken().getMessageUid(), 61 + loadedMessages[i]); 62 + } 63 + } 64 + } 65 + 66 + protected String getInitialStatus() { 67 + return statusMessage + "..."; 68 + } 69 + 70 + public void execute(MailClient client) throws IOException, MailException { 71 + PopClient incomingClient = (PopClient)client; 72 + 73 + checkActiveFolder(incomingClient, folder); 74 + 75 + // Get the Index-to-UID map for the folder 76 + ToIntHashtable uidIndexMap = incomingClient.getFolderMessageIndexMap(getProgressHandler(statusMessage)); 77 + 78 + // Get configuration values that affect the rest of the process 79 + int initialMessageLimit = incomingClient.getAcctConfig().getInitialFolderMessages(); 80 + int messageRetentionLimit = incomingClient.getAcctConfig().getMaximumFolderMessages(); 81 + 82 + // Initialize collections used during processing 83 + Vector messagesUpdated = new Vector(); 84 + SimpleSortingIntVector indexVector = new SimpleSortingIntVector(); 85 + IntHashtable cachedIndexToMessageMap = new IntHashtable(); 86 + 87 + // Iterate through the UID-to-index map, and do the following: 88 + // - Remove cache-loaded messages from the orphan set if they exist on the server. 89 + // - Update index information for those messages that do still exist server-side. 90 + // - Build a sortable vector of index values 91 + Enumeration e = uidIndexMap.keys(); 92 + while(e.hasMoreElements()) { 93 + String uid = (String)e.nextElement(); 94 + int index = uidIndexMap.get(uid); 95 + indexVector.addElement(index); 96 + 97 + FolderMessage message = (FolderMessage)loadedMessageMap.remove(uid); 98 + if(message != null) { 99 + message.setIndex(index); 100 + message.getMessageToken().updateMessageIndex(index); 101 + messagesUpdated.addElement(message); 102 + cachedIndexToMessageMap.put(index, message); 103 + } 104 + } 105 + indexVector.reSort(SimpleSortingIntVector.SORT_TYPE_NUMERIC); 106 + 107 + notifyMessageFlagUpdates(messagesUpdated); 108 + removeOrphanedMessages(); 109 + 110 + // Determine the fetch range 111 + int size = indexVector.size(); 112 + if(size == 0) { return; } 113 + int fetchRangeStart = Math.max(0, size - initialMessageLimit); 114 + 115 + // Build a list of indices to fetch 116 + IntVector messagesToFetch = new IntVector(); 117 + for(int i=indexVector.size() - 1; i >= fetchRangeStart; --i) { 118 + int index = indexVector.elementAt(i); 119 + if(!cachedIndexToMessageMap.containsKey(index)) { 120 + messagesToFetch.addElement(index); 121 + } 122 + } 123 + 124 + int additionalMessageLimit = messageRetentionLimit - initialMessageLimit; 125 + for(int i=fetchRangeStart - 1; i >= 0; --i) { 126 + if(additionalMessageLimit > 0) { 127 + additionalMessageLimit--; 128 + } 129 + else { 130 + // Beyond the limit, add these back to the orphan set 131 + FolderMessage message = (FolderMessage)cachedIndexToMessageMap.get(indexVector.elementAt(i)); 132 + if(message != null) { 133 + loadedMessageMap.put(message.getMessageToken().getMessageUid(), message); 134 + } 135 + } 136 + } 137 + removeOrphanedMessages(); 138 + 139 + // Do the final request for missing messages 140 + if(messagesToFetch.size() > 0) { 141 + fetchMessageSetByIndices(incomingClient, messagesToFetch.toArray()); 142 + } 143 + 144 + loadedMessageMap.clear(); 145 + fireMailStoreRequestComplete(); 146 + } 147 + 148 + private void notifyMessageFlagUpdates(Vector messagesUpdated) { 149 + if(!messagesUpdated.isEmpty()) { 150 + FolderMessage[] messages = new FolderMessage[messagesUpdated.size()]; 151 + messagesUpdated.copyInto(messages); 152 + mailStore.fireFolderMessagesAvailable(folder, messages, true); 153 + } 154 + } 155 + 156 + private void removeOrphanedMessages() { 157 + Enumeration e = loadedMessageMap.elements(); 158 + MessageToken[] orphanedTokens = new MessageToken[loadedMessageMap.size()]; 159 + int index = 0; 160 + while(e.hasMoreElements()) { 161 + FolderMessage message = (FolderMessage)e.nextElement(); 162 + orphanedTokens[index++] = message.getMessageToken(); 163 + } 164 + loadedMessageMap.clear(); 165 + mailStore.fireFolderExpunged(folder, orphanedTokens, new MessageToken[0]); 166 + } 167 + 168 + private void fetchMessageSetByIndices(PopClient incomingClient, int[] messageIndices) throws IOException, MailException { 169 + GetFolderMessageCallback clientCallback = new GetFolderMessageCallback(); 170 + incomingClient.getFolderMessages( 171 + messageIndices, 172 + clientCallback, 173 + getProgressHandler(statusMessage)); 174 + } 175 + 176 + private class GetFolderMessageCallback implements FolderMessageCallback { 177 + public void folderMessageUpdate(FolderMessage folderMessage) { 178 + if(folderMessage != null) { 179 + mailStore.fireFolderMessagesAvailable(folder, new FolderMessage[] { folderMessage }, false); 180 + } 181 + else { 182 + // This is the last update of the sequence 183 + mailStore.fireFolderMessagesAvailable(folder, null, false); 184 + } 185 + } 186 + }; 187 + }
-2
LogicMail/src/org/logicprobe/LogicMail/model/AccountNode.java
··· 33 33 import org.logicprobe.LogicMail.mail.FolderEvent; 34 34 import org.logicprobe.LogicMail.mail.FolderExpungedEvent; 35 35 import org.logicprobe.LogicMail.mail.FolderListener; 36 - import org.logicprobe.LogicMail.mail.FolderMessageIndexMapEvent; 37 36 import org.logicprobe.LogicMail.mail.FolderMessagesEvent; 38 37 import org.logicprobe.LogicMail.mail.FolderTreeItem; 39 38 import org.logicprobe.LogicMail.mail.MailStoreEvent; ··· 107 106 mailboxNode.mailStoreFolderExpunged(e); 108 107 } 109 108 110 - public void folderMessageIndexMapAvailable(FolderMessageIndexMapEvent e) { } 111 109 public void folderRefreshRequired(FolderEvent e) { } 112 110 }); 113 111
+28 -47
LogicMail/src/org/logicprobe/LogicMail/model/FolderRequestHandler.java
··· 31 31 package org.logicprobe.LogicMail.model; 32 32 33 33 import java.util.Date; 34 - import java.util.Enumeration; 35 - import java.util.Hashtable; 36 34 import java.util.Vector; 37 35 38 36 import org.logicprobe.LogicMail.conf.MailSettings; ··· 81 79 private final Vector postRefreshTasks = new Vector(); 82 80 83 81 /** Indicates that the initial refresh has completed. */ 84 - protected volatile boolean initialRefreshComplete; 82 + private volatile boolean initialRefreshComplete; 85 83 86 84 /** Indicates that cached messages have been loaded. */ 87 85 private boolean cacheLoaded; 88 86 89 87 /** 90 - * Set of messages that have been loaded from the cache, but no longer 91 - * exist on the server. 92 - */ 93 - protected final Hashtable orphanedMessageSet = new Hashtable(); 94 - 95 - /** 96 88 * Set if the mail store is disconnected, to indicate that local state 97 89 * should be cleared prior to the next refresh request. 98 90 */ 99 - protected volatile boolean cleanPriorToUse; 91 + private volatile boolean cleanPriorToUse; 100 92 101 93 /** 102 94 * Tasks that need to be executed after a folder refresh should subclass ··· 148 140 if(cleanPriorToUse) { 149 141 refreshInProgressDeliberate = true; 150 142 initialRefreshComplete = false; 151 - orphanedMessageSet.clear(); 152 143 cleanPriorToUse = false; 153 144 } 154 145 } ··· 192 183 * operation. Upon completion of the operation, regardless of outcome, 193 184 * {@link #endFolderRefreshOperation()} must be called. 194 185 */ 195 - protected abstract void beginFolderRefreshOperation(); 186 + protected void beginFolderRefreshOperation() { 187 + if(mailStore.hasLockedFolders() && initialRefreshComplete) { 188 + // Subsequent refresh is pointless on locked-folder mail stores 189 + endFolderRefreshOperation(true); 190 + return; 191 + } 192 + 193 + mailStoreServices.invokeLater(new Runnable() { public void run() { 194 + FolderMessage[] cacheLoadedMessages; 195 + if(!initialRefreshComplete) { 196 + // Fetch messages stored in cache 197 + cacheLoadedMessages = loadCachedFolderMessages(); 198 + } 199 + else { 200 + cacheLoadedMessages = null; 201 + } 202 + 203 + MailStoreRequest request = mailStore.createFolderRefreshRequest(folderTreeItem, cacheLoadedMessages); 204 + request.setRequestCallback(finalFetchCallback); 205 + processMailStoreRequest(request); 206 + }}); 207 + } 196 208 197 209 /** 198 210 * This method should be called upon the completion of a folder refresh. ··· 208 220 // queue, to make sure that they happen after the completion of any 209 221 // other refresh-related code. 210 222 mailStoreServices.invokeLater(new Runnable() { public void run() { 211 - // Clear the set of loaded messages that have not yet been reconciled, 212 - // which should only be non-empty if this method was called due to an 213 - // error. 214 - orphanedMessageSet.clear(); 215 - 216 223 // Commit the folder message cache 217 224 folderMessageCache.commit(); 218 225 ··· 233 240 * events are fired to notify listeners of the messages. The load order is 234 241 * determined by the global message display order setting. 235 242 */ 236 - protected void loadCachedFolderMessages() { 243 + protected FolderMessage[] loadCachedFolderMessages() { 237 244 boolean dispOrder = MailSettings.getInstance().getGlobalConfig().getDispOrder(); 238 245 FolderMessage[] messages = folderMessageCache.getFolderMessages(folderTreeItem); 239 246 if(messages.length > 0) { ··· 245 252 // it cannot be considered recent. This is done to prevent 246 253 // redundant new message notifications. 247 254 messages[i].getFlags().setRecent(false); 248 - 249 - orphanedMessageSet.put(messages[i].getMessageToken().getMessageUid(), messages[i]); 250 255 } 251 256 252 257 // If the cached messages have already been loaded, then we can ··· 276 281 cacheLoaded = true; 277 282 } 278 283 } 284 + return messages; 279 285 } 280 286 281 - /** 282 - * Removes any messages stored in the <code>orphanedMessageSet</code> from 283 - * the cache, clears the set, and then fires the necessary events to have 284 - * them expunged by any listeners. 285 - */ 286 - protected void removeOrphanedMessages() { 287 - Enumeration e = orphanedMessageSet.elements(); 288 - MessageToken[] orphanedTokens = new MessageToken[orphanedMessageSet.size()]; 289 - int index = 0; 290 - while(e.hasMoreElements()) { 291 - FolderMessage message = (FolderMessage)e.nextElement(); 292 - folderMessageCache.removeFolderMessage(folderTreeItem, message); 293 - orphanedTokens[index++] = message.getMessageToken(); 294 - } 295 - orphanedMessageSet.clear(); 296 - mailStoreServices.fireFolderExpunged(folderTreeItem, orphanedTokens, new MessageToken[0]); 297 - } 298 - 299 287 public void requestMoreFolderMessages(MessageToken firstToken, int increment) { 300 288 processMailStoreRequest(mailStore.createFolderMessagesRangeRequest(folderTreeItem, firstToken, increment) 301 289 .setRequestCallback(new MailStoreRequestCallback() { ··· 593 581 * refresh process. It will set the <code>initialRefreshComplete</code> 594 582 * flag prior to cleanup. 595 583 */ 596 - protected MailStoreRequestCallback finalFetchCallback = new FolderRefreshRequestCallback() { 584 + private MailStoreRequestCallback finalFetchCallback = new MailStoreRequestCallback() { 597 585 public void mailStoreRequestComplete(MailStoreRequest request) { 598 586 initialRefreshComplete = true; 599 587 endFolderRefreshOperation(true); 600 588 } 601 - }; 602 - 603 - /** 604 - * Standard callback used by all requests that are part of the folder 605 - * refresh process. 606 - */ 607 - protected abstract class FolderRefreshRequestCallback implements MailStoreRequestCallback { 608 589 public void mailStoreRequestFailed(MailStoreRequest request, Throwable exception, boolean isFinal) { 609 590 // All folder refresh request failures are handled by cleanly 610 591 // ending the refresh process. 611 592 endFolderRefreshOperation(false); 612 593 } 613 - } 594 + }; 614 595 }
-177
LogicMail/src/org/logicprobe/LogicMail/model/ImapFolderRequestHandler.java
··· 31 31 package org.logicprobe.LogicMail.model; 32 32 33 33 import java.util.Date; 34 - import java.util.Enumeration; 35 34 import java.util.Vector; 36 35 37 - import net.rim.device.api.collection.util.BigVector; 38 - import net.rim.device.api.util.Comparator; 39 - 40 - import org.logicprobe.LogicMail.mail.FolderMessagesRequest; 41 36 import org.logicprobe.LogicMail.mail.FolderTreeItem; 42 - import org.logicprobe.LogicMail.mail.MailStoreRequest; 43 37 import org.logicprobe.LogicMail.mail.MessageToken; 44 38 import org.logicprobe.LogicMail.mail.NetworkMailStore; 45 39 import org.logicprobe.LogicMail.message.FolderMessage; ··· 54 48 * </p> 55 49 */ 56 50 class ImapFolderRequestHandler extends FolderRequestHandler { 57 - /** 58 - * Flag that is set during an existing refresh to force a check of 59 - * all tokens. 60 - */ 61 - private volatile boolean checkAllTokens; 62 - 63 - private Vector secondaryMessageTokensToFetch; 64 - 65 - /** 66 - * Keeps a running total of how many messages we may retain, updated 67 - * throughout the refresh process. 68 - */ 69 - private int messageRetentionLimit; 70 51 71 52 public ImapFolderRequestHandler( 72 53 NetworkMailStoreServices mailStoreServices, ··· 76 57 super(mailStoreServices, mailStore, folderMessageCache, folderTreeItem); 77 58 } 78 59 79 - protected void prepareForUse() { 80 - messageRetentionLimit = mailStore.getAccountConfig().getMaximumFolderMessages(); 81 - secondaryMessageTokensToFetch = null; 82 - super.prepareForUse(); 83 - } 84 - 85 - public void requestFolderRefreshRequired() { 86 - if(refreshInProgress.get()) { 87 - checkAllTokens = true; 88 - } 89 - else { 90 - cleanBeforeNextUse(); 91 - } 92 - } 93 - 94 - protected void beginFolderRefreshOperation() { 95 - mailStoreServices.invokeLater(new Runnable() { public void run() { 96 - if(!initialRefreshComplete) { 97 - // Fetch messages stored in cache 98 - loadCachedFolderMessages(); 99 - } 100 - 101 - // Queue a request for new folder messages from the mail store 102 - processMailStoreRequest(mailStore.createFolderMessagesRecentRequest(folderTreeItem, true) 103 - .setRequestCallback(new FolderRefreshRequestCallback() { 104 - public void mailStoreRequestComplete(MailStoreRequest request) { 105 - FolderMessagesRequest folderMessagesRequest = (FolderMessagesRequest)request; 106 - Vector folderMessages = folderMessagesRequest.getResultFolderMessages(); 107 - initialFlagsRefreshComplete(folderMessages); 108 - } 109 - })); 110 - }}); 111 - } 112 - 113 - private void initialFlagsRefreshComplete(final Vector pendingFlagUpdates) { 114 - mailStoreServices.invokeLater(new Runnable() { public void run() { 115 - secondaryMessageTokensToFetch = new Vector(); 116 - MessageToken oldestFetchedToken = null; 117 - Comparator tokenComparator = null; 118 - 119 - // Iterate through the pending flag updates, doing the following: 120 - // - Remove messages from the orphan set that exist on the server 121 - // - Update the cache for fetched messages 122 - // - Build a collection of messages to provide update notifications for 123 - // - Build a collection of messages that need to be fetched 124 - // - Keep track of the oldest message in the update set 125 - int size = pendingFlagUpdates.size(); 126 - for(int i=0; i<size; i++) { 127 - FolderMessage message = (FolderMessage)pendingFlagUpdates.elementAt(i); 128 - MessageToken token = message.getMessageToken(); 129 - 130 - // Remove messages with received flag updates from the orphan set 131 - orphanedMessageSet.remove(token.getMessageUid()); 132 - 133 - if(!folderMessageCache.updateFolderMessage(folderTreeItem, message)) { 134 - secondaryMessageTokensToFetch.addElement(token); 135 - } 136 - 137 - if(oldestFetchedToken == null) { 138 - oldestFetchedToken = token; 139 - tokenComparator = token.getComparator(); 140 - } 141 - else { 142 - if(tokenComparator.compare(token, oldestFetchedToken) < 0) { 143 - oldestFetchedToken = token; 144 - } 145 - } 146 - } 147 - messageRetentionLimit -= size; 148 - pendingFlagUpdates.removeAllElements(); 149 - 150 - // Build a collection of messages in the cache that still need to be verified 151 - Vector cachedTokensToCheck = new Vector(); 152 - if(checkAllTokens) { 153 - for(Enumeration e = orphanedMessageSet.elements(); e.hasMoreElements() ;) { 154 - MessageToken token = ((FolderMessage)e.nextElement()).getMessageToken(); 155 - cachedTokensToCheck.addElement(token); 156 - } 157 - } 158 - else if(oldestFetchedToken != null) { 159 - for(Enumeration e = orphanedMessageSet.elements(); e.hasMoreElements() ;) { 160 - MessageToken token = ((FolderMessage)e.nextElement()).getMessageToken(); 161 - if(tokenComparator.compare(token, oldestFetchedToken) < 0) { 162 - cachedTokensToCheck.addElement(token); 163 - } 164 - } 165 - } 166 - checkAllTokens = false; 167 - 168 - if(cachedTokensToCheck.size() > 0 && messageRetentionLimit > 0) { 169 - // Perform a second flags fetch 170 - MessageToken[] tokens = new MessageToken[cachedTokensToCheck.size()]; 171 - cachedTokensToCheck.copyInto(tokens); 172 - processMailStoreRequest(mailStore.createFolderMessagesSetRequest(folderTreeItem, tokens, true) 173 - .setRequestCallback(new FolderRefreshRequestCallback() { 174 - public void mailStoreRequestComplete(MailStoreRequest request) { 175 - FolderMessagesRequest folderMessagesRequest = (FolderMessagesRequest)request; 176 - Vector folderMessages = folderMessagesRequest.getResultFolderMessages(); 177 - secondaryFlagsRefreshComplete(folderMessages); 178 - } 179 - })); 180 - } 181 - else { 182 - removeOrphanedMessages(); 183 - finalFolderMessageFetch(); 184 - } 185 - }}); 186 - } 187 - 188 - private void secondaryFlagsRefreshComplete(Vector pendingFlagUpdates) { 189 - int size = pendingFlagUpdates.size(); 190 - BigVector messagesUpdated = new BigVector(size); 191 - Comparator folderMessageComparator = FolderMessage.getComparator(); 192 - for(int i=0; i<size; i++) { 193 - FolderMessage message = (FolderMessage)pendingFlagUpdates.elementAt(i); 194 - MessageToken token = message.getMessageToken(); 195 - 196 - // Remove messages with received flag updates from the orphan set 197 - orphanedMessageSet.remove(token.getMessageUid()); 198 - 199 - if(folderMessageCache.updateFolderMessage(folderTreeItem, message)) { 200 - messagesUpdated.insertElement(folderMessageComparator, message); 201 - } 202 - } 203 - pendingFlagUpdates.removeAllElements(); 204 - 205 - // Determine the how many messages from this secondary set we can keep 206 - size = messagesUpdated.size(); 207 - if(size > messageRetentionLimit) { 208 - // We have too many additional messages, so we need to prune the set 209 - messagesUpdated.optimize(); 210 - 211 - int splitIndex = messagesUpdated.size() - messageRetentionLimit; 212 - for(int i=0; i<splitIndex; i++) { 213 - FolderMessage message = (FolderMessage)messagesUpdated.elementAt(i); 214 - orphanedMessageSet.put(message.getMessageToken().getMessageUid(), message); 215 - } 216 - } 217 - 218 - removeOrphanedMessages(); 219 - finalFolderMessageFetch(); 220 - } 221 - 222 - private void finalFolderMessageFetch() { 223 - // Queue a fetch for messages missing from the cache 224 - if(!secondaryMessageTokensToFetch.isEmpty()) { 225 - MessageToken[] fetchArray = new MessageToken[secondaryMessageTokensToFetch.size()]; 226 - secondaryMessageTokensToFetch.copyInto(fetchArray); 227 - secondaryMessageTokensToFetch.removeAllElements(); 228 - processMailStoreRequest(mailStore.createFolderMessagesSetRequest(folderTreeItem, fetchArray) 229 - .setRequestCallback(finalFetchCallback)); 230 - } 231 - else { 232 - initialRefreshComplete = true; 233 - endFolderRefreshOperation(true); 234 - } 235 - } 236 - 237 60 public void setPriorFolderMessagesSeen(final Date startDate) { 238 61 invokeAfterRefresh(new PostRefreshRunnable() { 239 62 public void run(boolean refreshSuccessful) {
-28
LogicMail/src/org/logicprobe/LogicMail/model/MailStoreServices.java
··· 32 32 33 33 import java.util.Date; 34 34 35 - import net.rim.device.api.util.ToIntHashtable; 36 - 37 35 import org.logicprobe.LogicMail.mail.AbstractMailStore; 38 36 import org.logicprobe.LogicMail.mail.FolderEvent; 39 37 import org.logicprobe.LogicMail.mail.FolderExpungedEvent; 40 38 import org.logicprobe.LogicMail.mail.FolderListener; 41 - import org.logicprobe.LogicMail.mail.FolderMessageIndexMapEvent; 42 39 import org.logicprobe.LogicMail.mail.FolderMessagesEvent; 43 40 import org.logicprobe.LogicMail.mail.FolderTreeItem; 44 41 import org.logicprobe.LogicMail.mail.MailStoreEvent; ··· 90 87 else { 91 88 handleFolderExpunged(e.getFolder(), e.getExpungedIndices(), e.getUpdatedTokens()); 92 89 } 93 - } 94 - public void folderMessageIndexMapAvailable(FolderMessageIndexMapEvent e) { 95 - handleFolderMessageIndexMapAvailable(e.getFolder(), e.getUidIndexMap()); 96 90 } 97 91 public void folderRefreshRequired(FolderEvent e) { 98 92 handleFolderRefreshRequired(e.getFolder(), e.getEventOrigin()); ··· 727 721 } 728 722 } 729 723 730 - protected void handleFolderMessageIndexMapAvailable(FolderTreeItem folder, ToIntHashtable uidIndexMap) { 731 - fireFolderMessageIndexMapAvailable(folder, uidIndexMap); 732 - } 733 - 734 - /** 735 - * Notifies all registered <tt>FolderListener</tt>s that 736 - * the UID-to-index map for a folder has been loaded. 737 - * 738 - * @param folder The folder which has available messages 739 - * @param uidIndexMap The UID-to-index map for the folder's messages 740 - */ 741 - protected final void fireFolderMessageIndexMapAvailable(FolderTreeItem folder, ToIntHashtable uidIndexMap) { 742 - Object[] listeners = listenerList.getListeners(FolderListener.class); 743 - FolderMessageIndexMapEvent e = null; 744 - for(int i=0; i<listeners.length; i++) { 745 - if(e == null) { 746 - e = new FolderMessageIndexMapEvent(this, folder, uidIndexMap); 747 - } 748 - ((FolderListener)listeners[i]).folderMessageIndexMapAvailable(e); 749 - } 750 - } 751 - 752 724 protected void handleFolderRefreshRequired(FolderTreeItem folder, int eventOrigin) { } 753 725 754 726 protected void handleFolderExpunged(FolderTreeItem folder, int[] indices, MessageToken[] updatedTokens) {
+4 -64
LogicMail/src/org/logicprobe/LogicMail/model/NetworkMailStoreServices.java
··· 36 36 import java.util.Vector; 37 37 38 38 import net.rim.device.api.util.Arrays; 39 - import net.rim.device.api.util.ToIntHashtable; 40 39 41 40 import org.logicprobe.LogicMail.conf.AccountConfig; 42 41 import org.logicprobe.LogicMail.conf.ImapConfig; ··· 48 47 import org.logicprobe.LogicMail.mail.MailStoreRequest; 49 48 import org.logicprobe.LogicMail.mail.MailStoreRequestCallback; 50 49 import org.logicprobe.LogicMail.mail.MessageToken; 51 - import org.logicprobe.LogicMail.mail.NetworkClientIdleModeRequest; 52 50 import org.logicprobe.LogicMail.mail.NetworkMailStore; 53 51 import org.logicprobe.LogicMail.mail.NetworkPollingStartRequest; 54 52 import org.logicprobe.LogicMail.message.FolderMessage; ··· 153 151 } 154 152 155 153 private void requestFolderRefreshImpl(final FolderTreeItem folderTreeItem, final boolean automated) { 156 - NetworkClientIdleModeRequest disableIdleRequest = mailStore.createClientIdleModeRequest(false); 157 - disableIdleRequest.setRequestCallback(new MailStoreRequestCallback() { 158 - public void mailStoreRequestComplete(MailStoreRequest request) { 159 - FolderRequestHandler handler = getFolderRequestHandler(folderTreeItem); 160 - handler.requestFolderRefresh(!automated, new FolderRequestHandler.PostRefreshRunnable() { 161 - public void run(boolean refreshSuccessful) { 162 - if(!refreshSuccessful) { return; } 163 - mailStore.processRequest(mailStore.createClientIdleModeRequest(true)); 164 - } 165 - }); 166 - } 167 - public void mailStoreRequestFailed(MailStoreRequest request, Throwable exception, boolean isFinal) { } 168 - }); 169 - mailStore.processRequest(disableIdleRequest); 154 + FolderRequestHandler handler = getFolderRequestHandler(folderTreeItem); 155 + handler.requestFolderRefresh(!automated); 170 156 } 171 157 172 158 private void requestFolderRefreshImpl(final FolderTreeItem[] folderTreeItems, final boolean automated) { ··· 174 160 requestFolderRefreshImpl(folderTreeItems[0], automated); 175 161 } 176 162 else { 177 - // Special refresh implementation that chains each folder refresh 178 - // onto the end of the previous folder refresh. This awkward 179 - // process is necessary to ensure that all folders are refreshed in 180 - // the specified order, without overlapping requests. 181 - 182 - final Vector foldersNeedingRefresh = new Vector(folderTreeItems.length - 1); 183 163 for(int i=1; i<folderTreeItems.length; i++) { 184 - foldersNeedingRefresh.addElement(folderTreeItems[i]); 185 - } 186 - 187 - NetworkClientIdleModeRequest disableIdleRequest = mailStore.createClientIdleModeRequest(false); 188 - disableIdleRequest.setRequestCallback(new MailStoreRequestCallback() { 189 - public void mailStoreRequestComplete(MailStoreRequest request) { 190 - FolderRequestHandler handler = getFolderRequestHandler(folderTreeItems[0]); 191 - handler.requestFolderRefresh(!automated, new FolderRefreshSequenceRunnable(automated, foldersNeedingRefresh)); 192 - } 193 - public void mailStoreRequestFailed(MailStoreRequest request, Throwable exception, boolean isFinal) { } 194 - }); 195 - mailStore.processRequest(disableIdleRequest); 196 - } 197 - } 198 - 199 - private class FolderRefreshSequenceRunnable extends FolderRequestHandler.PostRefreshRunnable { 200 - private final boolean automated; 201 - private final Vector foldersNeedingRefresh; 202 - public FolderRefreshSequenceRunnable(boolean automated, Vector foldersNeedingRefresh) { 203 - this.automated = automated; 204 - this.foldersNeedingRefresh = foldersNeedingRefresh; 205 - } 206 - public void run(boolean refreshSuccessful) { 207 - if(!refreshSuccessful) { return; } 208 - FolderTreeItem nextFolder = (FolderTreeItem)foldersNeedingRefresh.elementAt(0); 209 - foldersNeedingRefresh.removeElementAt(0); 210 - FolderRequestHandler nextHandler = getFolderRequestHandler(nextFolder); 211 - 212 - if(foldersNeedingRefresh.size() == 0) { 213 - nextHandler.requestFolderRefresh(!automated, new FolderRequestHandler.PostRefreshRunnable() { 214 - public void run(boolean refreshSuccessful) { 215 - if(!refreshSuccessful) { return; } 216 - mailStore.processRequest(mailStore.createClientIdleModeRequest(true)); 217 - } 218 - }); 219 - } 220 - else { 221 - nextHandler.requestFolderRefresh(!automated, new FolderRefreshSequenceRunnable(automated, foldersNeedingRefresh)); 164 + FolderRequestHandler handler = getFolderRequestHandler(folderTreeItems[i]); 165 + handler.requestFolderRefresh(!automated); 222 166 } 223 167 } 224 168 } ··· 271 215 } 272 216 } 273 217 274 - protected void handleFolderMessageIndexMapAvailable(FolderTreeItem folder, ToIntHashtable uidIndexMap) { 275 - // No longer handled through a mail store listener 276 - } 277 - 278 218 protected void handleFolderRefreshRequired(FolderTreeItem folder, int eventOrigin) { 279 219 FolderRequestHandler handler = getFolderRequestHandler(folder); 280 220
-113
LogicMail/src/org/logicprobe/LogicMail/model/PopFolderRequestHandler.java
··· 31 31 package org.logicprobe.LogicMail.model; 32 32 33 33 import java.util.Date; 34 - import java.util.Enumeration; 35 34 import java.util.Vector; 36 35 37 - import net.rim.device.api.util.IntHashtable; 38 - import net.rim.device.api.util.IntVector; 39 - import net.rim.device.api.util.SimpleSortingIntVector; 40 - import net.rim.device.api.util.ToIntHashtable; 41 - 42 36 import org.logicprobe.LogicMail.mail.FolderTreeItem; 43 - import org.logicprobe.LogicMail.mail.MailStoreRequest; 44 37 import org.logicprobe.LogicMail.mail.MessageToken; 45 - import org.logicprobe.LogicMail.mail.NetworkFolderMessageIndexMapRequest; 46 38 import org.logicprobe.LogicMail.mail.NetworkMailStore; 47 39 import org.logicprobe.LogicMail.message.FolderMessage; 48 40 import org.logicprobe.LogicMail.message.MessageFlags; ··· 65 57 FolderMessageCache folderMessageCache, 66 58 FolderTreeItem folderTreeItem) { 67 59 super(mailStoreServices, mailStore, folderMessageCache, folderTreeItem); 68 - } 69 - 70 - protected void beginFolderRefreshOperation() { 71 - if(initialRefreshComplete) { 72 - // Subsequent refresh is pointless on locked-folder mail stores 73 - endFolderRefreshOperation(true); 74 - return; 75 - } 76 - 77 - mailStoreServices.invokeLater(new Runnable() { public void run() { 78 - // Fetch messages stored in the cache 79 - loadCachedFolderMessages(); 80 - 81 - processMailStoreRequest(mailStore.requestFolderMessageIndexMap(folderTreeItem) 82 - .setRequestCallback(new FolderRefreshRequestCallback() { 83 - public void mailStoreRequestComplete(MailStoreRequest request) { 84 - NetworkFolderMessageIndexMapRequest indexMapRequest = (NetworkFolderMessageIndexMapRequest)request; 85 - indexMapFetchComplete(indexMapRequest.getResultUidIndexMap()); 86 - } 87 - })); 88 - }}); 89 - } 90 - 91 - private void indexMapFetchComplete(final ToIntHashtable uidIndexMap) { 92 - final int initialMessageLimit = mailStore.getAccountConfig().getInitialFolderMessages(); 93 - final int messageRetentionLimit = mailStore.getAccountConfig().getMaximumFolderMessages(); 94 - 95 - mailStoreServices.invokeLater(new Runnable() { public void run() { 96 - Vector messagesUpdated = new Vector(); 97 - SimpleSortingIntVector indexVector = new SimpleSortingIntVector(); 98 - IntHashtable cachedIndexToMessageMap = new IntHashtable(); 99 - 100 - // Iterate through the UID-to-index map, and do the following: 101 - // - Remove cache-loaded messages from the orphan set if they exist on the server. 102 - // - Update index information for those messages that do still exist server-side. 103 - // - Build a sortable vector of index values 104 - Enumeration e = uidIndexMap.keys(); 105 - while(e.hasMoreElements()) { 106 - String uid = (String)e.nextElement(); 107 - int index = uidIndexMap.get(uid); 108 - indexVector.addElement(index); 109 - 110 - FolderMessage message = (FolderMessage)orphanedMessageSet.remove(uid); 111 - if(message != null) { 112 - message.setIndex(index); 113 - message.getMessageToken().updateMessageIndex(index); 114 - 115 - if(folderMessageCache.updateFolderMessage(folderTreeItem, message)) { 116 - messagesUpdated.addElement(message); 117 - cachedIndexToMessageMap.put(index, message); 118 - } 119 - } 120 - } 121 - indexVector.reSort(SimpleSortingIntVector.SORT_TYPE_NUMERIC); 122 - 123 - notifyMessageFlagUpdates(messagesUpdated); 124 - removeOrphanedMessages(); 125 - 126 - // Determine the fetch range 127 - int size = indexVector.size(); 128 - if(size == 0) { return; } 129 - int fetchRangeStart = Math.max(0, size - initialMessageLimit); 130 - 131 - // Build a list of indices to fetch 132 - IntVector messagesToFetch = new IntVector(); 133 - for(int i=indexVector.size() - 1; i >= fetchRangeStart; --i) { 134 - int index = indexVector.elementAt(i); 135 - if(!cachedIndexToMessageMap.containsKey(index)) { 136 - messagesToFetch.addElement(index); 137 - } 138 - } 139 - 140 - int additionalMessageLimit = messageRetentionLimit - initialMessageLimit; 141 - for(int i=fetchRangeStart - 1; i >= 0; --i) { 142 - if(additionalMessageLimit > 0) { 143 - additionalMessageLimit--; 144 - } 145 - else { 146 - // Beyond the limit, add these back to the orphan set 147 - FolderMessage message = (FolderMessage)cachedIndexToMessageMap.get(indexVector.elementAt(i)); 148 - if(message != null) { 149 - orphanedMessageSet.put(message.getMessageToken().getMessageUid(), message); 150 - } 151 - } 152 - } 153 - removeOrphanedMessages(); 154 - 155 - // Do the final request for missing messages 156 - if(messagesToFetch.size() > 0) { 157 - processMailStoreRequest(mailStore.createFolderMessagesSetByIndexRequest(folderTreeItem, messagesToFetch.toArray()) 158 - .setRequestCallback(finalFetchCallback)); 159 - } 160 - else { 161 - initialRefreshComplete = true; 162 - endFolderRefreshOperation(true); 163 - } 164 - }}); 165 - } 166 - 167 - private void notifyMessageFlagUpdates(final Vector messagesUpdated) { 168 - if(!messagesUpdated.isEmpty()) { 169 - FolderMessage[] messages = new FolderMessage[messagesUpdated.size()]; 170 - messagesUpdated.copyInto(messages); 171 - mailStoreServices.fireFolderMessagesAvailable(folderTreeItem, messages, true, true); 172 - } 173 60 } 174 61 175 62 public void setPriorFolderMessagesSeen(final Date startDate) {
-26
LogicMailTests/src/org/logicprobe/LogicMail/mail/MockAbstractMailStore.java
··· 560 560 super.fireFolderMessagesAvailable(arg0, arg1, arg2); 561 561 } 562 562 563 - public static final MockMethod MTHD_FIRE_FOLDER_MESSAGE_INDEX_MAP_AVAILABLE_$_FOLDERTREEITEM_TOINTHASHTABLE = new MockMethod( 564 - MockAbstractMailStore.class, 565 - "MTHD_FIRE_FOLDER_MESSAGE_INDEX_MAP_AVAILABLE_$_FOLDERTREEITEM_TOINTHASHTABLE", 566 - new Class[]{org.logicprobe.LogicMail.mail.FolderTreeItem.class, net.rim.device.api.util.ToIntHashtable.class}, 567 - new Class[]{}, 568 - null, 569 - false); 570 - public void fireFolderMessageIndexMapAvailable(org.logicprobe.LogicMail.mail.FolderTreeItem arg0, net.rim.device.api.util.ToIntHashtable arg1) { 571 - try { 572 - Object[] args = new Object[2]; 573 - args[0] = arg0; 574 - args[1] = arg1; 575 - MethodInvocation mi = new MethodInvocation(MTHD_FIRE_FOLDER_MESSAGE_INDEX_MAP_AVAILABLE_$_FOLDERTREEITEM_TOINTHASHTABLE, this, args); 576 - getInvocationHandler().invoke(mi); 577 - if (mi.isEvaluated()) { 578 - mi.getReturnValue(); 579 - return; 580 - } 581 - } catch (Throwable t) { 582 - if (t instanceof java.lang.Error) { throw (java.lang.Error)t; } 583 - if (t instanceof java.lang.RuntimeException) { throw (java.lang.RuntimeException)t; } 584 - throw new HammockException(t); 585 - } 586 - super.fireFolderMessageIndexMapAvailable(arg0, arg1); 587 - } 588 - 589 563 public static final MockMethod MTHD_FIRE_FOLDER_REFRESH_REQUIRED_$_FOLDERTREEITEM_BOOLEAN = new MockMethod( 590 564 MockAbstractMailStore.class, 591 565 "MTHD_FIRE_FOLDER_REFRESH_REQUIRED_$_FOLDERTREEITEM_BOOLEAN",
-1
LogicMailTests/src/org/logicprobe/LogicMail/mail/NetworkMailStoreTest.java
··· 101 101 eventFolderStatusChanged = e; 102 102 } 103 103 public void folderExpunged(FolderExpungedEvent e) { } 104 - public void folderMessageIndexMapAvailable(FolderMessageIndexMapEvent e) { } 105 104 public void folderRefreshRequired(FolderEvent e) { } 106 105 }); 107 106