From 6c84e196aa9ddd37f33c57bcb189f9e3cca590a3 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Tue, 21 Jun 2011 02:56:53 +0530 Subject: [PATCH 01/19] Astyle is seriously getting on my nerves. I'm committing this with nothing but astyle changes so that forthcoming commits are clean. --- .../k9/activity/setup/AccountSettings.java | 4 ++-- src/com/fsck/k9/mail/internet/MimeUtility.java | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java index 38c9e38e0..1032291f6 100644 --- a/src/com/fsck/k9/activity/setup/AccountSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSettings.java @@ -884,8 +884,8 @@ public class AccountSettings extends K9PreferenceActivity { } } - allFolderValues = new String[folders.size()+1]; - allFolderLabels = new String[folders.size()+1]; + allFolderValues = new String[folders.size() + 1]; + allFolderLabels = new String[folders.size() + 1]; allFolderValues[0] = K9.FOLDER_NONE; allFolderLabels[0] = K9.FOLDER_NONE; diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java index 0fd320c0a..54040a364 100644 --- a/src/com/fsck/k9/mail/internet/MimeUtility.java +++ b/src/com/fsck/k9/mail/internet/MimeUtility.java @@ -1076,7 +1076,7 @@ public class MimeUtility { */ if (contentTransferEncoding != null) { contentTransferEncoding = - MimeUtility.getHeaderParameter(contentTransferEncoding, null); + MimeUtility.getHeaderParameter(contentTransferEncoding, null); if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) { in = new QuotedPrintableInputStream(in); } else if ("base64".equalsIgnoreCase(contentTransferEncoding)) { @@ -1102,7 +1102,7 @@ public class MimeUtility { * @throws MessagingException */ public static void collectParts(Part part, ArrayList viewables, - ArrayList attachments) throws MessagingException { + ArrayList attachments) throws MessagingException { /* * If the part is Multipart but not alternative it's either mixed or * something we don't know about, which means we treat it as mixed @@ -1326,10 +1326,10 @@ public class MimeUtility { private static String getJisVariantFromAddress(String address) { if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") || - isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com")) + isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com")) return "docomo"; else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") || - isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp")) + isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp")) return "softbank"; else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp")) return "kddi"; @@ -1364,14 +1364,14 @@ public class MimeUtility { // iso-2022-jp variants are supported by no versions as of Dec 2010. if (charset.length() > 19 && charset.startsWith("x-") && - charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) { + charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) { in = new Iso2022JpToShiftJisInputStream(in); charset = "x-" + charset.substring(2, charset.length() - 17) + "-shift_jis-2007"; } // shift_jis variants are supported by Eclair and later. if (charset.length() > 17 && charset.startsWith("x-") && - charset.endsWith("-shift_jis-2007") && !Charset.isSupported(charset)) { + charset.endsWith("-shift_jis-2007") && !Charset.isSupported(charset)) { // If the JIS variant is iPhone, map the Unicode private use area in iPhone to the one in Android after // converting the character set from the standard Shift JIS to Unicode. if (charset.substring(2, charset.length() - 15).equals("iphone")) @@ -1392,7 +1392,7 @@ public class MimeUtility { } if (!supported) { Log.e(K9.LOG_TAG, "I don't know how to deal with the charset " + charset + - ". Falling back to US-ASCII"); + ". Falling back to US-ASCII"); charset = "US-ASCII"; } /* @@ -2393,12 +2393,12 @@ public class MimeUtility { public static void setCharset(String charset, Part part) throws MessagingException { part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, - part.getMimeType() + ";\n charset=" + getExternalCharset(charset)); + part.getMimeType() + ";\n charset=" + getExternalCharset(charset)); } public static String getExternalCharset(String charset) { if (charset.length() > 17 && charset.startsWith("x-") && - charset.endsWith("-shift_jis-2007")) + charset.endsWith("-shift_jis-2007")) return "shift_jis"; return charset; From 970271dbf943e6a71dcb262832c2eaff5880b7cb Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Tue, 21 Jun 2011 04:34:57 +0530 Subject: [PATCH 02/19] If the response for an APPEND command contains the APPENDUID response code, read the UID of the newly appended message from there. --- src/com/fsck/k9/mail/store/ImapStore.java | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index b639a11f2..f37e44de8 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -52,6 +52,7 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.PowerManager; +import android.text.TextUtils; import android.util.Log; import com.beetstra.jutf7.CharsetProvider; @@ -1617,6 +1618,31 @@ public class ImapStore extends Store { while (response.more()); } while (response.mTag == null); + /* + * If the server supports UIDPLUS, then along with the APPEND response it will return an APPENDUID response code. + * e.g. 11 OK [APPENDUID 2 238268] APPEND completed + * + * We can use the UID included in this response to update our records. + * + * Code imported from AOSP Email as of git rev a5c3744a247e432acf9f571a9dfb55321c4baa1a + */ + Object responseList = response.get(1); + + if (responseList instanceof ImapList) { + final ImapList appendList = (ImapList) responseList; + if ((appendList.size() >= 3) && appendList.getString(0).equals("APPENDUID")) { + String serverUid = appendList.getString(2); + if (!TextUtils.isEmpty(serverUid)) { + message.setUid(serverUid); + continue; + } + } + } + + + /* + * This part is executed in case the server does not support UIDPLUS or does not implement the APPENDUID response code. + */ String newUid = getUidFromMessageId(message); if (K9.DEBUG) Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId()); From 0ba7f206225d4f9c71e83cf94f1bf74ca27aa5aa Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Fri, 24 Jun 2011 02:54:12 +0530 Subject: [PATCH 03/19] Attempt to implement COPYUID, works for the most part except for updation of the LocalStore with freshly copied messages. --- src/com/fsck/k9/mail/store/ImapStore.java | 130 ++++++++++++++++++++-- 1 file changed, 122 insertions(+), 8 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index f37e44de8..8824e0988 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -29,6 +29,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -39,7 +40,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.SortedSet; import java.util.StringTokenizer; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -813,8 +816,24 @@ public class ImapStore extends Store { ImapFolder iFolder = (ImapFolder)folder; checkOpen(); + + SortedSet messageSet = new TreeSet(new Comparator() { + public int compare(Message m1, Message m2) { + int uid1 = Integer.parseInt(m1.getUid()), uid2 = Integer.parseInt(m2.getUid()); + if (uid1 < uid2) { + return -1; + } else if (uid1 == uid2) { + return 0; + } else { + return 1; + } + } + }); String[] uids = new String[messages.length]; for (int i = 0, count = messages.length; i < count; i++) { + messageSet.add(messages[i]); + + // Not bothering to sort the UIDs in ascending order while sending the command for convenience, and because it does not make a difference. uids[i] = messages[i].getUid(); } try { @@ -829,20 +848,104 @@ public class ImapStore extends Store { iFolder.create(FolderType.HOLDS_MESSAGES); } - if (exists(remoteDestName)) { - executeSimpleCommand(String.format("UID COPY %s %s", - Utility.combine(uids, ','), - remoteDestName)); - } else { - throw new MessagingException("IMAPMessage.copyMessages: remote destination folder " + folder.getName() - + " does not exist and could not be created for " + getLogId() - , true); + mConnection.sendCommand(String.format("UID COPY %s %s", + Utility.combine(uids, ','), + remoteDestName), false); + ImapResponse response; + do { + response = mConnection.readResponse(); + handleUntaggedResponse(response); + if (response.mCommandContinuationRequested) { + EOLConvertingOutputStream eolOut = new EOLConvertingOutputStream(mConnection.mOut); + eolOut.write('\r'); + eolOut.write('\n'); + eolOut.flush(); + } + while (response.more()); + } while (response.mTag == null); + + /* + * If the server supports UIDPLUS, then along with the COPY response it will return an COPYUID response code. + * e.g. 24 OK [COPYUID 38505 304,319:320 3956:3958] Success + * + * COPYUID is followed by UIDVALIDITY, set of UIDs of copied messages from the source folder and set of corresponding UIDs assigned to them in the destination folder. + * + * We can use the new UIDs included in this response to update our records. + */ + + Object responseList = response.get(1); + + if (responseList instanceof ImapList) { + final ImapList copyList = (ImapList) responseList; + if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { + List oldUids = parseSequenceSet(copyList.getString(2)); + List newUids = parseSequenceSet(copyList.getString(3)); + if (oldUids.size() == newUids.size()) { + Iterator messageIterator = messageSet.iterator(); + for (int i = 0; i < messages.length && messageIterator.hasNext(); i++) { + Message nextMessage = messageIterator.next(); + if (oldUids.get(i).equals(nextMessage.getUid())) { + /* + * Here, we need to *create* new messages in the localstore, same as the older messages, the only changes are that old UIDs need to be swapped with new UIDs, + * and old folder swapped with new folder. + */ +// nextMessage.setUid(newUids.get(i)); + } + } + } + } } } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } } + private List parseSequenceSet(String set) { + int index = 0; + List sequenceList = new ArrayList(); + String element = ""; + + while (index < set.length()) { + if (set.charAt(index) == ':') { + String upperBound = ""; + index++; + while (index < set.length()) { + if (!(set.charAt(index) == ',')) { + upperBound += set.charAt(index); + index++; + } else { + break; + } + } + + int lower = Integer.parseInt(element); + int upper = Integer.parseInt(upperBound); + + if (lower < upper) { + for (int i = lower; i <= upper; i++) { + sequenceList.add(String.valueOf(i)); + } + } else { + for (int i = upper; i <= lower; i++) { + sequenceList.add(String.valueOf(i)); + } + } + + element = ""; + } else if (set.charAt(index) == ',') { + sequenceList.add(element); + element = ""; + } else { + element += set.charAt(index); + } + index++; + } + if (!element.equals("")) { + sequenceList.add(element); + } + return sequenceList; + } + @Override public void moveMessages(Message[] messages, Folder folder) throws MessagingException { if (messages.length == 0) @@ -2290,6 +2393,17 @@ public class ImapStore extends Store { return executeSimpleCommand(command, sensitive, null); } +// public void logResponse (ImapList response) { +// for(int i=0;i executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) throws IOException, ImapException, MessagingException { String commandToLog = command; From bc9b7030d7d250c61b7bf8b624c6fa84c0363dd3 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Tue, 28 Jun 2011 16:50:48 +0530 Subject: [PATCH 04/19] COPYUID implementation now in place and working, restructured appendMessages, copyMessages and moveMessages globally to return a Map of srcUids -> destUids rather than returning nothing. This is now used to bring local and remote UIDs upto speed without the need for additional requests. --- .../k9/controller/MessagingController.java | 94 ++++++++++++++++--- src/com/fsck/k9/mail/Folder.java | 11 ++- src/com/fsck/k9/mail/store/ImapStore.java | 83 ++++++++-------- src/com/fsck/k9/mail/store/LocalStore.java | 30 ++++-- src/com/fsck/k9/mail/store/Pop3Store.java | 4 +- src/com/fsck/k9/mail/store/WebDavStore.java | 10 +- 6 files changed, 163 insertions(+), 69 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 079b72812..198f4b9a0 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2088,9 +2088,32 @@ public class MessagingController implements Runnable { command.arguments[0] = srcFolder; command.arguments[1] = destFolder; command.arguments[2] = Boolean.toString(isCopy); - System.arraycopy(uids, 0, command.arguments, 3, uids.length); + command.arguments[3] = Boolean.toString(false); + System.arraycopy(uids, 0, command.arguments, 4, uids.length); queuePendingCommand(account, command); } + + private void queueMoveOrCopy(Account account, String srcFolder, String destFolder, boolean isCopy, String uids[], Map uidMap) { + if (uidMap == null || uidMap.isEmpty()) { + queueMoveOrCopy(account, srcFolder, destFolder, isCopy, uids); + } else { + if (account.getErrorFolderName().equals(srcFolder)) { + return; + } + PendingCommand command = new PendingCommand(); + command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK; + + int length = 4 + uidMap.keySet().size() + uidMap.values().size(); + command.arguments = new String[length]; + command.arguments[0] = srcFolder; + command.arguments[1] = destFolder; + command.arguments[2] = Boolean.toString(isCopy); + command.arguments[3] = Boolean.toString(true); + System.arraycopy(uidMap.keySet().toArray(), 0, command.arguments, 4, uidMap.keySet().size()); + System.arraycopy(uidMap.values().toArray(), 0, command.arguments, 4 + uidMap.keySet().size(), uidMap.values().size()); + queuePendingCommand(account, command); + } + } /** * Process a pending trash message command. * @@ -2102,6 +2125,7 @@ public class MessagingController implements Runnable { throws MessagingException { Folder remoteSrcFolder = null; Folder remoteDestFolder = null; + Folder localDestFolder = null; try { String srcFolder = command.arguments[0]; if (account.getErrorFolderName().equals(srcFolder)) { @@ -2109,14 +2133,42 @@ public class MessagingController implements Runnable { } String destFolder = command.arguments[1]; String isCopyS = command.arguments[2]; + String hasNewUidsS = command.arguments[3]; + + boolean hasNewUids = false; + if (hasNewUidsS != null) { + hasNewUids = Boolean.parseBoolean(hasNewUidsS); + } + Store remoteStore = account.getRemoteStore(); remoteSrcFolder = remoteStore.getFolder(srcFolder); + Store localStore = account.getLocalStore(); + localDestFolder = localStore.getFolder(destFolder); List messages = new ArrayList(); - for (int i = 3; i < command.arguments.length; i++) { - String uid = command.arguments[i]; - if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { - messages.add(remoteSrcFolder.getMessage(uid)); + + /* + * We split up the localUidMap into two parts while sending the command, here we assemble it back. + */ + Map localUidMap = new HashMap(); + if (hasNewUids) { + int offset = (command.arguments.length - 4) / 2; + + for (int i = 4; i < 4 + offset; i++) { + localUidMap.put(command.arguments[i], command.arguments[i + offset]); + + String uid = command.arguments[i]; + if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + messages.add(remoteSrcFolder.getMessage(uid)); + } + } + + } else { + for (int i = 4; i < command.arguments.length; i++) { + String uid = command.arguments[i]; + if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + messages.add(remoteSrcFolder.getMessage(uid)); + } } } @@ -2137,6 +2189,8 @@ public class MessagingController implements Runnable { Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy: source folder = " + srcFolder + ", " + messages.size() + " messages, destination folder = " + destFolder + ", isCopy = " + isCopy); + Map remoteUidMap = new HashMap(); + if (!isCopy && destFolder.equals(account.getTrashFolderName())) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy doing special case for deleting message"); @@ -2150,9 +2204,9 @@ public class MessagingController implements Runnable { remoteDestFolder = remoteStore.getFolder(destFolder); if (isCopy) { - remoteSrcFolder.copyMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); + remoteUidMap = remoteSrcFolder.copyMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); } else { - remoteSrcFolder.moveMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); + remoteUidMap = remoteSrcFolder.moveMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); } } if (!isCopy && Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { @@ -2161,12 +2215,27 @@ public class MessagingController implements Runnable { remoteSrcFolder.expunge(); } + + /* + * This next part is used to bring the local UIDs of the local destination folder + * upto speed with the remote UIDs of remote destionation folder. + */ + if (!localUidMap.isEmpty() && !remoteUidMap.isEmpty()) { + Set remoteSrcUids = remoteUidMap.keySet(); + Iterator remoteSrcUidsIterator = remoteSrcUids.iterator(); + + while (remoteSrcUidsIterator.hasNext()) { + String remoteSrcUid = remoteSrcUidsIterator.next(); + String localDestUid = localUidMap.get(remoteSrcUid); + + Message localDestMessage = localDestFolder.getMessage(localDestUid); + localDestMessage.setUid(remoteUidMap.get(remoteSrcUid)); + } + } } finally { closeFolder(remoteSrcFolder); closeFolder(remoteDestFolder); } - - } private void queueSetFlag(final Account account, final String folderName, final String newState, final String flag, final String[] uids) { @@ -3248,6 +3317,7 @@ public class MessagingController implements Runnable { private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder, final Message[] inMessages, final String destFolder, final boolean isCopy, MessagingListener listener) { try { + Map uidMap = new HashMap(); Store localStore = account.getLocalStore(); Store remoteStore = account.getRemoteStore(); if (!isCopy && (!remoteStore.isMoveCapable() || !localStore.isMoveCapable())) { @@ -3285,9 +3355,9 @@ public class MessagingController implements Runnable { fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.BODY); localSrcFolder.fetch(messages, fp, null); - localSrcFolder.copyMessages(messages, localDestFolder); + uidMap = localSrcFolder.copyMessages(messages, localDestFolder); } else { - localSrcFolder.moveMessages(messages, localDestFolder); + uidMap = localSrcFolder.moveMessages(messages, localDestFolder); for (String origUid : origUidMap.keySet()) { for (MessagingListener l : getListeners()) { l.messageUidChanged(account, srcFolder, origUid, origUidMap.get(origUid).getUid()); @@ -3296,7 +3366,7 @@ public class MessagingController implements Runnable { } } - queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidMap.keySet().toArray(EMPTY_STRING_ARRAY)); + queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidMap.keySet().toArray(EMPTY_STRING_ARRAY), uidMap); } processPendingCommands(account); diff --git a/src/com/fsck/k9/mail/Folder.java b/src/com/fsck/k9/mail/Folder.java index 0f5b96060..e3dafc3e5 100644 --- a/src/com/fsck/k9/mail/Folder.java +++ b/src/com/fsck/k9/mail/Folder.java @@ -1,6 +1,7 @@ package com.fsck.k9.mail; import java.util.Date; +import java.util.Map; import android.util.Log; import com.fsck.k9.Account; @@ -102,11 +103,15 @@ public abstract class Folder { public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener) throws MessagingException; - public abstract void appendMessages(Message[] messages) throws MessagingException; + public abstract Map appendMessages(Message[] messages) throws MessagingException; - public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {} + public Map copyMessages(Message[] msgs, Folder folder) throws MessagingException { + return null; + } - public void moveMessages(Message[] msgs, Folder folder) throws MessagingException {} + public Map moveMessages(Message[] msgs, Folder folder) throws MessagingException { + return null; + } public void delete(Message[] msgs, String trashFolderName) throws MessagingException { for (Message message : msgs) { diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 8824e0988..b4da67248 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -29,7 +29,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -40,9 +39,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.SortedSet; import java.util.StringTokenizer; -import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -806,32 +803,19 @@ public class ImapStore extends Store { } @Override - public void copyMessages(Message[] messages, Folder folder) throws MessagingException { + public Map copyMessages(Message[] messages, Folder folder) throws MessagingException { if (!(folder instanceof ImapFolder)) { throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder"); } if (messages.length == 0) - return; + return null; ImapFolder iFolder = (ImapFolder)folder; checkOpen(); - SortedSet messageSet = new TreeSet(new Comparator() { - public int compare(Message m1, Message m2) { - int uid1 = Integer.parseInt(m1.getUid()), uid2 = Integer.parseInt(m2.getUid()); - if (uid1 < uid2) { - return -1; - } else if (uid1 == uid2) { - return 0; - } else { - return 1; - } - } - }); String[] uids = new String[messages.length]; for (int i = 0, count = messages.length; i < count; i++) { - messageSet.add(messages[i]); // Not bothering to sort the UIDs in ascending order while sending the command for convenience, and because it does not make a difference. uids[i] = messages[i].getUid(); @@ -875,31 +859,36 @@ public class ImapStore extends Store { Object responseList = response.get(1); + Map uidMap = null; + if (responseList instanceof ImapList) { final ImapList copyList = (ImapList) responseList; if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { - List oldUids = parseSequenceSet(copyList.getString(2)); - List newUids = parseSequenceSet(copyList.getString(3)); - if (oldUids.size() == newUids.size()) { - Iterator messageIterator = messageSet.iterator(); - for (int i = 0; i < messages.length && messageIterator.hasNext(); i++) { - Message nextMessage = messageIterator.next(); - if (oldUids.get(i).equals(nextMessage.getUid())) { - /* - * Here, we need to *create* new messages in the localstore, same as the older messages, the only changes are that old UIDs need to be swapped with new UIDs, - * and old folder swapped with new folder. - */ -// nextMessage.setUid(newUids.get(i)); - } + List srcUids = parseSequenceSet(copyList.getString(2)); + List destUids = parseSequenceSet(copyList.getString(3)); + if (srcUids.size() == destUids.size()) { + Iterator srcUidsIterator = srcUids.iterator(); + Iterator destUidsIterator = destUids.iterator(); + uidMap = new HashMap(); + while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { + uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); } } } } + return uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } } + /** + * Can be used to parse sequence sets or UID sets appearing is responses such as COPYUID. + * e.g. [COPYUID 38505 304,319:320 3956:3958] + * + * @param set + * @return List sequenceSet + */ private List parseSequenceSet(String set) { int index = 0; List sequenceList = new ArrayList(); @@ -947,11 +936,12 @@ public class ImapStore extends Store { } @Override - public void moveMessages(Message[] messages, Folder folder) throws MessagingException { + public Map moveMessages(Message[] messages, Folder folder) throws MessagingException { if (messages.length == 0) - return; - copyMessages(messages, folder); + return null; + Map uidMap = copyMessages(messages, folder); setFlags(messages, new Flag[] { Flag.DELETED }, true); + return uidMap; } @Override @@ -1698,9 +1688,10 @@ public class ImapStore extends Store { * new server UID. */ @Override - public void appendMessages(Message[] messages) throws MessagingException { + public Map appendMessages(Message[] messages) throws MessagingException { checkOpen(); try { + Map uidMap = null; for (Message message : messages) { mConnection.sendCommand( String.format("APPEND %s (%s) {%d}", @@ -1734,9 +1725,18 @@ public class ImapStore extends Store { if (responseList instanceof ImapList) { final ImapList appendList = (ImapList) responseList; if ((appendList.size() >= 3) && appendList.getString(0).equals("APPENDUID")) { - String serverUid = appendList.getString(2); - if (!TextUtils.isEmpty(serverUid)) { - message.setUid(serverUid); + String newUid = appendList.getString(2); + + /* + * We need uidMap to be null initially to maintain consistency with the behavior of other similar methods (copyMessages, moveMessages) which + * return null if new UIDs are not available. Therefore, we initialize uidMap over here. + */ + if (uidMap == null) { + uidMap = new HashMap(); + } + uidMap.put(message.getUid(), newUid); + if (!TextUtils.isEmpty(newUid)) { + message.setUid(newUid); continue; } } @@ -1751,11 +1751,14 @@ public class ImapStore extends Store { Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId()); if (newUid != null) { + if (uidMap == null) { + uidMap = new HashMap(); + } + uidMap.put(message.getUid(), newUid); message.setUid(newUid); } - - } + return uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index fba282164..0c3647034 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -1885,21 +1885,23 @@ public class LocalStore extends Store implements Serializable { } @Override - public void copyMessages(Message[] msgs, Folder folder) throws MessagingException { + public Map copyMessages(Message[] msgs, Folder folder) throws MessagingException { if (!(folder instanceof LocalFolder)) { throw new MessagingException("copyMessages called with incorrect Folder"); } - ((LocalFolder) folder).appendMessages(msgs, true); + return ((LocalFolder) folder).appendMessages(msgs, true); } @Override - public void moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException { + public Map moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException { if (!(destFolder instanceof LocalFolder)) { throw new MessagingException("moveMessages called with non-LocalFolder"); } final LocalFolder lDestFolder = (LocalFolder)destFolder; + final Map uidMap = new HashMap(); + try { database.execute(false, new DbCallback() { @Override @@ -1936,7 +1938,7 @@ public class LocalStore extends Store implements Serializable { LocalMessage placeHolder = new LocalMessage(oldUID, LocalFolder.this); placeHolder.setFlagInternal(Flag.DELETED, true); placeHolder.setFlagInternal(Flag.SEEN, true); - appendMessages(new Message[] { placeHolder }); + uidMap.putAll(appendMessages(new Message[] { placeHolder })); } } catch (MessagingException e) { throw new WrappedException(e); @@ -1944,6 +1946,7 @@ public class LocalStore extends Store implements Serializable { return null; } }); + return uidMap; } catch (WrappedException e) { throw(MessagingException) e.getCause(); } @@ -1989,8 +1992,8 @@ public class LocalStore extends Store implements Serializable { * message, retrieve the appropriate local message instance first (if it already exists). */ @Override - public void appendMessages(Message[] messages) throws MessagingException { - appendMessages(messages, false); + public Map appendMessages(Message[] messages) throws MessagingException { + return appendMessages(messages, false); } public void destroyMessages(final Message[] messages) throws MessagingException { @@ -2026,10 +2029,12 @@ public class LocalStore extends Store implements Serializable { * message, retrieve the appropriate local message instance first (if it already exists). * @param messages * @param copy + * @return Map uidMap of srcUids -> destUids */ - private void appendMessages(final Message[] messages, final boolean copy) throws MessagingException { + private Map appendMessages(final Message[] messages, final boolean copy) throws MessagingException { open(OpenMode.READ_WRITE); try { + final Map uidMap = new HashMap(); database.execute(true, new DbCallback() { @Override public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException { @@ -2040,11 +2045,15 @@ public class LocalStore extends Store implements Serializable { } String uid = message.getUid(); - if (uid == null || copy) { + if (uid == null && !copy) { uid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); - if (!copy) { - message.setUid(uid); + message.setUid(uid); + } else if (copy) { + String temp = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); + if (uid != null) { + uidMap.put(uid, temp); } + uid = temp; } else { Message oldMessage = getMessage(uid); if (oldMessage != null && !oldMessage.isSet(Flag.SEEN)) { @@ -2150,6 +2159,7 @@ public class LocalStore extends Store implements Serializable { return null; } }); + return uidMap; } catch (WrappedException e) { throw(MessagingException) e.getCause(); } diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index a307c8484..e0c57591e 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -24,6 +24,7 @@ import java.util.LinkedList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; public class Pop3Store extends Store { public static final int CONNECTION_SECURITY_NONE = 0; @@ -736,7 +737,8 @@ public class Pop3Store extends Store { } @Override - public void appendMessages(Message[] messages) throws MessagingException { + public Map appendMessages(Message[] messages) throws MessagingException { + return null; } @Override diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index 81a498f25..7720b9c18 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -51,6 +51,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Stack; import java.util.zip.GZIPInputStream; @@ -1161,13 +1162,15 @@ public class WebDavStore extends Store { } @Override - public void copyMessages(Message[] messages, Folder folder) throws MessagingException { + public Map copyMessages(Message[] messages, Folder folder) throws MessagingException { moveOrCopyMessages(messages, folder.getName(), false); + return null; } @Override - public void moveMessages(Message[] messages, Folder folder) throws MessagingException { + public Map moveMessages(Message[] messages, Folder folder) throws MessagingException { moveOrCopyMessages(messages, folder.getName(), true); + return null; } @Override @@ -1728,8 +1731,9 @@ public class WebDavStore extends Store { } @Override - public void appendMessages(Message[] messages) throws MessagingException { + public Map appendMessages(Message[] messages) throws MessagingException { appendWebDavMessages(messages); + return null; } public Message[] appendWebDavMessages(Message[] messages) throws MessagingException { From 3321ebdc33f3e51c145d20cc627cafe09021ad81 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Sat, 2 Jul 2011 11:59:00 +0530 Subject: [PATCH 05/19] COPYUID changes were being updated only in memory, but were not being written to db. --- src/com/fsck/k9/controller/MessagingController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 198f4b9a0..c971eebce 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2125,7 +2125,7 @@ public class MessagingController implements Runnable { throws MessagingException { Folder remoteSrcFolder = null; Folder remoteDestFolder = null; - Folder localDestFolder = null; + LocalFolder localDestFolder = null; try { String srcFolder = command.arguments[0]; if (account.getErrorFolderName().equals(srcFolder)) { @@ -2144,7 +2144,7 @@ public class MessagingController implements Runnable { remoteSrcFolder = remoteStore.getFolder(srcFolder); Store localStore = account.getLocalStore(); - localDestFolder = localStore.getFolder(destFolder); + localDestFolder = (LocalFolder) localStore.getFolder(destFolder); List messages = new ArrayList(); /* @@ -2230,6 +2230,7 @@ public class MessagingController implements Runnable { Message localDestMessage = localDestFolder.getMessage(localDestUid); localDestMessage.setUid(remoteUidMap.get(remoteSrcUid)); + localDestFolder.changeUid((LocalMessage)localDestMessage); } } } finally { From da9a5e6c17ce89d2fc54cebf35c5397fd609e93d Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Mon, 25 Jul 2011 06:50:26 +0530 Subject: [PATCH 06/19] Made some cosmetic changes for clarity, added debug messages for erroneous conditions. Fixed potential NPE in ImapFolder.parseSequenceSet(). --- src/com/fsck/k9/mail/store/ImapStore.java | 26 +++++++++++++++++----- src/com/fsck/k9/mail/store/LocalStore.java | 6 ++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index b4da67248..4e743e70a 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -866,15 +866,26 @@ public class ImapStore extends Store { if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { List srcUids = parseSequenceSet(copyList.getString(2)); List destUids = parseSequenceSet(copyList.getString(3)); - if (srcUids.size() == destUids.size()) { - Iterator srcUidsIterator = srcUids.iterator(); - Iterator destUidsIterator = destUids.iterator(); - uidMap = new HashMap(); - while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { - uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); + if (srcUids != null && destUids != null) { + if (srcUids.size() == destUids.size()) { + Iterator srcUidsIterator = srcUids.iterator(); + Iterator destUidsIterator = destUids.iterator(); + uidMap = new HashMap(); + while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { + uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); + } + } else { + if(K9.DEBUG) + Log.v(K9.LOG_TAG, "Parse error: size of source UIDs list is not the same as size of destination UIDs list."); } + } else { + if(K9.DEBUG) + Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); } } + } else { + if(K9.DEBUG) + Log.v(K9.LOG_TAG, "Expected COPYUID response was not found."); } return uidMap; } catch (IOException ioe) { @@ -890,6 +901,9 @@ public class ImapStore extends Store { * @return List sequenceSet */ private List parseSequenceSet(String set) { + if (set == null) { + return null; + } int index = 0; List sequenceList = new ArrayList(); String element = ""; diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 0c3647034..7a5a6b437 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -2049,11 +2049,11 @@ public class LocalStore extends Store implements Serializable { uid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); message.setUid(uid); } else if (copy) { - String temp = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); + String randomLocalUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); if (uid != null) { - uidMap.put(uid, temp); + uidMap.put(uid, randomLocalUid); } - uid = temp; + uid = randomLocalUid; } else { Message oldMessage = getMessage(uid); if (oldMessage != null && !oldMessage.isSet(Flag.SEEN)) { From cf390700487daf0f6611415d4531f9b013195b46 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Mon, 25 Jul 2011 18:56:21 +0530 Subject: [PATCH 07/19] Whitespaces :/ --- src/com/fsck/k9/mail/store/ImapStore.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 4e743e70a..d9f8b4548 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -875,16 +875,16 @@ public class ImapStore extends Store { uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); } } else { - if(K9.DEBUG) + if(K9.DEBUG) Log.v(K9.LOG_TAG, "Parse error: size of source UIDs list is not the same as size of destination UIDs list."); } } else { - if(K9.DEBUG) + if(K9.DEBUG) Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); } } } else { - if(K9.DEBUG) + if(K9.DEBUG) Log.v(K9.LOG_TAG, "Expected COPYUID response was not found."); } return uidMap; From 4b0d3ccf21caf250806d5b15565d90b0e242b495 Mon Sep 17 00:00:00 2001 From: Apoorv Khatreja Date: Thu, 17 Nov 2011 02:46:01 +0530 Subject: [PATCH 08/19] Removed an unnecessary portion of code that attempted to handle command continuation requests after a UID COPY command. Also removed some extraneous test code that mysteriously creeped into the repo. --- src/com/fsck/k9/mail/store/ImapStore.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index d9f8b4548..a0efebeee 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -839,12 +839,6 @@ public class ImapStore extends Store { do { response = mConnection.readResponse(); handleUntaggedResponse(response); - if (response.mCommandContinuationRequested) { - EOLConvertingOutputStream eolOut = new EOLConvertingOutputStream(mConnection.mOut); - eolOut.write('\r'); - eolOut.write('\n'); - eolOut.flush(); - } while (response.more()); } while (response.mTag == null); @@ -2410,17 +2404,6 @@ public class ImapStore extends Store { return executeSimpleCommand(command, sensitive, null); } -// public void logResponse (ImapList response) { -// for(int i=0;i executeSimpleCommand(String command, boolean sensitive, UntaggedHandler untaggedHandler) throws IOException, ImapException, MessagingException { String commandToLog = command; From 396005974a269d03f66785f53aa91adbd4c2539d Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 21:33:53 +0100 Subject: [PATCH 09/19] Cleaned up ImapStore.ImapFolder.appendMessages() --- src/com/fsck/k9/mail/store/ImapStore.java | 85 +++++++++++++---------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index abd8cd2e2..461339d41 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -54,7 +54,6 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.PowerManager; -import android.text.TextUtils; import android.util.Log; import com.beetstra.jutf7.CharsetProvider; @@ -62,6 +61,7 @@ import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.R; import com.fsck.k9.controller.MessageRetrievalListener; +import com.fsck.k9.helper.StringUtils; import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.power.TracingPowerManager; import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; @@ -1099,7 +1099,6 @@ public class ImapStore extends Store { do { response = mConnection.readResponse(); handleUntaggedResponse(response); - while (response.more()); } while (response.mTag == null); /* @@ -1953,21 +1952,30 @@ public class ImapStore extends Store { } /** - * Appends the given messages to the selected folder. This implementation also determines - * the new UID of the given message on the IMAP server and sets the Message's UID to the - * new server UID. + * Appends the given messages to the selected folder. + * + *

+ * This implementation also determines the new UIDs of the given messages on the IMAP + * server and changes the messages' UIDs to the new server UIDs. + *

+ * + * @param messages + * The messages to append to the folder. + * + * @return The mapping of original message UIDs to the new server UIDs. */ @Override public Map appendMessages(Message[] messages) throws MessagingException { checkOpen(); try { - Map uidMap = null; + Map uidMap = new HashMap(); for (Message message : messages) { mConnection.sendCommand( String.format("APPEND %s (%s) {%d}", encodeString(encodeFolderName(getPrefixedName())), combineFlags(message.getFlags()), message.calculateSize()), false); + ImapResponse response; do { response = mConnection.readResponse(); @@ -1981,53 +1989,54 @@ public class ImapStore extends Store { } } while (response.mTag == null); - /* - * If the server supports UIDPLUS, then along with the APPEND response it will return an APPENDUID response code. - * e.g. 11 OK [APPENDUID 2 238268] APPEND completed - * - * We can use the UID included in this response to update our records. - * - * Code imported from AOSP Email as of git rev a5c3744a247e432acf9f571a9dfb55321c4baa1a - */ - Object responseList = response.get(1); + if (response.size() > 1) { + /* + * If the server supports UIDPLUS, then along with the APPEND response it + * will return an APPENDUID response code, e.g. + * + * 11 OK [APPENDUID 2 238268] APPEND completed + * + * We can use the UID included in this response to update our records. + */ + Object responseList = response.get(1); - if (responseList instanceof ImapList) { - final ImapList appendList = (ImapList) responseList; - if ((appendList.size() >= 3) && appendList.getString(0).equals("APPENDUID")) { - String newUid = appendList.getString(2); + if (responseList instanceof ImapList) { + ImapList appendList = (ImapList) responseList; + if (appendList.size() >= 3 && + appendList.getString(0).equals("APPENDUID")) { - /* - * We need uidMap to be null initially to maintain consistency with the behavior of other similar methods (copyMessages, moveMessages) which - * return null if new UIDs are not available. Therefore, we initialize uidMap over here. - */ - if (uidMap == null) { - uidMap = new HashMap(); - } - uidMap.put(message.getUid(), newUid); - if (!TextUtils.isEmpty(newUid)) { - message.setUid(newUid); - continue; + String newUid = appendList.getString(2); + + if (!StringUtils.isNullOrEmpty(newUid)) { + message.setUid(newUid); + uidMap.put(message.getUid(), newUid); + continue; + } } } } - /* - * This part is executed in case the server does not support UIDPLUS or does not implement the APPENDUID response code. + * This part is executed in case the server does not support UIDPLUS or does + * not implement the APPENDUID response code. */ String newUid = getUidFromMessageId(message); - if (K9.DEBUG) + if (K9.DEBUG) { Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId()); + } - if (newUid != null) { - if (uidMap == null) { - uidMap = new HashMap(); - } + if (!StringUtils.isNullOrEmpty(newUid)) { uidMap.put(message.getUid(), newUid); message.setUid(newUid); } } - return uidMap; + + /* + * We need uidMap to be null if new UIDs are not available to maintain consistency + * with the behavior of other similar methods (copyMessages, moveMessages) which + * return null. + */ + return (uidMap.size() == 0) ? null : uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); } From 3e4e6c72fd5180db967ddeaca5febe47954b7121 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 22:12:44 +0100 Subject: [PATCH 10/19] Cleaned up ImapStore.ImapFolder.copyMessages() --- src/com/fsck/k9/mail/store/ImapStore.java | 108 ++++++++++++++-------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 461339d41..32b07a6bd 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -1062,36 +1062,54 @@ public class ImapStore extends Store { } } + /** + * Copies the given messages to the specified folder. + * + *

+ * Note: + * Only the UIDs of the given {@link Message} instances are used. It is assumed that all + * UIDs represent valid messages in this folder. + *

+ * + * @param messages + * The messages to copy to the specfied folder. + * @param folder + * The name of the target folder. + * + * @return The mapping of original message UIDs to the new server UIDs. + */ @Override - public Map copyMessages(Message[] messages, Folder folder) throws MessagingException { + public Map copyMessages(Message[] messages, Folder folder) + throws MessagingException { if (!(folder instanceof ImapFolder)) { throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder"); } - if (messages.length == 0) + if (messages.length == 0) { return null; + } ImapFolder iFolder = (ImapFolder)folder; checkOpen(); String[] uids = new String[messages.length]; for (int i = 0, count = messages.length; i < count; i++) { - - // Not bothering to sort the UIDs in ascending order while sending the command for convenience, and because it does not make a difference. uids[i] = messages[i].getUid(); } + try { String remoteDestName = encodeString(encodeFolderName(iFolder.getPrefixedName())); if (!exists(remoteDestName)) { - /* - * If the remote trash folder doesn't exist we try to create it. - */ - if (K9.DEBUG) - Log.i(K9.LOG_TAG, "IMAPMessage.copyMessages: attempting to create remote '" + remoteDestName + "' folder for " + getLogId()); + // If the remote trash folder doesn't exist we try to create it. + if (K9.DEBUG) { + Log.i(K9.LOG_TAG, "Attempting to create remote folder '" + remoteDestName + + "' for " + getLogId()); + } iFolder.create(FolderType.HOLDS_MESSAGES); } + //TODO: Split this into multiple commands if the command exceeds a certain length. mConnection.sendCommand(String.format("UID COPY %s %s", Utility.combine(uids, ','), remoteDestName), false); @@ -1101,45 +1119,55 @@ public class ImapStore extends Store { handleUntaggedResponse(response); } while (response.mTag == null); - /* - * If the server supports UIDPLUS, then along with the COPY response it will return an COPYUID response code. - * e.g. 24 OK [COPYUID 38505 304,319:320 3956:3958] Success - * - * COPYUID is followed by UIDVALIDITY, set of UIDs of copied messages from the source folder and set of corresponding UIDs assigned to them in the destination folder. - * - * We can use the new UIDs included in this response to update our records. - */ - - Object responseList = response.get(1); - Map uidMap = null; + if (response.size() > 1) { + /* + * If the server supports UIDPLUS, then along with the COPY response it will + * return an COPYUID response code, e.g. + * + * 24 OK [COPYUID 38505 304,319:320 3956:3958] Success + * + * COPYUID is followed by UIDVALIDITY, the set of UIDs of copied messages from + * the source folder and the set of corresponding UIDs assigned to them in the + * destination folder. + * + * We can use the new UIDs included in this response to update our records. + */ + Object responseList = response.get(1); - if (responseList instanceof ImapList) { - final ImapList copyList = (ImapList) responseList; - if ((copyList.size() >= 4) && copyList.getString(0).equals("COPYUID")) { - List srcUids = parseSequenceSet(copyList.getString(2)); - List destUids = parseSequenceSet(copyList.getString(3)); - if (srcUids != null && destUids != null) { - if (srcUids.size() == destUids.size()) { - Iterator srcUidsIterator = srcUids.iterator(); - Iterator destUidsIterator = destUids.iterator(); - uidMap = new HashMap(); - while (srcUidsIterator.hasNext() && destUidsIterator.hasNext()) { - uidMap.put(srcUidsIterator.next(), destUidsIterator.next()); + if (responseList instanceof ImapList) { + final ImapList copyList = (ImapList) responseList; + if (copyList.size() >= 4 && copyList.getString(0).equals("COPYUID")) { + List srcUids = parseSequenceSet(copyList.getString(2)); + List destUids = parseSequenceSet(copyList.getString(3)); + + if (srcUids != null && destUids != null) { + if (srcUids.size() == destUids.size()) { + Iterator srcUidsIterator = srcUids.iterator(); + Iterator destUidsIterator = destUids.iterator(); + uidMap = new HashMap(); + while (srcUidsIterator.hasNext() && + destUidsIterator.hasNext()) { + String srcUid = srcUidsIterator.next(); + String destUid = destUidsIterator.next(); + uidMap.put(srcUid, destUid); + } + } else { + if (K9.DEBUG) { + Log.v(K9.LOG_TAG, "Parse error: size of source UIDs " + + "list is not the same as size of destination " + + "UIDs list."); + } } } else { - if(K9.DEBUG) - Log.v(K9.LOG_TAG, "Parse error: size of source UIDs list is not the same as size of destination UIDs list."); + if (K9.DEBUG) { + Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); + } } - } else { - if(K9.DEBUG) - Log.v(K9.LOG_TAG, "Parsing of the sequence set failed."); } } - } else { - if(K9.DEBUG) - Log.v(K9.LOG_TAG, "Expected COPYUID response was not found."); } + return uidMap; } catch (IOException ioe) { throw ioExceptionHandler(mConnection, ioe); From 89e0ed3c03a6fbab38ffa4c7b45c4fdd4a63de1b Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 22:34:50 +0100 Subject: [PATCH 11/19] Notify listeners of UID changes after remote copy/move --- src/com/fsck/k9/controller/MessagingController.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 61ae0f1b4..6673df75f 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2227,10 +2227,14 @@ public class MessagingController implements Runnable { while (remoteSrcUidsIterator.hasNext()) { String remoteSrcUid = remoteSrcUidsIterator.next(); String localDestUid = localUidMap.get(remoteSrcUid); + String newUid = remoteUidMap.get(remoteSrcUid); Message localDestMessage = localDestFolder.getMessage(localDestUid); - localDestMessage.setUid(remoteUidMap.get(remoteSrcUid)); + localDestMessage.setUid(newUid); localDestFolder.changeUid((LocalMessage)localDestMessage); + for (MessagingListener l : getListeners()) { + l.messageUidChanged(account, destFolder, localDestUid, newUid); + } } } } finally { From 4ed77c7a29a508240e926854505953391bb17dfb Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 22:36:57 +0100 Subject: [PATCH 12/19] Avoid NullPointerException --- src/com/fsck/k9/controller/MessagingController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 6673df75f..06fb59370 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -2189,7 +2189,7 @@ public class MessagingController implements Runnable { Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy: source folder = " + srcFolder + ", " + messages.size() + " messages, destination folder = " + destFolder + ", isCopy = " + isCopy); - Map remoteUidMap = new HashMap(); + Map remoteUidMap = null; if (!isCopy && destFolder.equals(account.getTrashFolderName())) { if (K9.DEBUG) @@ -2220,7 +2220,7 @@ public class MessagingController implements Runnable { * This next part is used to bring the local UIDs of the local destination folder * upto speed with the remote UIDs of remote destionation folder. */ - if (!localUidMap.isEmpty() && !remoteUidMap.isEmpty()) { + if (!localUidMap.isEmpty() && remoteUidMap != null && !remoteUidMap.isEmpty()) { Set remoteSrcUids = remoteUidMap.keySet(); Iterator remoteSrcUidsIterator = remoteSrcUids.iterator(); From 023c60513eeb308f280c836b3334c0959d5de4b3 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 23:37:44 +0100 Subject: [PATCH 13/19] Fixed UID mapping return value of LocalStore.moveMessages() --- src/com/fsck/k9/mail/store/LocalStore.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index ca8980ab0..c36350dfa 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -1963,7 +1963,10 @@ public class LocalStore extends Store implements Serializable { Log.d(K9.LOG_TAG, "Updating folder_id to " + lDestFolder.getId() + " for message with UID " + message.getUid() + ", id " + lMessage.getId() + " currently in folder " + getName()); - message.setUid(K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString()); + String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); + message.setUid(newUid); + + uidMap.put(oldUID, newUid); db.execSQL("UPDATE messages " + "SET folder_id = ?, uid = ? " + "WHERE id = ?", new Object[] { lDestFolder.getId(), @@ -1971,10 +1974,15 @@ public class LocalStore extends Store implements Serializable { lMessage.getId() }); + /* + * Add a placeholder message so we won't download the original + * message again if we synchronize before the remote move is + * complete. + */ LocalMessage placeHolder = new LocalMessage(oldUID, LocalFolder.this); placeHolder.setFlagInternal(Flag.DELETED, true); placeHolder.setFlagInternal(Flag.SEEN, true); - uidMap.putAll(appendMessages(new Message[] { placeHolder })); + appendMessages(new Message[] { placeHolder }); } } catch (MessagingException e) { throw new WrappedException(e); From c3885bc475f95dc655d3adbc706749c3d78d0add Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 16 Feb 2012 23:52:44 +0100 Subject: [PATCH 14/19] Cleaned up LocalFolder.appendMessages() a bit --- src/com/fsck/k9/mail/store/LocalStore.java | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index c36350dfa..bf797ebfe 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -2090,16 +2090,27 @@ public class LocalStore extends Store implements Serializable { long oldMessageId = -1; String uid = message.getUid(); - if (uid == null && !copy) { - uid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); - message.setUid(uid); - } else if (copy) { - String randomLocalUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); - if (uid != null) { + if (uid == null || copy) { + /* + * Create a new message in the database + */ + String randomLocalUid = K9.LOCAL_UID_PREFIX + + UUID.randomUUID().toString(); + + if (copy) { + // Save mapping: source UID -> target UID uidMap.put(uid, randomLocalUid); + } else { + // Modify the Message instance to reference the new UID + message.setUid(randomLocalUid); } + + // The message will be saved with the newly generated UID uid = randomLocalUid; } else { + /* + * Replace an existing message in the database + */ LocalMessage oldMessage = (LocalMessage) getMessage(uid); if (oldMessage != null) { From f7299a69d44efbc36d864a0096d1f741d2a797d6 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 00:43:35 +0100 Subject: [PATCH 15/19] Keep track of UIDs when moving messages to the trash folder --- src/com/fsck/k9/controller/MessagingController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 06fb59370..cc09d8e31 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -3515,6 +3515,7 @@ public class MessagingController implements Runnable { } Store localStore = account.getLocalStore(); localFolder = localStore.getFolder(folder); + Map uidMap = null; if (folder.equals(account.getTrashFolderName()) || K9.FOLDER_NONE.equals(account.getTrashFolderName())) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Deleting messages in trash folder or trash set to -None-, not copying"); @@ -3529,7 +3530,7 @@ public class MessagingController implements Runnable { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Deleting messages in normal folder, moving"); - localFolder.moveMessages(messages, localTrashFolder); + uidMap = localFolder.moveMessages(messages, localTrashFolder); } } @@ -3562,7 +3563,7 @@ public class MessagingController implements Runnable { if (folder.equals(account.getTrashFolderName())) { queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids); } else { - queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids); + queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids, uidMap); } processPendingCommands(account); } else if (account.getDeletePolicy() == Account.DELETE_POLICY_MARK_AS_READ) { From c1ed0c78a9c8c08178565ccfa781cb9762884ad0 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 01:59:04 +0100 Subject: [PATCH 16/19] Introduced new pending command to retain upgradability --- .../k9/controller/MessagingController.java | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index cc09d8e31..008b9afe0 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -117,6 +117,7 @@ public class MessagingController implements Runnable { private static final String PENDING_COMMAND_MOVE_OR_COPY = "com.fsck.k9.MessagingController.moveOrCopy"; private static final String PENDING_COMMAND_MOVE_OR_COPY_BULK = "com.fsck.k9.MessagingController.moveOrCopyBulk"; + private static final String PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW = "com.fsck.k9.MessagingController.moveOrCopyBulkNew"; private static final String PENDING_COMMAND_EMPTY_TRASH = "com.fsck.k9.MessagingController.emptyTrash"; private static final String PENDING_COMMAND_SET_FLAG_BULK = "com.fsck.k9.MessagingController.setFlagBulk"; private static final String PENDING_COMMAND_SET_FLAG = "com.fsck.k9.MessagingController.setFlag"; @@ -1902,6 +1903,8 @@ public class MessagingController implements Runnable { } else if (PENDING_COMMAND_MARK_ALL_AS_READ.equals(command.command)) { processPendingMarkAllAsRead(command, account); } else if (PENDING_COMMAND_MOVE_OR_COPY_BULK.equals(command.command)) { + processPendingMoveOrCopyOld2(command, account); + } else if (PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW.equals(command.command)) { processPendingMoveOrCopy(command, account); } else if (PENDING_COMMAND_MOVE_OR_COPY.equals(command.command)) { processPendingMoveOrCopyOld(command, account); @@ -2081,7 +2084,7 @@ public class MessagingController implements Runnable { return; } PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK; + command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; int length = 3 + uids.length; command.arguments = new String[length]; @@ -2101,7 +2104,7 @@ public class MessagingController implements Runnable { return; } PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK; + command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; int length = 4 + uidMap.keySet().size() + uidMap.values().size(); command.arguments = new String[length]; @@ -2114,6 +2117,39 @@ public class MessagingController implements Runnable { queuePendingCommand(account, command); } } + + /** + * Convert pending command to new format and call + * {@link #processPendingMoveOrCopy(PendingCommand, Account)}. + * + *

+ * TODO: This method is obsolete and is only for transition from K-9 4.0 to K-9 4.2 + * Eventually, it should be removed. + *

+ * + * @param command + * Pending move/copy command in old format. + * @param account + * The account the pending command belongs to. + * + * @throws MessagingException + * In case of an error. + */ + private void processPendingMoveOrCopyOld2(PendingCommand command, Account account) + throws MessagingException { + PendingCommand newCommand = new PendingCommand(); + int len = command.arguments.length; + newCommand.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; + newCommand.arguments = new String[len + 1]; + newCommand.arguments[0] = command.arguments[0]; + newCommand.arguments[1] = command.arguments[1]; + newCommand.arguments[2] = command.arguments[2]; + newCommand.arguments[3] = Boolean.toString(false); + System.arraycopy(command.arguments, 3, newCommand.arguments, 4, len - 3); + + processPendingMoveOrCopy(newCommand, account); + } + /** * Process a pending trash message command. * From deb01bcd16184eeaeac63b9f22c5b91ad6600682 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 14:59:50 +0100 Subject: [PATCH 17/19] Imported ImapUtility.java from AOSP Email Modified it to fit our needs. --- .../fsck/k9/mail/store/imap/ImapUtility.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/com/fsck/k9/mail/store/imap/ImapUtility.java diff --git a/src/com/fsck/k9/mail/store/imap/ImapUtility.java b/src/com/fsck/k9/mail/store/imap/ImapUtility.java new file mode 100644 index 000000000..d0ac2b31a --- /dev/null +++ b/src/com/fsck/k9/mail/store/imap/ImapUtility.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 The K-9 Dog Walkers + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fsck.k9.mail.store.imap; + +import android.util.Log; + +import com.fsck.k9.K9; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for use with IMAP. + */ +public class ImapUtility { + /** + * Gets all of the values in a sequence set per RFC 3501. + * + *

+ * Any ranges are expanded into a list of individual numbers. + *

+ * + *
+     * sequence-number = nz-number / "*"
+     * sequence-range  = sequence-number ":" sequence-number
+     * sequence-set    = (sequence-number / sequence-range) *("," sequence-set)
+     * 
+ * + * @param set + * The sequence set string as received by the server. + * + * @return The list of IDs as strings in this sequence set. If the set is invalid, an empty + * list is returned. + */ + public static List getImapSequenceValues(String set) { + ArrayList list = new ArrayList(); + if (set != null) { + String[] setItems = set.split(","); + for (String item : setItems) { + if (item.indexOf(':') == -1) { + // simple item + try { + Integer.parseInt(item); // Don't need the value; just ensure it's valid + list.add(item); + } catch (NumberFormatException e) { + Log.d(K9.LOG_TAG, "Invalid UID value", e); + } + } else { + // range + list.addAll(getImapRangeValues(item)); + } + } + } + + return list; + } + + /** + * Expand the given number range into a list of individual numbers. + * + *
+     * sequence-number = nz-number / "*"
+     * sequence-range  = sequence-number ":" sequence-number
+     * sequence-set    = (sequence-number / sequence-range) *("," sequence-set)
+     * 
+ * + * @param range + * The range string as received by the server. + * + * @return The list of IDs as strings in this range. If the range is not valid, an empty list + * is returned. + */ + public static List getImapRangeValues(String range) { + ArrayList list = new ArrayList(); + try { + if (range != null) { + int colonPos = range.indexOf(':'); + if (colonPos > 0) { + int first = Integer.parseInt(range.substring(0, colonPos)); + int second = Integer.parseInt(range.substring(colonPos + 1)); + if (first < second) { + for (int i = first; i <= second; i++) { + list.add(Integer.toString(i)); + } + } else { + for (int i = first; i >= second; i--) { + list.add(Integer.toString(i)); + } + } + } + } + } catch (NumberFormatException e) { + Log.d(K9.LOG_TAG, "Invalid range value", e); + } + + return list; + } +} From 5083b8f1e866a6783f5afe074d13398d493513db Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 15:01:30 +0100 Subject: [PATCH 18/19] Use newly imported AOSP code instead of ImapStore.parseSequenceSet() --- src/com/fsck/k9/mail/store/ImapStore.java | 63 ++--------------------- 1 file changed, 5 insertions(+), 58 deletions(-) diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 32b07a6bd..b0ecd2adc 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -90,6 +90,7 @@ import com.fsck.k9.mail.internet.MimeMultipart; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.ImapResponseParser.ImapList; import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse; +import com.fsck.k9.mail.store.imap.ImapUtility; import com.fsck.k9.mail.transport.imap.ImapSettings; import com.jcraft.jzlib.JZlib; import com.jcraft.jzlib.ZOutputStream; @@ -1138,8 +1139,10 @@ public class ImapStore extends Store { if (responseList instanceof ImapList) { final ImapList copyList = (ImapList) responseList; if (copyList.size() >= 4 && copyList.getString(0).equals("COPYUID")) { - List srcUids = parseSequenceSet(copyList.getString(2)); - List destUids = parseSequenceSet(copyList.getString(3)); + List srcUids = ImapUtility.getImapSequenceValues( + copyList.getString(2)); + List destUids = ImapUtility.getImapSequenceValues( + copyList.getString(3)); if (srcUids != null && destUids != null) { if (srcUids.size() == destUids.size()) { @@ -1174,62 +1177,6 @@ public class ImapStore extends Store { } } - /** - * Can be used to parse sequence sets or UID sets appearing is responses such as COPYUID. - * e.g. [COPYUID 38505 304,319:320 3956:3958] - * - * @param set - * @return List sequenceSet - */ - private List parseSequenceSet(String set) { - if (set == null) { - return null; - } - int index = 0; - List sequenceList = new ArrayList(); - String element = ""; - - while (index < set.length()) { - if (set.charAt(index) == ':') { - String upperBound = ""; - index++; - while (index < set.length()) { - if (!(set.charAt(index) == ',')) { - upperBound += set.charAt(index); - index++; - } else { - break; - } - } - - int lower = Integer.parseInt(element); - int upper = Integer.parseInt(upperBound); - - if (lower < upper) { - for (int i = lower; i <= upper; i++) { - sequenceList.add(String.valueOf(i)); - } - } else { - for (int i = upper; i <= lower; i++) { - sequenceList.add(String.valueOf(i)); - } - } - - element = ""; - } else if (set.charAt(index) == ',') { - sequenceList.add(element); - element = ""; - } else { - element += set.charAt(index); - } - index++; - } - if (!element.equals("")) { - sequenceList.add(element); - } - return sequenceList; - } - @Override public Map moveMessages(Message[] messages, Folder folder) throws MessagingException { if (messages.length == 0) From 33029a1f17149a218d6bb03ce8a4629eb5c4e0b7 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 17 Feb 2012 15:34:10 +0100 Subject: [PATCH 19/19] Imported unit tests for ImapUtility from AOSP Email --- .../k9/mail/store/imap/ImapUtilityTest.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java diff --git a/tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java b/tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java new file mode 100644 index 000000000..496d6922e --- /dev/null +++ b/tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 The K-9 Dog Walkers + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.fsck.k9.mail.store.imap; + +import java.util.List; +import android.test.MoreAsserts; +import junit.framework.TestCase; + +public class ImapUtilityTest extends TestCase { + /** + * Test getting elements of an IMAP sequence set. + */ + public void testGetImapSequenceValues() { + String[] expected; + List actual; + + // Test valid sets + expected = new String[] {"1"}; + actual = ImapUtility.getImapSequenceValues("1"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"1", "3", "2"}; + actual = ImapUtility.getImapSequenceValues("1,3,2"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"4", "5", "6"}; + actual = ImapUtility.getImapSequenceValues("4:6"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"9", "8", "7"}; + actual = ImapUtility.getImapSequenceValues("9:7"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"1", "2", "3", "4", "9", "8", "7"}; + actual = ImapUtility.getImapSequenceValues("1,2:4,9:7"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + // Test partially invalid sets + expected = new String[] { "1", "5" }; + actual = ImapUtility.getImapSequenceValues("1,x,5"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] { "1", "2", "3" }; + actual = ImapUtility.getImapSequenceValues("a:d,1:3"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + // Test invalid sets + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues(""); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues(null); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues("a"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapSequenceValues("1:x"); + MoreAsserts.assertEquals(expected, actual.toArray()); + } + + /** + * Test getting elements of an IMAP range. + */ + public void testGetImapRangeValues() { + String[] expected; + List actual; + + // Test valid ranges + expected = new String[] {"1", "2", "3"}; + actual = ImapUtility.getImapRangeValues("1:3"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[] {"16", "15", "14"}; + actual = ImapUtility.getImapRangeValues("16:14"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + // Test in-valid ranges + expected = new String[0]; + actual = ImapUtility.getImapRangeValues(""); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues(null); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("a"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("6"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("1:3,6"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("1:x"); + MoreAsserts.assertEquals(expected, actual.toArray()); + + expected = new String[0]; + actual = ImapUtility.getImapRangeValues("1:*"); + MoreAsserts.assertEquals(expected, actual.toArray()); + } +}